Implement I2P connections

This commit is contained in:
str4d 2016-07-31 01:33:03 +12:00
parent 6840b80c7b
commit a0cb3a430c
4 changed files with 81 additions and 18 deletions

View file

@ -89,6 +89,10 @@ class Connection(object):
if not self.server.tor_manager or not self.server.tor_manager.enabled: 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") raise Exception("Can't connect to onion addresses, no Tor controller present")
self.sock = self.server.tor_manager.createSocket(self.ip, self.port) 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: else:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((self.ip, int(self.port))) self.sock.connect((self.ip, int(self.port)))
@ -164,24 +168,32 @@ class Connection(object):
# My handshake info # My handshake info
def getHandshakeInfo(self): def getHandshakeInfo(self):
# No TLS for onion connections # No TLS for onion or I2P connections
if self.ip.endswith(".onion"): if self.ip.endswith(".onion") or self.ip.endswith(".i2p"):
crypt_supported = [] crypt_supported = []
else: else:
crypt_supported = CryptConnection.manager.crypt_supported crypt_supported = CryptConnection.manager.crypt_supported
# No peer id for onion connections # No peer id for onion or I2P connections
if self.ip.endswith(".onion") or self.ip == "127.0.0.1": if self.ip.endswith(".onion") or self.ip.endswith(".i2p") or self.ip == "127.0.0.1":
peer_id = "" peer_id = ""
else: else:
peer_id = self.server.peer_id peer_id = self.server.peer_id
# Setup peer lock from requested onion address # Setup peer lock from requested onion address or I2P Destination
if self.handshake and self.handshake.get("target_ip", "").endswith(".onion"): if self.handshake:
if self.handshake.get("target_ip", "").endswith(".onion"):
target_onion = self.handshake.get("target_ip").replace(".onion", "") # My onion address 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 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) self.site_lock = onion_sites.get(target_onion)
if not self.site_lock: if not self.site_lock:
self.server.log.error("Unknown target onion address: %s" % target_onion) self.server.log.error("Unknown target onion address: %s" % target_onion)
self.site_lock = "unknown" 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 = { handshake = {
"version": config.version, "version": config.version,
@ -195,15 +207,21 @@ class Connection(object):
"crypt": self.crypt "crypt": self.crypt
} }
if self.site_lock: if self.site_lock:
if self.ip.endswith(".onion"):
handshake["onion"] = self.server.tor_manager.getOnion(self.site_lock) 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"): elif self.ip.endswith(".onion"):
handshake["onion"] = self.server.tor_manager.getOnion("global") handshake["onion"] = self.server.tor_manager.getOnion("global")
elif self.ip.endswith(".i2p"):
handshake["i2p"] = self.server.i2p_manager.getDest("global").base64()
return handshake return handshake
def setHandshake(self, handshake): def setHandshake(self, handshake):
self.handshake = 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 self.port = 0
else: else:
self.port = handshake["fileserver_port"] # Set peer fileserver port self.port = handshake["fileserver_port"] # Set peer fileserver port
@ -212,9 +230,13 @@ class Connection(object):
self.ip = handshake["onion"] + ".onion" self.ip = handshake["onion"] + ".onion"
self.updateName() 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 # 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 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 crypt = None
elif handshake.get("crypt"): # Recommended crypt by server elif handshake.get("crypt"): # Recommended crypt by server
crypt = handshake["crypt"] crypt = handshake["crypt"]

View file

@ -12,6 +12,7 @@ from Connection import Connection
from Config import config from Config import config
from Crypt import CryptConnection from Crypt import CryptConnection
from Crypt import CryptHash from Crypt import CryptHash
from I2P import I2PManager
from Tor import TorManager from Tor import TorManager
@ -28,6 +29,11 @@ class ConnectionServer:
else: else:
self.tor_manager = None self.tor_manager = None
if config.i2p != "disabled":
self.i2p_manager = I2PManager(self.handleIncomingConnection)
else:
self.i2p_manager = None
self.connections = [] # Connections self.connections = [] # Connections
self.whitelist = ("127.0.0.1",) # No flood protection on this ips 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 self.ip_incoming = {} # Incoming connections from ip in the last minute to avoid connection flood
@ -96,7 +102,8 @@ class ConnectionServer:
connection.handleIncomingConnection(sock) connection.handleIncomingConnection(sock)
def getConnection(self, ip=None, port=None, peer_id=None, create=True, site=None): 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 key = ip + site.address
else: else:
key = ip key = ip
@ -116,7 +123,8 @@ class ConnectionServer:
if connection.ip == ip: if connection.ip == ip:
if peer_id and connection.handshake.get("peer_id") != peer_id: # Does not match if peer_id and connection.handshake.get("peer_id") != peer_id: # Does not match
continue 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 # For different site
continue continue
if not connection.connected and create: if not connection.connected and create:
@ -130,7 +138,8 @@ class ConnectionServer:
if port == 0: if port == 0:
raise Exception("This peer is not connectable") raise Exception("This peer is not connectable")
try: 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) connection = Connection(self, ip, port, site_lock=site.address)
else: else:
connection = Connection(self, ip, port) connection = Connection(self, ip, port)

View file

@ -191,6 +191,7 @@ class FileServer(ConnectionServer):
self.openport() self.openport()
if self.port_opened is False: if self.port_opened is False:
self.tor_manager.startOnions() self.tor_manager.startOnions()
self.i2p_manager.startDests()
if not sites_checking: if not sites_checking:
for address, site in self.sites.items(): # Check sites integrity for address, site in self.sites.items(): # Check sites integrity

View file

@ -31,6 +31,37 @@ class TestI2P:
# Delete # Delete
i2p_manager.delDest(dest) 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): def testSiteDest(self, i2p_manager):
assert i2p_manager.getDest("address1") != i2p_manager.getDest("address2") assert i2p_manager.getDest("address1") != i2p_manager.getDest("address2")
assert i2p_manager.getDest("address1") == i2p_manager.getDest("address1") assert i2p_manager.getDest("address1") == i2p_manager.getDest("address1")