Skip to content

Commit

Permalink
Add I2P Support
Browse files Browse the repository at this point in the history
Merge HelloZeroNet#602

Signed-off-by: Marek Küthe <[email protected]>
  • Loading branch information
marek22k committed Oct 22, 2023
1 parent 2900259 commit 20604ce
Show file tree
Hide file tree
Showing 17 changed files with 447 additions and 27 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ Decentralized websites using Bitcoin crypto and the BitTorrent network - https:/
* Password-less [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
based authorization: Your account is protected by the same cryptography as your Bitcoin wallet
* Built-in SQL server with P2P data synchronization: Allows easier site development and faster page load times
* Anonymity: Full Tor network support with .onion hidden services instead of IPv4 addresses
* Anonymity:
* Full Tor network support with .onion hidden services instead of IPv4 addresses
* Full I2P network support with I2P Destinations instead of IPv4 addresses
* TLS encrypted connections
* Automatic uPnP port opening
* Plugin for multiuser (openproxy) support
Expand Down Expand Up @@ -132,7 +134,7 @@ https://zeronet.ipfsscan.io/

* File transactions are not compressed
* No private sites

* ~~No more anonymous than Bittorrent~~ (built-in full Tor and I2P support added)

## How can I create a ZeroNet site?

Expand Down
2 changes: 1 addition & 1 deletion Vagrantfile
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.provision "shell",
inline: "sudo apt-get install msgpack-python python-gevent python-pip python-dev -y"
config.vm.provision "shell",
inline: "sudo pip install msgpack --upgrade"
inline: "sudo pip install -r requirements.txt --upgrade"

end
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ websocket_client
gevent-ws
coincurve
maxminddb
i2p.socket
5 changes: 5 additions & 0 deletions src/Config.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ def createArguments(self):
"http://t.publictracker.xyz:6969/announce",
"https://tracker.lilithraws.cf:443/announce",
"https://tracker.babico.name.tr:443/announce",
"http://opentracker.dg2.i2p/announce",
"http://opentracker.skank.i2p/announce"
]
# Platform specific
if sys.platform.startswith("win"):
Expand Down Expand Up @@ -311,6 +313,9 @@ def createArguments(self):
self.parser.add_argument('--tor_use_bridges', help='Use obfuscated bridge relays to avoid Tor block', action='store_true')
self.parser.add_argument('--tor_hs_limit', help='Maximum number of hidden services in Tor always mode', metavar='limit', type=int, default=10)
self.parser.add_argument('--tor_hs_port', help='Hidden service port in Tor always mode', metavar='limit', type=int, default=15441)

self.parser.add_argument('--i2p', help='enable: Use only for I2P peers, always: Use I2P for every connection', choices=["disable", "enable", "always"], default='enable')
self.parser.add_argument('--i2p_sam', help='I2P SAM API address', metavar='ip:port', default='127.0.0.1:7656')

self.parser.add_argument('--version', action='version', version='ZeroNet %s r%s' % (self.version, self.rev))
self.parser.add_argument('--end', help='Stop multi value argument parsing', action='store_true')
Expand Down
51 changes: 36 additions & 15 deletions src/Connection/Connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ def connect(self):
self.sock = socks.socksocket()
proxy_ip, proxy_port = config.trackers_proxy.split(":")
self.sock.set_proxy(socks.PROXY_TYPE_SOCKS5, proxy_ip, int(proxy_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 = self.createSocket()

Expand Down Expand Up @@ -344,22 +348,27 @@ def handleStream(self, message, buff):
# My handshake info
def getHandshakeInfo(self):
# No TLS for onion connections
if self.ip_type == "onion":
if self.ip_type == "onion" or self.ip_type == "i2p":
crypt_supported = []
elif self.ip in self.server.broken_ssl_ips:
crypt_supported = []
else:
crypt_supported = CryptConnection.manager.crypt_supported
# No peer id for onion connections
if self.ip_type == "onion" or self.ip in config.ip_local:
if self.ip_type == "onion" or self.ip_type == "i2p" or self.ip in config.ip_local:
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") and self.server.tor_manager.start_onions:
self.target_onion = self.handshake.get("target_ip").replace(".onion", "") # My onion address
if not self.server.tor_manager.site_onions.values():
self.server.log.warning("Unknown target onion address: %s" % self.target_onion)
if self.handshake
if self.handshake.get("target_ip", "").endswith(".onion") and self.server.tor_manager.start_onions:
self.target_onion = self.handshake.get("target_ip").replace(".onion", "") # My onion address
if not self.server.tor_manager.site_onions.values():
self.server.log.warning("Unknown target onion address: %s" % self.target_onion)
elif self.handshake.get("target_ip", "").endswith(".i2p") and self.server.i2p_manager.start_dests:
self.target_dest = self.handshake.get("target_ip").replace(".i2p", "") # My I2P Destination
if not dest_sites.get(target_dest):
self.server.log.error("Unknown target I2P Destination: %s" % target_dest)

handshake = {
"version": config.version,
Expand All @@ -378,6 +387,10 @@ def getHandshakeInfo(self):
handshake["onion"] = self.target_onion
elif self.ip_type == "onion":
handshake["onion"] = self.server.tor_manager.getOnion("global")
elif self.target_dest:
handshake["i2p"] = self.target_dest
elif self.ip_type == "i2p":
handshake["i2p"] = self.server.i2p_manager.getDest("global").base64()

if self.is_tracker_connection:
handshake["tracker_connection"] = True
Expand All @@ -397,7 +410,7 @@ def setHandshake(self, handshake):
return False

self.handshake = handshake
if handshake.get("port_opened", None) is False and "onion" not in handshake and not self.is_private_ip: # Not connectable
if handshake.get("port_opened", None) is False and "onion" not in handshake and "i2p" not in handshake and not self.is_private_ip: # Not connectable
self.port = 0
else:
self.port = int(handshake["fileserver_port"]) # Set peer fileserver port
Expand All @@ -416,7 +429,7 @@ def setHandshake(self, handshake):
if type(handshake["crypt_supported"][0]) is bytes:
handshake["crypt_supported"] = [item.decode() for item in handshake["crypt_supported"]] # Backward compatibility

if self.ip_type == "onion" or self.ip in config.ip_local:
if self.ip_type == "onion" or self.ip_type == "i2p" or self.ip in config.ip_local:
crypt = None
elif handshake.get("crypt"): # Recommended crypt by server
crypt = handshake["crypt"]
Expand All @@ -426,13 +439,21 @@ def setHandshake(self, handshake):
if crypt:
self.crypt = crypt

if self.type == "in" and handshake.get("onion") and not self.ip_type == "onion": # Set incoming connection's onion address
if self.server.ips.get(self.ip) == self:
del self.server.ips[self.ip]
self.setIp(handshake["onion"] + ".onion")
self.log("Changing ip to %s" % self.ip)
self.server.ips[self.ip] = self
self.updateName()
if self.type == "in"
if handshake.get("onion") and not self.ip_type == "onion": # Set incoming connection's onion address
if self.server.ips.get(self.ip) == self:
del self.server.ips[self.ip]
self.setIp(handshake["onion"] + ".onion")
self.log("Changing ip to %s" % self.ip)
self.server.ips[self.ip] = self
self.updateName()
if handshake.get("i2p") and not self.ip_type == "i2p": # Set incoming connection's I2P Destination
if self.server.ips.get(self.ip) == self:
del self.server.ips[self.ip]
self.setIp(handshake["i2p"] + ".i2p")
self.log("Changing ip to %s" % self.ip)
self.server.ips[self.ip] = self
self.updateName()

self.event_connected.set(True) # Mark handshake as done
self.event_connected = None
Expand Down
15 changes: 12 additions & 3 deletions src/Connection/ConnectionServer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from Crypt import CryptConnection
from Crypt import CryptHash
from Tor import TorManager
from I2P import I2PManager
from Site import SiteManager


Expand All @@ -38,6 +39,10 @@ def __init__(self, ip=None, port=None, request_handler=None):
self.peer_blacklist = SiteManager.peer_blacklist

self.tor_manager = TorManager(self.ip, self.port)
if config.i2p != "disabled":
self.i2p_manager = I2PManager(self.handleIncomingConnection)
else:
self.i2p_manager = None
self.connections = [] # Connections
self.whitelist = config.ip_local # No flood protection on this ips
self.ip_incoming = {} # Incoming connections from ip in the last minute to avoid connection flood
Expand Down Expand Up @@ -171,10 +176,13 @@ def handleMessage(self, *args, **kwargs):

def getConnection(self, ip=None, port=None, peer_id=None, create=True, site=None, is_tracker_connection=False):
ip_type = helper.getIpType(ip)
has_per_site_onion = (ip.endswith(".onion") or self.port_opened.get(ip_type, None) == False) and self.tor_manager.start_onions and site
if has_per_site_onion: # Site-unique connection for Tor
has_per_site_onion = (((ip.endswith(".onion") or self.port_opened.get("onion", None) == False) and self.tor_manager.start_onions) or \
((ip.endswith(".i2p") or self.port_opened.get("i2p", None) == False) and self.i2p_manager.start_dests)) and site
if has_per_site_onion: # Site-unique connection for Tor or I2P
if ip.endswith(".onion"):
site_onion = self.tor_manager.getOnion(site.address)
elif ip.endswith(".i2p"):
site_onion = self.i2p_manager.getDest(site.address)
else:
site_onion = self.tor_manager.getOnion("global")
key = ip + site_onion
Expand All @@ -196,7 +204,8 @@ def getConnection(self, ip=None, port=None, peer_id=None, create=True, site=None
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 ip.replace(".onion", "") != connection.target_onion:
if (ip.endswith(".onion") and self.tor_manager.start_onions and ip.replace(".onion", "") != connection.target_onion) or \
(ip.endswith(".i2p") and self.i2p_manager.start_dests and ip.replace(".i2p", "") != connection.target_dest):
# For different site
continue
if not connection.connected and create:
Expand Down
12 changes: 10 additions & 2 deletions src/File/FileRequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,13 @@ def actionPex(self, params):
if site.addPeer(*address, source="pex"):
added += 1

# Add sent i2p 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"], ignore=got_peer_keys, allow_private=False))

Expand All @@ -335,7 +342,8 @@ def actionPex(self, params):
back = {
"peers": packed_peers["ipv4"],
"peers_ipv6": packed_peers["ipv6"],
"peers_onion": packed_peers["onion"]
"peers_onion": packed_peers["onion"],
"peers_i2p": packed_peers["i2p"]
}

self.response(back)
Expand Down Expand Up @@ -410,7 +418,7 @@ def actionFindHashIds(self, params):
"Found: %s for %s hashids in %.3fs" %
({key: len(val) for key, val in back.items()}, len(params["hash_ids"]), time.time() - s)
)
self.response({"peers": back["ipv4"], "peers_onion": back["onion"], "peers_ipv6": back["ipv6"], "my": my_hashes})
self.response({"peers": back["ipv4"], "peers_onion": back["onion"], "peers_i2p": back["i2p"], "peers_ipv6": back["ipv6"], "my": my_hashes})

def actionSetHashfield(self, params):
site = self.sites.get(params["site"])
Expand Down
1 change: 1 addition & 0 deletions src/File/FileServer.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ def checkSites(self, check_files=False, force_port_check=False):

if not self.port_opened["ipv4"]:
self.tor_manager.startOnions()
self.tor_manager.startDests()

if not sites_checking:
check_pool = gevent.pool.Pool(5)
Expand Down
Loading

0 comments on commit 20604ce

Please sign in to comment.