Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add I2P Support #225

Open
wants to merge 2 commits into
base: py3-latest
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:https://t.publictracker.xyz:6969/announce",
"https://tracker.lilithraws.cf:443/announce",
"https://tracker.babico.name.tr:443/announce",
"http:https://opentracker.dg2.i2p/announce",
"http:https://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.i2p_manager.startDests()

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