Add Tor hidden service support

- PyBitmessage can now run as a hidden service on Tor
- three new variables in keys.dat: onionhostname, onionport, onionbindip
- you need to manually add a hidden service to tor
This commit is contained in:
Peter Šurda 2016-06-07 21:59:48 +02:00
parent 33991f4598
commit 1a40c29d22
Signed by untrusted user: PeterSurda
GPG Key ID: 0C5F50C0B5F37D87
6 changed files with 53 additions and 16 deletions

View File

@ -50,7 +50,10 @@ class outgoingSynSender(threading.Thread, StoppableThread):
if (random.random() <= priority): if (random.random() <= priority):
break break
time.sleep(0.01) # prevent CPU hogging if something is broken time.sleep(0.01) # prevent CPU hogging if something is broken
return peer try:
return peer
except NameError:
return shared.Peer('127.0.0.1', 8444)
def stopThread(self): def stopThread(self):
super(outgoingSynSender, self).stopThread() super(outgoingSynSender, self).stopThread()

View File

@ -601,10 +601,19 @@ class receiveDataThread(threading.Thread):
# the right child stream. # the right child stream.
with shared.knownNodesLock: with shared.knownNodesLock:
if len(shared.knownNodes[self.streamNumber]) > 0: if len(shared.knownNodes[self.streamNumber]) > 0:
ownPosition = random.randint(0, 499)
sentOwn = False
for i in range(500): for i in range(500):
peer, = random.sample(shared.knownNodes[self.streamNumber], 1) # if current connection is over a proxy, sent our own onion address at a random position
if ownPosition == i and ".onion" in shared.config.get("bitmessagesettings", "onionhostname") and self.sock.getproxytype() != 0 and not sentOwn:
peer = shared.Peer(shared.config.get("bitmessagesettings", "onionhostname"), shared.config.getint("bitmessagesettings", "onionport"))
else:
# still may contain own onion address, but we don't change it
peer, = random.sample(shared.knownNodes[self.streamNumber], 1)
if isHostInPrivateIPRange(peer.host): if isHostInPrivateIPRange(peer.host):
continue continue
if shared.config.get("bitmessagesettings", "onionhostname") == peer.host:
sentOwn = True
addrsInMyStream[peer] = shared.knownNodes[ addrsInMyStream[peer] = shared.knownNodes[
self.streamNumber][peer] self.streamNumber][peer]
if len(shared.knownNodes[self.streamNumber * 2]) > 0: if len(shared.knownNodes[self.streamNumber * 2]) > 0:

View File

@ -27,6 +27,9 @@ class singleListener(threading.Thread, StoppableThread):
def _createListenSocket(self, family): def _createListenSocket(self, family):
HOST = '' # Symbolic name meaning all available interfaces HOST = '' # Symbolic name meaning all available interfaces
# If not sockslisten, but onionhostname defined, only listen on localhost
if not shared.safeConfigGetBoolean('bitmessagesettings', 'sockslisten') and ".onion" in shared.config.get('bitmessagesettings', 'onionhostname'):
HOST = shared.config.get('bitmessagesettings', 'onionbindip')
PORT = shared.config.getint('bitmessagesettings', 'port') PORT = shared.config.getint('bitmessagesettings', 'port')
sock = socket.socket(family, socket.SOCK_STREAM) sock = socket.socket(family, socket.SOCK_STREAM)
if family == socket.AF_INET6: if family == socket.AF_INET6:
@ -43,12 +46,14 @@ class singleListener(threading.Thread, StoppableThread):
def stopThread(self): def stopThread(self):
super(singleListener, self).stopThread() super(singleListener, self).stopThread()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try: for ip in ('127.0.0.1', shared.config.get('bitmessagesettings', 'onionbindip')):
s.connect(('127.0.0.1', shared.config.getint('bitmessagesettings', 'port'))) try:
s.shutdown(socket.SHUT_RDWR) s.connect((ip, shared.config.getint('bitmessagesettings', 'port')))
s.close() s.shutdown(socket.SHUT_RDWR)
except: s.close()
pass break
except:
pass
def run(self): def run(self):
# If there is a trusted peer then we don't want to accept # If there is a trusted peer then we don't want to accept
@ -62,8 +67,12 @@ class singleListener(threading.Thread, StoppableThread):
# We typically don't want to accept incoming connections if the user is using a # We typically don't want to accept incoming connections if the user is using a
# SOCKS proxy, unless they have configured otherwise. If they eventually select # SOCKS proxy, unless they have configured otherwise. If they eventually select
# proxy 'none' or configure SOCKS listening then this will start listening for # proxy 'none' or configure SOCKS listening then this will start listening for
# connections. # connections. But if on SOCKS and have an onionhostname, listen
while shared.config.get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and not shared.config.getboolean('bitmessagesettings', 'sockslisten') and shared.shutdown == 0: # (socket is then only opened for localhost)
while shared.config.get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and \
(not shared.config.getboolean('bitmessagesettings', 'sockslisten') and \
".onion" not in shared.config.get('bitmessagesettings', 'onionhostname')) and \
shared.shutdown == 0:
self.stop.wait(5) self.stop.wait(5)
logger.info('Listening for incoming connections.') logger.info('Listening for incoming connections.')
@ -77,6 +86,7 @@ class singleListener(threading.Thread, StoppableThread):
if (isinstance(e.args, tuple) and if (isinstance(e.args, tuple) and
e.args[0] in (errno.EAFNOSUPPORT, e.args[0] in (errno.EAFNOSUPPORT,
errno.EPFNOSUPPORT, errno.EPFNOSUPPORT,
errno.EADDRNOTAVAIL,
errno.ENOPROTOOPT)): errno.ENOPROTOOPT)):
sock = self._createListenSocket(socket.AF_INET) sock = self._createListenSocket(socket.AF_INET)
else: else:
@ -90,7 +100,7 @@ class singleListener(threading.Thread, StoppableThread):
# SOCKS proxy, unless they have configured otherwise. If they eventually select # SOCKS proxy, unless they have configured otherwise. If they eventually select
# proxy 'none' or configure SOCKS listening then this will start listening for # proxy 'none' or configure SOCKS listening then this will start listening for
# connections. # connections.
while shared.config.get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and not shared.config.getboolean('bitmessagesettings', 'sockslisten') and shared.shutdown == 0: while shared.config.get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and not shared.config.getboolean('bitmessagesettings', 'sockslisten') and ".onion" not in shared.config.get('bitmessagesettings', 'onionhostname') and shared.shutdown == 0:
self.stop.wait(10) self.stop.wait(10)
while len(shared.connectedHostsList) > 220 and shared.shutdown == 0: while len(shared.connectedHostsList) > 220 and shared.shutdown == 0:
logger.info('We are connected to too many people. Not accepting further incoming connections for ten seconds.') logger.info('We are connected to too many people. Not accepting further incoming connections for ten seconds.')
@ -112,7 +122,9 @@ class singleListener(threading.Thread, StoppableThread):
# is already connected because the two computers will # is already connected because the two computers will
# share the same external IP. This is here to prevent # share the same external IP. This is here to prevent
# connection flooding. # connection flooding.
if HOST in shared.connectedHostsList: # permit repeated connections from Tor
# FIXME: sockshostname may be a hostname rather than IP, in such a case this will break
if HOST in shared.connectedHostsList and (".onion" not in shared.config.get('bitmessagesettings', 'onionhostname') or HOST != shared.config.get('bitmessagesettings', 'sockshostname')):
socketObject.close() socketObject.close()
logger.info('We are already connected to ' + str(HOST) + '. Ignoring connection.') logger.info('We are already connected to ' + str(HOST) + '. Ignoring connection.')
else: else:

View File

@ -419,6 +419,13 @@ class sqlThread(threading.Thread):
logger.debug('In messages.dat database, done adding address field to the pubkeys table and removing the hash field.') logger.debug('In messages.dat database, done adding address field to the pubkeys table and removing the hash field.')
self.cur.execute('''update settings set value=10 WHERE key='version';''') self.cur.execute('''update settings set value=10 WHERE key='version';''')
if not shared.config.has_option('bitmessagesettings', 'onionhostname'):
shared.config.set('bitmessagesettings', 'onionhostname', '')
if not shared.config.has_option('bitmessagesettings', 'onionport'):
shared.config.set('bitmessagesettings', 'onionport', '8444')
if not shared.config.has_option('bitmessagesettings', 'onionbindip'):
shared.config.set('bitmessagesettings', 'onionbindip', '127.0.0.1')
shared.writeKeysFile()
# Are you hoping to add a new option to the keys.dat file of existing # Are you hoping to add a new option to the keys.dat file of existing
# Bitmessage users or modify the SQLite database? Add it right above this line! # Bitmessage users or modify the SQLite database? Add it right above this line!

View File

@ -21,13 +21,16 @@ def knownNodes():
shared.knownNodes[stream] = {} shared.knownNodes[stream] = {}
for node_tuple in nodes.items(): for node_tuple in nodes.items():
try: try:
host, (port, time) = node_tuple host, (port, lastseen) = node_tuple
peer = shared.Peer(host, port) peer = shared.Peer(host, port)
except: except:
peer, time = node_tuple peer, lastseen = node_tuple
shared.knownNodes[stream][peer] = time shared.knownNodes[stream][peer] = lastseen
except: except:
shared.knownNodes = defaultKnownNodes.createDefaultKnownNodes(shared.appdata) shared.knownNodes = defaultKnownNodes.createDefaultKnownNodes(shared.appdata)
# your own onion address, if setup
if shared.config.has_option('bitmessagesettings', 'onionhostname') and ".onion" in shared.config.get('bitmessagesettings', 'onionhostname'):
shared.knownNodes[1][shared.Peer(shared.config.get('bitmessagesettings', 'onionhostname'), shared.config.getint('bitmessagesettings', 'onionport'))] = int(time.time())
if shared.config.getint('bitmessagesettings', 'settingsversion') > 10: if shared.config.getint('bitmessagesettings', 'settingsversion') > 10:
logger.error('Bitmessage cannot read future versions of the keys file (keys.dat). Run the newer version of Bitmessage.') logger.error('Bitmessage cannot read future versions of the keys file (keys.dat). Run the newer version of Bitmessage.')
raise SystemExit raise SystemExit

View File

@ -299,6 +299,9 @@ class socksocket(socket.socket):
""" """
return self.__proxypeername return self.__proxypeername
def getproxytype(self):
return self.__proxy[0]
def __negotiatesocks4(self,destaddr,destport): def __negotiatesocks4(self,destaddr,destport):
"""__negotiatesocks4(self,destaddr,destport) """__negotiatesocks4(self,destaddr,destport)
Negotiates a connection through a SOCKS4 server. Negotiates a connection through a SOCKS4 server.