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

[WIP] Add I2P support #602

Open
wants to merge 10 commits into
base: master
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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ ENV HOME /root
RUN \
apt-get update -y; \
apt-get -y install msgpack-python python-gevent python-pip python-dev; \
pip install msgpack-python --upgrade; \
pip install -r requirements.txt --upgrade; \
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

Expand Down
8 changes: 5 additions & 3 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 @@ -109,14 +111,14 @@ It downloads the latest version of ZeroNet then starts it automatically.

* `virtualenv env`
* `source env/bin/activate`
* `pip install msgpack-python gevent`
* `pip install -r requirements.txt`
* `python zeronet.py`
* Open http://127.0.0.1:43110/ in your browser

## Current limitations

* No torrent-like file splitting for big file support
* ~~No more anonymous than Bittorrent~~ (built-in full Tor support added)
* ~~No more anonymous than Bittorrent~~ (built-in full Tor and I2P support added)
* File transactions are not compressed ~~or encrypted yet~~ (TLS encryption added)
* No private sites

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-python --upgrade"
inline: "sudo pip install -r requirements.txt --upgrade"

end
33 changes: 31 additions & 2 deletions plugins/AnnounceZero/AnnounceZeroPlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,18 @@ def processPeerRes(site, peers):
peer_onion, peer_port = helper.unpackOnionAddress(packed_address)
if site.addPeer(peer_onion, peer_port):
added += 1
# I2P Destinations
found_dest = 0
for packed_address in peers["i2p"]:
found_dest += 1
peer_dest, peer_port = helper.unpackI2PAddress(packed_address)
if site.addPeer(peer_dest, peer_port):
added += 1

if added:
site.worker_manager.onPeers()
site.updateWebsocket(peers_added=added)
site.log.debug("Found %s ip4, %s onion peers, new: %s" % (found_ip4, found_onion, added))
site.log.debug("Found %s ip4, %s onion, %s I2P peers, new: %s" % (found_ip4, found_onion, found_dest, added))


@PluginManager.registerTo("Site")
Expand All @@ -48,6 +55,8 @@ def announceTracker(self, tracker_protocol, tracker_address, fileserver_port=0,
need_types = ["ip4"]
if self.connection_server and self.connection_server.tor_manager and self.connection_server.tor_manager.enabled:
need_types.append("onion")
if self.connection_server and self.connection_server.i2p_manager and self.connection_server.i2p_manager.enabled:
need_types.append("i2p")

if mode == "start" or mode == "more": # Single: Announce only this site
sites = [self]
Expand All @@ -62,12 +71,15 @@ def announceTracker(self, tracker_protocol, tracker_address, fileserver_port=0,

# Create request
request = {
"hashes": [], "onions": [], "port": fileserver_port, "need_types": need_types, "need_num": 20, "add": add_types
"hashes": [], "onions": [], "i2pdests": [], "port": fileserver_port, "need_types": need_types, "need_num": 20, "add": add_types
}
for site in sites:
if "onion" in add_types:
onion = self.connection_server.tor_manager.getOnion(site.address)
request["onions"].append(onion)
if "i2p" in add_types:
dest = self.connection_server.i2p_manager.getDest(site.address)
request["i2pdests"].append(dest.base64())
request["hashes"].append(hashlib.sha256(site.address).digest())

# Tracker can remove sites that we don't announce
Expand Down Expand Up @@ -112,6 +124,23 @@ def announceTracker(self, tracker_protocol, tracker_address, fileserver_port=0,
time_full_announced[tracker_address] = 0
return False

# Check if we need to sign prove the I2P Destinations
if "i2p_sign_this" in res:
self.log.debug("Signing %s for %s to add %s I2P dests" % (res["i2p_sign_this"], tracker_address, len(sites)))
request["i2p_signs"] = {}
request["i2p_sign_this"] = res["i2p_sign_this"]
request["need_num"] = 0
for site in sites:
dest = self.connection_server.i2p_manager.getPrivateDest(site.address)
sign = dest.sign(res["i2p_sign_this"])
request["i2p_signs"][dest.base64()] = sign
res = tracker.request("announce", request)
if not res or "i2p_sign_this" in res:
self.log.debug("Announce I2P Destination to %s failed: %s" % (tracker_address, res))
if full_announce:
time_full_announced[tracker_address] = 0
return False

if full_announce:
tracker.remove() # Close connection, we don't need it in next 5 minute

Expand Down
6 changes: 5 additions & 1 deletion plugins/Sidebar/SidebarPlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,26 +62,30 @@ def sidebarRenderPeerStats(self, body, site):
connected = len([peer for peer in site.peers.values() if peer.connection and peer.connection.connected])
connectable = len([peer_id for peer_id in site.peers.keys() if not peer_id.endswith(":0")])
onion = len([peer_id for peer_id in site.peers.keys() if ".onion" in peer_id])
i2p = len([peer_id for peer_id in site.peers.keys() if ".i2p" in peer_id])
peers_total = len(site.peers)
if peers_total:
percent_connected = float(connected) / peers_total
percent_connectable = float(connectable) / peers_total
percent_onion = float(onion) / peers_total
percent_i2p = float(i2p) / peers_total
else:
percent_connectable = percent_connected = percent_onion = 0
percent_connectable = percent_connected = percent_onion = percent_i2p = 0
body.append("""
<li>
<label>Peers</label>
<ul class='graph'>
<li style='width: 100%' class='total back-black' title="Total peers"></li>
<li style='width: {percent_connectable:.0%}' class='connectable back-blue' title='Connectable peers'></li>
<li style='width: {percent_onion:.0%}' class='connected back-purple' title='Onion'></li>
<li style='width: {percent_i2p:.0%}' class='connected back-purple' title='I2P'></li>
<li style='width: {percent_connected:.0%}' class='connected back-green' title='Connected peers'></li>
</ul>
<ul class='graph-legend'>
<li class='color-green'><span>connected:</span><b>{connected}</b></li>
<li class='color-blue'><span>Connectable:</span><b>{connectable}</b></li>
<li class='color-purple'><span>Onion:</span><b>{onion}</b></li>
<li class='color-purple'><span>I2P:</span><b>{i2p}</b></li>
<li class='color-black'><span>Total:</span><b>{peers_total}</b></li>
</ul>
</li>
Expand Down
5 changes: 5 additions & 0 deletions plugins/Stats/StatsPlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ def actionStats(self):
for site_address, onion in main.file_server.tor_manager.site_onions.items():
yield "- %-34s: %s<br>" % (site_address, onion)

# I2P Destinations
yield "<br><br><b>I2P Destinations (status: %s):</b><br>" % main.file_server.i2p_manager.status
for site_address, dest in main.file_server.i2p_manager.site_dests.items():
yield "- %-34s: %s<br>" % (site_address, dest.base32())

# Db
yield "<br><br><b>Db</b>:<br>"
for db in sys.modules["Db.Db"].opened_dbs:
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
gevent>=1.1.0
i2p.socket>=0.3.1
msgpack-python>=0.4.4
6 changes: 6 additions & 0 deletions src/Config.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ def createArguments(self):
"udp://tracker.coppersurfer.tk:6969",
"udp://tracker.leechers-paradise.org:6969",
"udp://9.rarbg.com:2710",
"http://w7tpbzncbcocrqtwwm3nezhnnsw4ozadvi2hmvzdhrqzfxfum7wa.b32.i2p/a", # opentracker.dg2.i2p
"http://vmow3h54yljn7zvzbqepdddt5fmygijujycod2q6yznpy2rrzuwa.b32.i2p/announce", # opentracker.psi.i2p
"http://avviiexdngd32ccoy4kuckvc3mkf53ycvzbz6vz75vzhv4tbpk5a.b32.i2p/tracker/a", # psi.i2p/tracker
"http://tracker.aletorrenty.pl:2710/announce",
"http://explodie.org:6969/announce",
"http://tracker1.wasabii.com.tw:6969/announce"
Expand Down Expand Up @@ -171,6 +174,9 @@ def createArguments(self):
self.parser.add_argument('--tor_controller', help='Tor controller address', metavar='ip:port', default='127.0.0.1:9051')
self.parser.add_argument('--tor_proxy', help='Tor proxy address', metavar='ip:port', default='127.0.0.1:9050')

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))

return self.parser
Expand Down
52 changes: 37 additions & 15 deletions src/Connection/Connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ def connect(self):
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")
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:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((self.ip, int(self.port)))
Expand Down Expand Up @@ -164,24 +168,32 @@ def messageLoop(self):

# My handshake info
def getHandshakeInfo(self):
# No TLS for onion connections
if self.ip.endswith(".onion"):
# No TLS for onion or I2P connections
if self.ip.endswith(".onion") or self.ip.endswith(".i2p"):
crypt_supported = []
else:
crypt_supported = CryptConnection.manager.crypt_supported
# No peer id for onion connections
if self.ip.endswith(".onion") or self.ip == "127.0.0.1":
# No peer id for onion or I2P connections
if self.ip.endswith(".onion") or self.ip.endswith(".i2p") or self.ip == "127.0.0.1":
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"):
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
self.site_lock = onion_sites.get(target_onion)
if not self.site_lock:
self.server.log.error("Unknown target onion address: %s" % target_onion)
self.site_lock = "unknown"
# Setup peer lock from requested onion address or I2P Destination
if self.handshake:
if self.handshake.get("target_ip", "").endswith(".onion"):
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
self.site_lock = onion_sites.get(target_onion)
if not self.site_lock:
self.server.log.error("Unknown target onion address: %s" % target_onion)
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 = {
"version": config.version,
Expand All @@ -195,15 +207,21 @@ def getHandshakeInfo(self):
"crypt": self.crypt
}
if self.site_lock:
handshake["onion"] = self.server.tor_manager.getOnion(self.site_lock)
if self.ip.endswith(".onion"):
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"):
handshake["onion"] = self.server.tor_manager.getOnion("global")
elif self.ip.endswith(".i2p"):
handshake["i2p"] = self.server.i2p_manager.getDest("global").base64()

return handshake

def setHandshake(self, 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
else:
self.port = handshake["fileserver_port"] # Set peer fileserver port
Expand All @@ -212,9 +230,13 @@ def setHandshake(self, handshake):
self.ip = handshake["onion"] + ".onion"
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
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
elif handshake.get("crypt"): # Recommended crypt by server
crypt = handshake["crypt"]
Expand Down
15 changes: 12 additions & 3 deletions src/Connection/ConnectionServer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from Config import config
from Crypt import CryptConnection
from Crypt import CryptHash
from I2P import I2PManager
from Tor import TorManager


Expand All @@ -28,6 +29,11 @@ def __init__(self, ip=None, port=None, request_handler=None):
else:
self.tor_manager = None

if config.i2p != "disabled":
self.i2p_manager = I2PManager(self.handleIncomingConnection)
else:
self.i2p_manager = None

self.connections = [] # Connections
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
Expand Down Expand Up @@ -96,7 +102,8 @@ def handleIncomingConnection(self, sock, addr):
connection.handleIncomingConnection(sock)

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
else:
key = ip
Expand All @@ -116,7 +123,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 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
continue
if not connection.connected and create:
Expand All @@ -130,7 +138,8 @@ def getConnection(self, ip=None, port=None, peer_id=None, create=True, site=None
if port == 0:
raise Exception("This peer is not connectable")
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)
else:
connection = Connection(self, ip, port)
Expand Down
Loading