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):
break
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):
super(outgoingSynSender, self).stopThread()

View File

@ -601,10 +601,19 @@ class receiveDataThread(threading.Thread):
# the right child stream.
with shared.knownNodesLock:
if len(shared.knownNodes[self.streamNumber]) > 0:
ownPosition = random.randint(0, 499)
sentOwn = False
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):
continue
if shared.config.get("bitmessagesettings", "onionhostname") == peer.host:
sentOwn = True
addrsInMyStream[peer] = shared.knownNodes[
self.streamNumber][peer]
if len(shared.knownNodes[self.streamNumber * 2]) > 0:

View File

@ -27,6 +27,9 @@ class singleListener(threading.Thread, StoppableThread):
def _createListenSocket(self, family):
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')
sock = socket.socket(family, socket.SOCK_STREAM)
if family == socket.AF_INET6:
@ -43,12 +46,14 @@ class singleListener(threading.Thread, StoppableThread):
def stopThread(self):
super(singleListener, self).stopThread()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect(('127.0.0.1', shared.config.getint('bitmessagesettings', 'port')))
s.shutdown(socket.SHUT_RDWR)
s.close()
except:
pass
for ip in ('127.0.0.1', shared.config.get('bitmessagesettings', 'onionbindip')):
try:
s.connect((ip, shared.config.getint('bitmessagesettings', 'port')))
s.shutdown(socket.SHUT_RDWR)
s.close()
break
except:
pass
def run(self):
# 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
# SOCKS proxy, unless they have configured otherwise. If they eventually select
# proxy 'none' or configure SOCKS listening then this will start listening for
# connections.
while shared.config.get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and not shared.config.getboolean('bitmessagesettings', 'sockslisten') and shared.shutdown == 0:
# connections. But if on SOCKS and have an onionhostname, listen
# (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)
logger.info('Listening for incoming connections.')
@ -77,6 +86,7 @@ class singleListener(threading.Thread, StoppableThread):
if (isinstance(e.args, tuple) and
e.args[0] in (errno.EAFNOSUPPORT,
errno.EPFNOSUPPORT,
errno.EADDRNOTAVAIL,
errno.ENOPROTOOPT)):
sock = self._createListenSocket(socket.AF_INET)
else:
@ -90,7 +100,7 @@ class singleListener(threading.Thread, StoppableThread):
# SOCKS proxy, unless they have configured otherwise. If they eventually select
# proxy 'none' or configure SOCKS listening then this will start listening for
# 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)
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.')
@ -112,7 +122,9 @@ class singleListener(threading.Thread, StoppableThread):
# is already connected because the two computers will
# share the same external IP. This is here to prevent
# 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()
logger.info('We are already connected to ' + str(HOST) + '. Ignoring connection.')
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.')
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
# Bitmessage users or modify the SQLite database? Add it right above this line!

View File

@ -21,13 +21,16 @@ def knownNodes():
shared.knownNodes[stream] = {}
for node_tuple in nodes.items():
try:
host, (port, time) = node_tuple
host, (port, lastseen) = node_tuple
peer = shared.Peer(host, port)
except:
peer, time = node_tuple
shared.knownNodes[stream][peer] = time
peer, lastseen = node_tuple
shared.knownNodes[stream][peer] = lastseen
except:
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:
logger.error('Bitmessage cannot read future versions of the keys file (keys.dat). Run the newer version of Bitmessage.')
raise SystemExit

View File

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