diff --git a/src/Connection/Connection.py b/src/Connection/Connection.py index c9455d15..be7bfa18 100644 --- a/src/Connection/Connection.py +++ b/src/Connection/Connection.py @@ -89,6 +89,10 @@ class Connection(object): if not self.server.tor_manager or not self.server.tor_manager.enabled: raise Exception("Can't connect to onion addresses, no Tor controller present") self.sock = self.server.tor_manager.createSocket(self.ip, self.port) + elif self.ip.endswith(".i2p"): + if not self.server.i2p_manager or not self.server.i2p_manager.enabled: + raise Exception("Can't connect to I2P addresses, no SAM API present") + self.sock = self.server.i2p_manager.createSocket(self.ip, self.port) else: self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect((self.ip, int(self.port))) @@ -164,24 +168,32 @@ class Connection(object): # My handshake info def getHandshakeInfo(self): - # No TLS for onion connections - if self.ip.endswith(".onion"): + # No TLS for onion or I2P connections + if self.ip.endswith(".onion") or self.ip.endswith(".i2p"): crypt_supported = [] else: crypt_supported = CryptConnection.manager.crypt_supported - # No peer id for onion connections - if self.ip.endswith(".onion") or self.ip == "127.0.0.1": + # No peer id for onion or I2P connections + if self.ip.endswith(".onion") or self.ip.endswith(".i2p") or self.ip == "127.0.0.1": peer_id = "" else: peer_id = self.server.peer_id - # Setup peer lock from requested onion address - if self.handshake and self.handshake.get("target_ip", "").endswith(".onion"): - target_onion = self.handshake.get("target_ip").replace(".onion", "") # My onion address - onion_sites = {v: k for k, v in self.server.tor_manager.site_onions.items()} # Inverse, Onion: Site address - self.site_lock = onion_sites.get(target_onion) - if not self.site_lock: - self.server.log.error("Unknown target onion address: %s" % target_onion) - self.site_lock = "unknown" + # Setup peer lock from requested onion address or I2P Destination + if self.handshake: + if self.handshake.get("target_ip", "").endswith(".onion"): + target_onion = self.handshake.get("target_ip").replace(".onion", "") # My onion address + onion_sites = {v: k for k, v in self.server.tor_manager.site_onions.items()} # Inverse, Onion: Site address + self.site_lock = onion_sites.get(target_onion) + if not self.site_lock: + self.server.log.error("Unknown target onion address: %s" % target_onion) + self.site_lock = "unknown" + elif self.handshake.get("target_ip", "").endswith(".i2p"): + target_dest = self.handshake.get("target_ip").replace(".i2p", "") # My I2P Destination + dest_sites = {v.base64(): k for k, v in self.server.i2p_manager.site_dests.items()} # Inverse, I2P Destination: Site address + self.site_lock = dest_sites.get(target_dest) + if not self.site_lock: + self.server.log.error("Unknown target I2P Destination: %s" % target_dest) + self.site_lock = "unknown" handshake = { "version": config.version, @@ -195,15 +207,21 @@ class Connection(object): "crypt": self.crypt } if self.site_lock: - handshake["onion"] = self.server.tor_manager.getOnion(self.site_lock) + if self.ip.endswith(".onion"): + handshake["onion"] = self.server.tor_manager.getOnion(self.site_lock) + elif self.ip.endswith(".i2p"): + handshake["i2p"] = self.server.i2p_manager.getDest(self.site_lock).base64() elif self.ip.endswith(".onion"): handshake["onion"] = self.server.tor_manager.getOnion("global") + elif self.ip.endswith(".i2p"): + handshake["i2p"] = self.server.i2p_manager.getDest("global").base64() return handshake def setHandshake(self, handshake): self.handshake = handshake - if handshake.get("port_opened", None) is False and "onion" not in handshake: # Not connectable + if handshake.get("port_opened", None) is False and "onion" not in handshake and \ + "i2p" not in handshake: # Not connectable self.port = 0 else: self.port = handshake["fileserver_port"] # Set peer fileserver port @@ -212,9 +230,13 @@ class Connection(object): self.ip = handshake["onion"] + ".onion" self.updateName() + if handshake.get("i2p") and not self.ip.endswith(".i2p"): # Set incoming connection's I2P Destination + self.ip = handshake["i2p"] + ".i2p" + self.updateName() + # Check if we can encrypt the connection if handshake.get("crypt_supported") and handshake["peer_id"] not in self.server.broken_ssl_peer_ids: - if self.ip.endswith(".onion"): + if self.ip.endswith(".onion") or self.ip.endswith(".i2p"): crypt = None elif handshake.get("crypt"): # Recommended crypt by server crypt = handshake["crypt"] diff --git a/src/Connection/ConnectionServer.py b/src/Connection/ConnectionServer.py index 91d3e4e1..01275442 100644 --- a/src/Connection/ConnectionServer.py +++ b/src/Connection/ConnectionServer.py @@ -12,6 +12,7 @@ from Connection import Connection from Config import config from Crypt import CryptConnection from Crypt import CryptHash +from I2P import I2PManager from Tor import TorManager @@ -28,6 +29,11 @@ class ConnectionServer: else: self.tor_manager = None + if config.i2p != "disabled": + self.i2p_manager = I2PManager(self.handleIncomingConnection) + else: + self.i2p_manager = None + self.connections = [] # Connections self.whitelist = ("127.0.0.1",) # No flood protection on this ips self.ip_incoming = {} # Incoming connections from ip in the last minute to avoid connection flood @@ -96,7 +102,8 @@ class ConnectionServer: connection.handleIncomingConnection(sock) def getConnection(self, ip=None, port=None, peer_id=None, create=True, site=None): - if ip.endswith(".onion") and self.tor_manager.start_onions and site: # Site-unique connection for Tor + if ((ip.endswith(".onion") and self.tor_manager.start_onions) or \ + (ip.endswith(".i2p") and self.i2p_manager.start_dests)) and site: # Site-unique connection for Tor or I2P key = ip + site.address else: key = ip @@ -116,7 +123,8 @@ class ConnectionServer: if connection.ip == ip: if peer_id and connection.handshake.get("peer_id") != peer_id: # Does not match continue - if ip.endswith(".onion") and self.tor_manager.start_onions and connection.site_lock != site.address: + if ((ip.endswith(".onion") and self.tor_manager.start_onions) or \ + (ip.endswith(".i2p") and self.i2p_manager.start_dests)) and connection.site_lock != site.address: # For different site continue if not connection.connected and create: @@ -130,7 +138,8 @@ class ConnectionServer: if port == 0: raise Exception("This peer is not connectable") try: - if ip.endswith(".onion") and self.tor_manager.start_onions and site: # Lock connection to site + if ((ip.endswith(".onion") and self.tor_manager.start_onions) or \ + (ip.endswith(".i2p") and self.i2p_manager.start_dests)) and site: # Lock connection to site connection = Connection(self, ip, port, site_lock=site.address) else: connection = Connection(self, ip, port) diff --git a/src/File/FileServer.py b/src/File/FileServer.py index 8faf9c63..b54ec281 100644 --- a/src/File/FileServer.py +++ b/src/File/FileServer.py @@ -191,6 +191,7 @@ class FileServer(ConnectionServer): self.openport() if self.port_opened is False: self.tor_manager.startOnions() + self.i2p_manager.startDests() if not sites_checking: for address, site in self.sites.items(): # Check sites integrity diff --git a/src/Test/TestI2P.py b/src/Test/TestI2P.py index 02eef10a..26c51133 100644 --- a/src/Test/TestI2P.py +++ b/src/Test/TestI2P.py @@ -31,6 +31,37 @@ class TestI2P: # Delete i2p_manager.delDest(dest) + @pytest.mark.skipif(not pytest.config.getvalue("slow"), reason="--slow not requested (takes around ~ 1min)") + def testConnection(self, i2p_manager, file_server, site, site_temp): + file_server.i2p_manager.start_dests = True + dest = file_server.i2p_manager.getDest(site.address) + assert dest + print "Connecting to", dest.base32() + for retry in range(5): # Wait for Destination creation + time.sleep(10) + try: + connection = file_server.getConnection(dest.base64()+".i2p", 1544) + if connection: + break + except Exception, err: + continue + assert connection.handshake + assert not connection.handshake["peer_id"] # No peer_id for I2P connections + + # Return the same connection without site specified + assert file_server.getConnection(dest.base64()+".i2p", 1544) == connection + # No reuse for different site + assert file_server.getConnection(dest.base64()+".i2p", 1544, site=site) != connection + assert file_server.getConnection(dest.base64()+".i2p", 1544, site=site) == file_server.getConnection(dest.base64()+".i2p", 1544, site=site) + site_temp.address = "1OTHERSITE" + assert file_server.getConnection(dest.base64()+".i2p", 1544, site=site) != file_server.getConnection(dest.base64()+".i2p", 1544, site=site_temp) + + # Only allow to query from the locked site + file_server.sites[site.address] = site + connection_locked = file_server.getConnection(dest.base64()+".i2p", 1544, site=site) + assert "body" in connection_locked.request("getFile", {"site": site.address, "inner_path": "content.json", "location": 0}) + assert connection_locked.request("getFile", {"site": "1OTHERSITE", "inner_path": "content.json", "location": 0})["error"] == "Invalid site" + def testSiteDest(self, i2p_manager): assert i2p_manager.getDest("address1") != i2p_manager.getDest("address2") assert i2p_manager.getDest("address1") == i2p_manager.getDest("address1")