diff --git a/src/File/FileRequest.py b/src/File/FileRequest.py index 42d5261e..521ab6d6 100644 --- a/src/File/FileRequest.py +++ b/src/File/FileRequest.py @@ -257,6 +257,13 @@ class FileRequest(object): if site.addPeer(*address): added += 1 + # Add sent peers to site + for packed_address in params.get("peers_i2p", []): + address = helper.unpackI2PAddress(packed_address) + got_peer_keys.append("%s:%s" % address) + if site.addPeer(*address): + added += 1 + # Send back peers that is not in the sent list and connectable (not port 0) packed_peers = helper.packPeers(site.getConnectablePeers(params["need"], got_peer_keys)) @@ -265,7 +272,7 @@ class FileRequest(object): if config.verbose: self.log.debug( "Added %s peers to %s using pex, sending back %s" % - (added, site, len(packed_peers["ip4"]) + len(packed_peers["onion"])) + (added, site, len(packed_peers["ip4"]) + len(packed_peers["onion"]) + len(packed_peers["i2p"])) ) back = {} @@ -273,6 +280,8 @@ class FileRequest(object): back["peers"] = packed_peers["ip4"] if packed_peers["onion"]: back["peers_onion"] = packed_peers["onion"] + if packed_peers["i2p"]: + back["peers_i2p"] = packed_peers["i2p"] self.response(back) @@ -317,14 +326,20 @@ class FileRequest(object): back_ip4 = {} back_onion = {} + back_i2p = {} for hash_id, peers in found.iteritems(): back_onion[hash_id] = [helper.packOnionAddress(peer.ip, peer.port) for peer in peers if peer.ip.endswith("onion")] - back_ip4[hash_id] = [helper.packAddress(peer.ip, peer.port) for peer in peers if not peer.ip.endswith("onion")] + back_i2p[hash_id] = [helper.packI2PAddress(peer.ip, peer.port) for peer in peers if peer.ip.endswith("i2p")] + back_ip4[hash_id] = [helper.packAddress(peer.ip, peer.port) for peer in peers if not (peer.ip.endswith("onion") or peer.ip.endswith("i2p"))] # Check my hashfield + # TODO Is it implied that a site address can only be on Tor, I2P or clearnet at once? if self.server.tor_manager and self.server.tor_manager.site_onions.get(site.address): # Running onion my_ip = helper.packOnionAddress(self.server.tor_manager.site_onions[site.address], self.server.port) my_back = back_onion + elif self.server.i2p_manager and self.server.i2p_manager.site_dests.get(site.address): # Running I2P dest + my_ip = helper.packI2PAddress(self.server.i2p_manager.site_dests[site.address], self.server.port) + my_back = back_i2p elif config.ip_external: # External ip defined my_ip = helper.packAddress(config.ip_external, self.server.port) my_back = back_ip4 @@ -340,10 +355,10 @@ class FileRequest(object): if config.verbose: self.log.debug( - "Found: IP4: %s, Onion: %s for %s hashids" % - (len(back_ip4), len(back_onion), len(params["hash_ids"])) + "Found: IP4: %s, Onion: %s, I2P: %s for %s hashids" % + (len(back_ip4), len(back_onion), len(back_i2p), len(params["hash_ids"])) ) - self.response({"peers": back_ip4, "peers_onion": back_onion}) + self.response({"peers": back_ip4, "peers_onion": back_onion, "peers_i2p": back_i2p}) def actionSetHashfield(self, params): site = self.sites.get(params["site"]) diff --git a/src/Peer/Peer.py b/src/Peer/Peer.py index 3cf4694b..c5ff788d 100644 --- a/src/Peer/Peer.py +++ b/src/Peer/Peer.py @@ -105,6 +105,8 @@ class Peer(object): def packMyAddress(self): if self.ip.endswith(".onion"): return helper.packOnionAddress(self.ip, self.port) + elif self.ip.endswith(".i2p"): + return helper.packI2PAddress(self.ip, self.port) else: return helper.packAddress(self.ip, self.port) @@ -241,6 +243,8 @@ class Peer(object): request = {"site": site.address, "peers": packed_peers["ip4"], "need": need_num} if packed_peers["onion"]: request["peers_onion"] = packed_peers["onion"] + if packed_peers["i2p"]: + request["peers_i2p"] = packed_peers["i2p"] res = self.request("pex", request) if not res or "error" in res: return False @@ -255,6 +259,11 @@ class Peer(object): address = helper.unpackOnionAddress(peer) if site.addPeer(*address): added += 1 + # I2P + for peer in res.get("peers_i2p", []): + address = helper.unpackI2PAddress(peer) + if site.addPeer(*address): + added += 1 if added: self.log("Added peers using pex: %s" % added) @@ -292,6 +301,11 @@ class Peer(object): if not hash in back: back[hash] = [] back[hash] += map(helper.unpackOnionAddress, onion_peers) + # Unpack I2P dest + for hash, i2p_peers in res.get("peers_i2p", {}).items()[0:30]: + if not hash in back: + back[hash] = [] + back[hash] += map(helper.unpackI2PAddress, i2p_peers) return back diff --git a/src/Site/Site.py b/src/Site/Site.py index 18acf56e..f5f4ed98 100644 --- a/src/Site/Site.py +++ b/src/Site/Site.py @@ -377,11 +377,17 @@ class Site(object): # Find out my ip and port tor_manager = self.connection_server.tor_manager + i2p_manager = self.connection_server.i2p_manager if tor_manager and tor_manager.enabled and tor_manager.start_onions: my_ip = tor_manager.getOnion(self.address) if my_ip: my_ip += ".onion" my_port = config.fileserver_port + elif i2p_manager and i2p_manager.enabled and i2p_manager.start_dests: + my_ip = i2p_manager.getDest(self.address) + if my_ip: + my_ip += ".i2p" + my_port = 0 else: my_ip = config.ip_external if self.connection_server.port_opened: diff --git a/src/Test/TestI2P.py b/src/Test/TestI2P.py index 26c51133..30603dfa 100644 --- a/src/Test/TestI2P.py +++ b/src/Test/TestI2P.py @@ -1,6 +1,8 @@ import pytest import time +from File import FileServer + # stats.i2p TEST_B64 = 'Okd5sN9hFWx-sr0HH8EFaxkeIMi6PC5eGTcjM1KB7uQ0ffCUJ2nVKzcsKZFHQc7pLONjOs2LmG5H-2SheVH504EfLZnoB7vxoamhOMENnDABkIRGGoRisc5AcJXQ759LraLRdiGSR0WTHQ0O1TU0hAz7vAv3SOaDp9OwNDr9u902qFzzTKjUTG5vMTayjTkLo2kOwi6NVchDeEj9M7mjj5ySgySbD48QpzBgcqw1R27oIoHQmjgbtbmV2sBL-2Tpyh3lRe1Vip0-K0Sf4D-Zv78MzSh8ibdxNcZACmZiVODpgMj2ejWJHxAEz41RsfBpazPV0d38Mfg4wzaS95R5hBBo6SdAM4h5vcZ5ESRiheLxJbW0vBpLRd4mNvtKOrcEtyCvtvsP3FpA-6IKVswyZpHgr3wn6ndDHiVCiLAQZws4MsIUE1nkfxKpKtAnFZtPrrB8eh7QO9CkH2JBhj7bG0ED6mV5~X5iqi52UpsZ8gnjZTgyG5pOF8RcFrk86kHxAAAA' @@ -62,6 +64,72 @@ class TestI2P: 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 testPex(self, file_server, site, site_temp): + # Register site to currently running fileserver + site.connection_server = file_server + file_server.sites[site.address] = site + # Create a new file server to emulate new peer connecting to our peer + file_server_temp = FileServer("127.0.0.1", 1545) + site_temp.connection_server = file_server_temp + file_server_temp.sites[site_temp.address] = site_temp + # We will request peers from this + peer_source = site_temp.addPeer("127.0.0.1", 1544) + + # Get ip4 peers from source site + assert peer_source.pex(need_num=10) == 1 # Need >5 to return also return non-connected peers + assert len(site_temp.peers) == 2 # Me, and the other peer + site.addPeer("1.2.3.4", 1555) # Add peer to source site + assert peer_source.pex(need_num=10) == 1 + assert len(site_temp.peers) == 3 + assert "1.2.3.4:1555" in site_temp.peers + + # Get I2P peers from source site + site.addPeer(TEST_B64+".i2p", 1555) + assert TEST_B64+".i2p:1555" not in site_temp.peers + assert peer_source.pex(need_num=10) == 1 # Need >5 to return also return non-connected peers + assert TEST_B64+".i2p:1555" in site_temp.peers + + def testFindHash(self, i2p_manager, file_server, site, site_temp): + file_server.ip_incoming = {} # Reset flood protection + file_server.sites[site.address] = site + assert file_server.i2p_manager == None + file_server.i2p_manager = i2p_manager + + client = FileServer("127.0.0.1", 1545) + client.sites[site_temp.address] = site_temp + site_temp.connection_server = client + + # Add file_server as peer to client + peer_file_server = site_temp.addPeer("127.0.0.1", 1544) + + assert peer_file_server.findHashIds([1234]) == {} + + # Add fake peer with requred hash + fake_peer_1 = site.addPeer(TEST_B64+".i2p", 1544) + fake_peer_1.hashfield.append(1234) + fake_peer_2 = site.addPeer("1.2.3.5", 1545) + fake_peer_2.hashfield.append(1234) + fake_peer_2.hashfield.append(1235) + fake_peer_3 = site.addPeer("1.2.3.6", 1546) + fake_peer_3.hashfield.append(1235) + fake_peer_3.hashfield.append(1236) + + assert peer_file_server.findHashIds([1234, 1235]) == { + 1234: [('1.2.3.5', 1545), (TEST_B64+".i2p", 1544)], + 1235: [('1.2.3.6', 1546), ('1.2.3.5', 1545)] + } + + # Test my address adding + site.content_manager.hashfield.append(1234) + my_i2p_address = i2p_manager.getDest(site_temp.address).base64()+".i2p" + + res = peer_file_server.findHashIds([1234, 1235]) + assert res[1234] == [('1.2.3.5', 1545), (TEST_B64+".i2p", 1544), (my_i2p_address, 1544)] + assert res[1235] == [('1.2.3.6', 1546), ('1.2.3.5', 1545)] + + # Reset + file_server.i2p_manager = None + def testSiteDest(self, i2p_manager): assert i2p_manager.getDest("address1") != i2p_manager.getDest("address2") assert i2p_manager.getDest("address1") == i2p_manager.getDest("address1") diff --git a/src/util/helper.py b/src/util/helper.py index c46a9042..30663881 100644 --- a/src/util/helper.py +++ b/src/util/helper.py @@ -7,6 +7,7 @@ import collections import time import logging import base64 +from i2p.datatypes import Destination def atomicWrite(dest, content, mode="w"): @@ -52,11 +53,13 @@ def shellquote(*args): def packPeers(peers): - packed_peers = {"ip4": [], "onion": []} + packed_peers = {"ip4": [], "onion": [], "i2p": []} for peer in peers: try: if peer.ip.endswith(".onion"): packed_peers["onion"].append(peer.packMyAddress()) + elif peer.ip.endswith(".i2p"): + packed_peers["i2p"].append(peer.packMyAddress()) else: packed_peers["ip4"].append(peer.packMyAddress()) except Exception, err: @@ -86,6 +89,19 @@ def unpackOnionAddress(packed): return base64.b32encode(packed[0:-2]).lower() + ".onion", struct.unpack("H", packed[-2:])[0] +# Destination, port to packed (389+)-byte format +def packI2PAddress(dest, port): + if not isinstance(dest, Destination): + dest = dest.replace(".i2p", "") + dest = Destination(raw=dest, b64=True) + return dest.serialize() + struct.pack("H", port) + + +# From (389+)-byte format to Destination, port +def unpackI2PAddress(packed): + return Destination(raw=packed[0:-2]).base64() + ".i2p", struct.unpack("H", packed[-2:])[0] + + # Get dir from file # Return: data/site/content.json -> data/site def getDirname(path):