Implement I2P connections
This commit is contained in:
parent
6840b80c7b
commit
a0cb3a430c
4 changed files with 81 additions and 18 deletions
|
@ -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"]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
Loading…
Reference in a new issue