Skip to content

Commit d731fe9

Browse files
committed
Add I2P Support
Merge HelloZeroNet#602 Signed-off-by: Marek Küthe <m.k@mk16.de>
1 parent 2900259 commit d731fe9

17 files changed

Lines changed: 447 additions & 27 deletions

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ Decentralized websites using Bitcoin crypto and the BitTorrent network - https:/
2222
* Password-less [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
2323
based authorization: Your account is protected by the same cryptography as your Bitcoin wallet
2424
* Built-in SQL server with P2P data synchronization: Allows easier site development and faster page load times
25-
* Anonymity: Full Tor network support with .onion hidden services instead of IPv4 addresses
25+
* Anonymity:
26+
* Full Tor network support with .onion hidden services instead of IPv4 addresses
27+
* Full I2P network support with I2P Destinations instead of IPv4 addresses
2628
* TLS encrypted connections
2729
* Automatic uPnP port opening
2830
* Plugin for multiuser (openproxy) support
@@ -132,7 +134,7 @@ https://zeronet.ipfsscan.io/
132134

133135
* File transactions are not compressed
134136
* No private sites
135-
137+
* ~~No more anonymous than Bittorrent~~ (built-in full Tor and I2P support added)
136138

137139
## How can I create a ZeroNet site?
138140

Vagrantfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,6 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
4040
config.vm.provision "shell",
4141
inline: "sudo apt-get install msgpack-python python-gevent python-pip python-dev -y"
4242
config.vm.provision "shell",
43-
inline: "sudo pip install msgpack --upgrade"
43+
inline: "sudo pip install -r requirements.txt --upgrade"
4444

4545
end

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ websocket_client
1111
gevent-ws
1212
coincurve
1313
maxminddb
14+
i2p.socket

src/Config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ def createArguments(self):
8888
"http://t.publictracker.xyz:6969/announce",
8989
"https://tracker.lilithraws.cf:443/announce",
9090
"https://tracker.babico.name.tr:443/announce",
91+
"http://opentracker.dg2.i2p/announce",
92+
"http://opentracker.skank.i2p/announce"
9193
]
9294
# Platform specific
9395
if sys.platform.startswith("win"):
@@ -311,6 +313,9 @@ def createArguments(self):
311313
self.parser.add_argument('--tor_use_bridges', help='Use obfuscated bridge relays to avoid Tor block', action='store_true')
312314
self.parser.add_argument('--tor_hs_limit', help='Maximum number of hidden services in Tor always mode', metavar='limit', type=int, default=10)
313315
self.parser.add_argument('--tor_hs_port', help='Hidden service port in Tor always mode', metavar='limit', type=int, default=15441)
316+
317+
self.parser.add_argument('--i2p', help='enable: Use only for I2P peers, always: Use I2P for every connection', choices=["disable", "enable", "always"], default='enable')
318+
self.parser.add_argument('--i2p_sam', help='I2P SAM API address', metavar='ip:port', default='127.0.0.1:7656')
314319

315320
self.parser.add_argument('--version', action='version', version='ZeroNet %s r%s' % (self.version, self.rev))
316321
self.parser.add_argument('--end', help='Stop multi value argument parsing', action='store_true')

src/Connection/Connection.py

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ def connect(self):
133133
self.sock = socks.socksocket()
134134
proxy_ip, proxy_port = config.trackers_proxy.split(":")
135135
self.sock.set_proxy(socks.PROXY_TYPE_SOCKS5, proxy_ip, int(proxy_port))
136+
elif self.ip.endswith(".i2p"):
137+
if not self.server.i2p_manager or not self.server.i2p_manager.enabled:
138+
raise Exception("Can't connect to I2P addresses, no SAM API present")
139+
self.sock = self.server.i2p_manager.createSocket(self.ip, self.port)
136140
else:
137141
self.sock = self.createSocket()
138142

@@ -344,22 +348,27 @@ def handleStream(self, message, buff):
344348
# My handshake info
345349
def getHandshakeInfo(self):
346350
# No TLS for onion connections
347-
if self.ip_type == "onion":
351+
if self.ip_type == "onion" or self.ip_type == "i2p":
348352
crypt_supported = []
349353
elif self.ip in self.server.broken_ssl_ips:
350354
crypt_supported = []
351355
else:
352356
crypt_supported = CryptConnection.manager.crypt_supported
353357
# No peer id for onion connections
354-
if self.ip_type == "onion" or self.ip in config.ip_local:
358+
if self.ip_type == "onion" or self.ip_type == "i2p" or self.ip in config.ip_local:
355359
peer_id = ""
356360
else:
357361
peer_id = self.server.peer_id
358362
# Setup peer lock from requested onion address
359-
if self.handshake and self.handshake.get("target_ip", "").endswith(".onion") and self.server.tor_manager.start_onions:
360-
self.target_onion = self.handshake.get("target_ip").replace(".onion", "") # My onion address
361-
if not self.server.tor_manager.site_onions.values():
362-
self.server.log.warning("Unknown target onion address: %s" % self.target_onion)
363+
if self.handshake:
364+
if self.handshake.get("target_ip", "").endswith(".onion") and self.server.tor_manager.start_onions:
365+
self.target_onion = self.handshake.get("target_ip").replace(".onion", "") # My onion address
366+
if not self.server.tor_manager.site_onions.values():
367+
self.server.log.warning("Unknown target onion address: %s" % self.target_onion)
368+
elif self.handshake.get("target_ip", "").endswith(".i2p") and self.server.i2p_manager.start_dests:
369+
self.target_dest = self.handshake.get("target_ip").replace(".i2p", "") # My I2P Destination
370+
if not dest_sites.get(target_dest):
371+
self.server.log.error("Unknown target I2P Destination: %s" % target_dest)
363372

364373
handshake = {
365374
"version": config.version,
@@ -378,6 +387,10 @@ def getHandshakeInfo(self):
378387
handshake["onion"] = self.target_onion
379388
elif self.ip_type == "onion":
380389
handshake["onion"] = self.server.tor_manager.getOnion("global")
390+
elif self.target_dest:
391+
handshake["i2p"] = self.target_dest
392+
elif self.ip_type == "i2p":
393+
handshake["i2p"] = self.server.i2p_manager.getDest("global").base64()
381394

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

399412
self.handshake = handshake
400-
if handshake.get("port_opened", None) is False and "onion" not in handshake and not self.is_private_ip: # Not connectable
413+
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
401414
self.port = 0
402415
else:
403416
self.port = int(handshake["fileserver_port"]) # Set peer fileserver port
@@ -416,7 +429,7 @@ def setHandshake(self, handshake):
416429
if type(handshake["crypt_supported"][0]) is bytes:
417430
handshake["crypt_supported"] = [item.decode() for item in handshake["crypt_supported"]] # Backward compatibility
418431

419-
if self.ip_type == "onion" or self.ip in config.ip_local:
432+
if self.ip_type == "onion" or self.ip_type == "i2p" or self.ip in config.ip_local:
420433
crypt = None
421434
elif handshake.get("crypt"): # Recommended crypt by server
422435
crypt = handshake["crypt"]
@@ -426,13 +439,21 @@ def setHandshake(self, handshake):
426439
if crypt:
427440
self.crypt = crypt
428441

429-
if self.type == "in" and handshake.get("onion") and not self.ip_type == "onion": # Set incoming connection's onion address
430-
if self.server.ips.get(self.ip) == self:
431-
del self.server.ips[self.ip]
432-
self.setIp(handshake["onion"] + ".onion")
433-
self.log("Changing ip to %s" % self.ip)
434-
self.server.ips[self.ip] = self
435-
self.updateName()
442+
if self.type == "in":
443+
if handshake.get("onion") and not self.ip_type == "onion": # Set incoming connection's onion address
444+
if self.server.ips.get(self.ip) == self:
445+
del self.server.ips[self.ip]
446+
self.setIp(handshake["onion"] + ".onion")
447+
self.log("Changing ip to %s" % self.ip)
448+
self.server.ips[self.ip] = self
449+
self.updateName()
450+
if handshake.get("i2p") and not self.ip_type == "i2p": # Set incoming connection's I2P Destination
451+
if self.server.ips.get(self.ip) == self:
452+
del self.server.ips[self.ip]
453+
self.setIp(handshake["i2p"] + ".i2p")
454+
self.log("Changing ip to %s" % self.ip)
455+
self.server.ips[self.ip] = self
456+
self.updateName()
436457

437458
self.event_connected.set(True) # Mark handshake as done
438459
self.event_connected = None

src/Connection/ConnectionServer.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from Crypt import CryptConnection
1818
from Crypt import CryptHash
1919
from Tor import TorManager
20+
from I2P import I2PManager
2021
from Site import SiteManager
2122

2223

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

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

172177
def getConnection(self, ip=None, port=None, peer_id=None, create=True, site=None, is_tracker_connection=False):
173178
ip_type = helper.getIpType(ip)
174-
has_per_site_onion = (ip.endswith(".onion") or self.port_opened.get(ip_type, None) == False) and self.tor_manager.start_onions and site
175-
if has_per_site_onion: # Site-unique connection for Tor
179+
has_per_site_onion = (((ip.endswith(".onion") or self.port_opened.get("onion", None) == False) and self.tor_manager.start_onions) or \
180+
((ip.endswith(".i2p") or self.port_opened.get("i2p", None) == False) and self.i2p_manager.start_dests)) and site
181+
if has_per_site_onion: # Site-unique connection for Tor or I2P
176182
if ip.endswith(".onion"):
177183
site_onion = self.tor_manager.getOnion(site.address)
184+
elif ip.endswith(".i2p"):
185+
site_onion = self.i2p_manager.getDest(site.address)
178186
else:
179187
site_onion = self.tor_manager.getOnion("global")
180188
key = ip + site_onion
@@ -196,7 +204,8 @@ def getConnection(self, ip=None, port=None, peer_id=None, create=True, site=None
196204
if connection.ip == ip:
197205
if peer_id and connection.handshake.get("peer_id") != peer_id: # Does not match
198206
continue
199-
if ip.endswith(".onion") and self.tor_manager.start_onions and ip.replace(".onion", "") != connection.target_onion:
207+
if (ip.endswith(".onion") and self.tor_manager.start_onions and ip.replace(".onion", "") != connection.target_onion) or \
208+
(ip.endswith(".i2p") and self.i2p_manager.start_dests and ip.replace(".i2p", "") != connection.target_dest):
200209
# For different site
201210
continue
202211
if not connection.connected and create:

src/File/FileRequest.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,13 @@ def actionPex(self, params):
321321
if site.addPeer(*address, source="pex"):
322322
added += 1
323323

324+
# Add sent i2p peers to site
325+
for packed_address in params.get("peers_i2p", []):
326+
address = helper.unpackI2PAddress(packed_address)
327+
got_peer_keys.append("%s:%s" % address)
328+
if site.addPeer(*address):
329+
added += 1
330+
324331
# Send back peers that is not in the sent list and connectable (not port 0)
325332
packed_peers = helper.packPeers(site.getConnectablePeers(params["need"], ignore=got_peer_keys, allow_private=False))
326333

@@ -335,7 +342,8 @@ def actionPex(self, params):
335342
back = {
336343
"peers": packed_peers["ipv4"],
337344
"peers_ipv6": packed_peers["ipv6"],
338-
"peers_onion": packed_peers["onion"]
345+
"peers_onion": packed_peers["onion"],
346+
"peers_i2p": packed_peers["i2p"]
339347
}
340348

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

415423
def actionSetHashfield(self, params):
416424
site = self.sites.get(params["site"])

src/File/FileServer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ def checkSites(self, check_files=False, force_port_check=False):
252252

253253
if not self.port_opened["ipv4"]:
254254
self.tor_manager.startOnions()
255+
self.i2p_manager.startDests()
255256

256257
if not sites_checking:
257258
check_pool = gevent.pool.Pool(5)

0 commit comments

Comments
 (0)