Version 0.3.5, Rev830, Full Tor mode support with hidden services, Onion stats in Sidebar, GeoDB download fix using Tor, Gray out disabled sites in Stats page, Tor hidden service status in stat page, Benchmark sha256, Skyts tracker out expodie in, 2 new tracker using ZeroNet protocol, Keep SSL cert option between restarts, SSL Certificate pinning support for connections, Site lock support for connections, Certificate pinned connections using implicit SSL, Flood protection whitelist support, Foreign keys support for DB layer, Not support for SQL query helper, 0 length file get bugfix, Pex onion address support, Faster port testing, Faster uPnP port opening, Need connections more often on owned sites, Delay ZeroHello startup message if port check or Tor manager not ready yet, Use lockfiles to avoid double start, Save original socket on proxy monkey patching to get ability to connect localhost directly, Handle atomic write errors, Broken gevent https workaround helper, Rsa crypt functions, Plugin to Bootstrap using ZeroNet protocol

This commit is contained in:
HelloZeroNet 2016-01-05 00:20:52 +01:00
parent c9578e9037
commit e9d2cdfd37
99 changed files with 9476 additions and 267 deletions

View file

@ -1,5 +1,6 @@
import socket
import time
import hashlib
import gevent
import msgpack
@ -8,20 +9,25 @@ from Config import config
from Debug import Debug
from util import StreamingMsgpack
from Crypt import CryptConnection
from Site import SiteManager
class Connection(object):
__slots__ = (
"sock", "sock_wrapped", "ip", "port", "id", "protocol", "type", "server", "unpacker", "req_id",
"sock", "sock_wrapped", "ip", "port", "cert_pin", "site_lock", "id", "protocol", "type", "server", "unpacker", "req_id",
"handshake", "crypt", "connected", "event_connected", "closed", "start_time", "last_recv_time",
"last_message_time", "last_send_time", "last_sent_time", "incomplete_buff_recv", "bytes_recv", "bytes_sent",
"last_ping_delay", "last_req_time", "last_cmd", "name", "updateName", "waiting_requests", "waiting_streams"
)
def __init__(self, server, ip, port, sock=None):
def __init__(self, server, ip, port, sock=None, site_lock=None):
self.sock = sock
self.ip = ip
self.port = port
self.cert_pin = None
if "#" in ip:
self.ip, self.cert_pin = ip.split("#")
self.site_lock = site_lock # Only this site requests allowed (for Tor)
self.id = server.last_connection_id
server.last_connection_id += 1
self.protocol = "?"
@ -73,17 +79,23 @@ class Connection(object):
def connect(self):
self.log("Connecting...")
self.type = "out"
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((self.ip, int(self.port)))
if self.ip.endswith(".onion"):
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)
else:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((self.ip, int(self.port)))
# Implicit SSL in the future
# self.sock = CryptConnection.manager.wrapSocket(self.sock, "tls-rsa")
# self.sock.do_handshake()
# self.crypt = "tls-rsa"
# self.sock_wrapped = True
# Implicit SSL
if self.cert_pin:
self.sock = CryptConnection.manager.wrapSocket(self.sock, "tls-rsa", cert_pin=self.cert_pin)
self.sock.do_handshake()
self.crypt = "tls-rsa"
self.sock_wrapped = True
# Detect protocol
self.send({"cmd": "handshake", "req_id": 0, "params": self.handshakeInfo()})
self.send({"cmd": "handshake", "req_id": 0, "params": self.getHandshakeInfo()})
event_connected = self.event_connected
gevent.spawn(self.messageLoop)
return event_connected.get() # Wait for handshake
@ -92,14 +104,15 @@ class Connection(object):
def handleIncomingConnection(self, sock):
self.log("Incoming connection...")
self.type = "in"
try:
if sock.recv(1, gevent.socket.MSG_PEEK) == "\x16":
self.log("Crypt in connection using implicit SSL")
self.sock = CryptConnection.manager.wrapSocket(self.sock, "tls-rsa", True)
self.sock_wrapped = True
self.crypt = "tls-rsa"
except Exception, err:
self.log("Socket peek error: %s" % Debug.formatException(err))
if self.ip != "127.0.0.1": # Clearnet: Check implicit SSL
try:
if sock.recv(1, gevent.socket.MSG_PEEK) == "\x16":
self.log("Crypt in connection using implicit SSL")
self.sock = CryptConnection.manager.wrapSocket(self.sock, "tls-rsa", True)
self.sock_wrapped = True
self.crypt = "tls-rsa"
except Exception, err:
self.log("Socket peek error: %s" % Debug.formatException(err))
self.messageLoop()
# Message loop for connection
@ -142,28 +155,60 @@ class Connection(object):
self.close() # MessageLoop ended, close connection
# My handshake info
def handshakeInfo(self):
return {
def getHandshakeInfo(self):
# No TLS for onion connections
if self.ip.endswith(".onion"):
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":
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"
handshake = {
"version": config.version,
"protocol": "v2",
"peer_id": self.server.peer_id,
"peer_id": peer_id,
"fileserver_port": self.server.port,
"port_opened": self.server.port_opened,
"target_ip": self.ip,
"rev": config.rev,
"crypt_supported": CryptConnection.manager.crypt_supported,
"crypt_supported": crypt_supported,
"crypt": self.crypt
}
if self.site_lock:
handshake["onion"] = self.server.tor_manager.getOnion(self.site_lock)
elif self.ip.endswith(".onion"):
handshake["onion"] = self.server.tor_manager.getOnion("global")
return handshake
def setHandshake(self, handshake):
self.handshake = handshake
if handshake.get("port_opened", None) is False: # Not connectable
if handshake.get("port_opened", None) is False and not "onion" in handshake: # Not connectable
self.port = 0
else:
self.port = handshake["fileserver_port"] # Set peer fileserver port
if handshake.get("onion") and not self.ip.endswith(".onion"): # Set incoming connection's onion address
self.ip = handshake["onion"] + ".onion"
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 handshake.get("crypt"): # Recommended crypt by server
if self.ip.endswith(".onion"):
crypt = None
elif handshake.get("crypt"): # Recommended crypt by server
crypt = handshake["crypt"]
else: # Select the best supported on both sides
crypt = CryptConnection.manager.selectCrypt(handshake["crypt_supported"])
@ -193,30 +238,21 @@ class Connection(object):
self.crypt = message["crypt"]
server = (self.type == "in")
self.log("Crypt out connection using: %s (server side: %s)..." % (self.crypt, server))
self.sock = CryptConnection.manager.wrapSocket(self.sock, self.crypt, server)
self.sock = CryptConnection.manager.wrapSocket(self.sock, self.crypt, server, cert_pin=self.cert_pin)
self.sock.do_handshake()
self.sock_wrapped = True
if not self.sock_wrapped and self.cert_pin:
self.log("Crypt connection error: Socket not encrypted, but certificate pin present")
self.close()
return
self.setHandshake(message)
else:
self.log("Unknown response: %s" % message)
elif message.get("cmd"): # Handhsake request
if message["cmd"] == "handshake":
if config.debug_socket:
self.log("Handshake request: %s" % message)
self.setHandshake(message["params"])
data = self.handshakeInfo()
data["cmd"] = "response"
data["to"] = message["req_id"]
self.send(data) # Send response to handshake
# Sent crypt request to client
if self.crypt and not self.sock_wrapped:
server = (self.type == "in")
self.log("Crypt in connection using: %s (server side: %s)..." % (self.crypt, server))
try:
self.sock = CryptConnection.manager.wrapSocket(self.sock, self.crypt, server)
self.sock_wrapped = True
except Exception, err:
self.log("Crypt connection error: %s, adding peerid %s as broken ssl." % (err, message["params"]["peer_id"]))
self.server.broken_ssl_peer_ids[message["params"]["peer_id"]] = True
self.handleHandshake(message)
else:
self.server.handleRequest(self, message)
else: # Old style response, no req_id definied
@ -226,6 +262,30 @@ class Connection(object):
self.waiting_requests[last_req_id].set(message)
del self.waiting_requests[last_req_id] # Remove from waiting request
# Incoming handshake set request
def handleHandshake(self, message):
if config.debug_socket:
self.log("Handshake request: %s" % message)
self.setHandshake(message["params"])
data = self.getHandshakeInfo()
data["cmd"] = "response"
data["to"] = message["req_id"]
self.send(data) # Send response to handshake
# Sent crypt request to client
if self.crypt and not self.sock_wrapped:
server = (self.type == "in")
self.log("Crypt in connection using: %s (server side: %s)..." % (self.crypt, server))
try:
self.sock = CryptConnection.manager.wrapSocket(self.sock, self.crypt, server, cert_pin=self.cert_pin)
self.sock_wrapped = True
except Exception, err:
self.log("Crypt connection error: %s, adding peerid %s as broken ssl." % (err, message["params"]["peer_id"]))
self.server.broken_ssl_peer_ids[message["params"]["peer_id"]] = True
if not self.sock_wrapped and self.cert_pin:
self.log("Crypt connection error: Socket not encrypted, but certificate pin present")
self.close()
# Stream socket directly to a file
def handleStream(self, message):
if config.debug_socket:

View file

@ -1,6 +1,4 @@
import logging
import random
import string
import time
import sys
@ -14,6 +12,7 @@ from Connection import Connection
from Config import config
from Crypt import CryptConnection
from Crypt import CryptHash
from Tor import TorManager
class ConnectionServer:
@ -24,7 +23,13 @@ class ConnectionServer:
self.log = logging.getLogger("ConnServer")
self.port_opened = None
if config.tor != "disabled":
self.tor_manager = TorManager(self.ip, self.port)
else:
self.tor_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
self.broken_ssl_peer_ids = {} # Peerids of broken ssl connections
self.ips = {} # Connection by ip
@ -41,7 +46,7 @@ class ConnectionServer:
# Check msgpack version
if msgpack.version[0] == 0 and msgpack.version[1] < 4:
self.log.error(
"Error: Unsupported msgpack version: %s (<0.4.0), please run `sudo pip install msgpack-python --upgrade`" %
"Error: Unsupported msgpack version: %s (<0.4.0), please run `sudo apt-get install python-pip; sudo pip install msgpack-python --upgrade`" %
str(msgpack.version)
)
sys.exit(0)
@ -74,7 +79,7 @@ class ConnectionServer:
ip, port = addr
# Connection flood protection
if ip in self.ip_incoming:
if ip in self.ip_incoming and ip not in self.whitelist:
self.ip_incoming[ip] += 1
if self.ip_incoming[ip] > 3: # Allow 3 in 1 minute from same ip
self.log.debug("Connection flood detected from %s" % ip)
@ -89,10 +94,15 @@ class ConnectionServer:
self.ips[ip] = connection
connection.handleIncomingConnection(sock)
def getConnection(self, ip=None, port=None, peer_id=None, create=True):
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
key = ip + site.address
else:
key = ip
# Find connection by ip
if ip in self.ips:
connection = self.ips[ip]
if key in self.ips:
connection = self.ips[key]
if not peer_id or connection.handshake.get("peer_id") == peer_id: # Filter by peer_id
if not connection.connected and create:
succ = connection.event_connected.get() # Wait for connection
@ -105,6 +115,9 @@ class ConnectionServer:
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:
# For different site
continue
if not connection.connected and create:
succ = connection.event_connected.get() # Wait for connection
if not succ:
@ -116,8 +129,11 @@ class ConnectionServer:
if port == 0:
raise Exception("This peer is not connectable")
try:
connection = Connection(self, ip, port)
self.ips[ip] = connection
if ip.endswith(".onion") and self.tor_manager.start_onions and site: # Lock connection to site
connection = Connection(self, ip, port, site_lock=site.address)
else:
connection = Connection(self, ip, port)
self.ips[key] = connection
self.connections.append(connection)
succ = connection.connect()
if not succ:
@ -134,14 +150,22 @@ class ConnectionServer:
def removeConnection(self, connection):
self.log.debug("Removing %s..." % connection)
if self.ips.get(connection.ip) == connection: # Delete if same as in registry
# Delete if same as in registry
if self.ips.get(connection.ip) == connection:
del self.ips[connection.ip]
# Site locked connection
if connection.site_lock and self.ips.get(connection.ip + connection.site_lock) == connection:
del self.ips[connection.ip + connection.site_lock]
# Cert pinned connection
if connection.cert_pin and self.ips.get(connection.ip + "#" + connection.cert_pin) == connection:
del self.ips[connection.ip + "#" + connection.cert_pin]
if connection in self.connections:
self.connections.remove(connection)
def checkConnections(self):
while self.running:
time.sleep(60) # Sleep 1 min
time.sleep(60) # Check every minute
self.ip_incoming = {} # Reset connected ips counter
self.broken_ssl_peer_ids = {} # Reset broken ssl peerids count
for connection in self.connections[:]: # Make a copy
@ -151,7 +175,10 @@ class ConnectionServer:
# Delete the unpacker if not needed
del connection.unpacker
connection.unpacker = None
connection.log("Unpacker deleted")
elif connection.last_cmd == "announce" and idle > 20: # Bootstrapper connection close after 20 sec
connection.log("[Cleanup] Tracker connection: %s" % idle)
connection.close()
if idle > 60 * 60:
# Wake up after 1h