From 8356ed76d62c3ea130520f4f059d0d9388c3f8b2 Mon Sep 17 00:00:00 2001 From: mailchuck Date: Thu, 26 Nov 2015 23:45:44 +0100 Subject: [PATCH] DNS bootstrap over Tor If proxy type is SOCKS5, it will try to perform DNS bootstrap using the Tor RESOLVE extension. --- src/helper_bootstrap.py | 60 +++++++++++++++++++++++++++++++---------- src/socks/__init__.py | 50 ++++++++++++++++++++++++++++++++-- 2 files changed, 94 insertions(+), 16 deletions(-) diff --git a/src/helper_bootstrap.py b/src/helper_bootstrap.py index ae1adacb..d0de8622 100644 --- a/src/helper_bootstrap.py +++ b/src/helper_bootstrap.py @@ -5,6 +5,7 @@ import pickle import time from debug import logger +import socks def knownNodes(): try: @@ -37,20 +38,51 @@ def dns(): # defaultKnownNodes.py. Hopefully either they are up to date or the user # has run Bitmessage recently without SOCKS turned on and received good # bootstrap nodes using that method. - with shared.printLock: - if shared.config.get('bitmessagesettings', 'socksproxytype') == 'none': + if shared.config.get('bitmessagesettings', 'socksproxytype') == 'none': + try: + for item in socket.getaddrinfo('bootstrap8080.bitmessage.org', 80): + logger.info('Adding ' + item[4][0] + ' to knownNodes based on DNS bootstrap method') + shared.knownNodes[1][shared.Peer(item[4][0], 8080)] = int(time.time()) + except: + logger.error('bootstrap8080.bitmessage.org DNS bootstrapping failed.') + try: + for item in socket.getaddrinfo('bootstrap8444.bitmessage.org', 80): + logger.info ('Adding ' + item[4][0] + ' to knownNodes based on DNS bootstrap method') + shared.knownNodes[1][shared.Peer(item[4][0], 8444)] = int(time.time()) + except: + logger.error('bootstrap8444.bitmessage.org DNS bootstrapping failed.') + elif shared.config.get('bitmessagesettings', 'socksproxytype') == 'SOCKS5': + for port in [8080, 8444]: + logger.debug("Resolving %i through SOCKS...", port) + address_family = socket.AF_INET + sock = socks.socksocket(address_family, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.settimeout(20) + proxytype = socks.PROXY_TYPE_SOCKS5 + sockshostname = shared.config.get( + 'bitmessagesettings', 'sockshostname') + socksport = shared.config.getint( + 'bitmessagesettings', 'socksport') + rdns = True # Do domain name lookups through the proxy; though this setting doesn't really matter since we won't be doing any domain name lookups anyway. + if shared.config.getboolean('bitmessagesettings', 'socksauthentication'): + socksusername = shared.config.get( + 'bitmessagesettings', 'socksusername') + sockspassword = shared.config.get( + 'bitmessagesettings', 'sockspassword') + sock.setproxy( + proxytype, sockshostname, socksport, rdns, socksusername, sockspassword) + else: + sock.setproxy( + proxytype, sockshostname, socksport, rdns) try: - for item in socket.getaddrinfo('bootstrap8080.bitmessage.org', 80): - logger.info('Adding ' + item[4][0] + ' to knownNodes based on DNS bootstrap method') - shared.knownNodes[1][shared.Peer(item[4][0], 8080)] = int(time.time()) + ip = sock.resolve("bootstrap" + str(port) + ".bitmessage.org") + sock.shutdown(socket.SHUT_RDWR) + sock.close() except: - logger.error('bootstrap8080.bitmessage.org DNS bootstrapping failed.') - try: - for item in socket.getaddrinfo('bootstrap8444.bitmessage.org', 80): - logger.info ('Adding ' + item[4][0] + ' to knownNodes based on DNS bootstrap method') - shared.knownNodes[1][shared.Peer(item[4][0], 8444)] = int(time.time()) - except: - logger.error('bootstrap8444.bitmessage.org DNS bootstrapping failed.') - else: - logger.info('DNS bootstrap skipped because SOCKS is used.') + logger.error("SOCKS DNS resolving failed", exc_info=True) + if ip is not None: + logger.info ('Adding ' + ip + ' to knownNodes based on SOCKS DNS bootstrap method') + shared.knownNodes[1][shared.Peer(ip, port)] = time.time() + else: + logger.info('DNS bootstrap skipped because the proxy type does not support DNS resolution.') diff --git a/src/socks/__init__.py b/src/socks/__init__.py index ebb68f45..d5d59687 100644 --- a/src/socks/__init__.py +++ b/src/socks/__init__.py @@ -155,7 +155,7 @@ class socksocket(socket.socket): """ self.__proxy = (proxytype, addr, port, rdns, username, password) - def __negotiatesocks5(self, destaddr, destport): + def __negotiatesocks5(self): """__negotiatesocks5(self,destaddr,destport) Negotiates a connection through a SOCKS5 server. """ @@ -200,6 +200,8 @@ class socksocket(socket.socket): raise Socks5AuthError((2, _socks5autherrors[2])) else: raise GeneralProxyError((1, _generalerrors[1])) + + def __connectsocks5(self, destaddr, destport): # Now we can request the actual connection req = struct.pack('BBB', 0x05, 0x01, 0x00) # If the given destination address is an IP address, we'll @@ -247,6 +249,37 @@ class socksocket(socket.socket): else: self.__proxypeername = (destaddr, destport) + def __resolvesocks5(self, host): + # Now we can request the actual connection + req = struct.pack('BBB', 0x05, 0xF0, 0x00) + req += chr(0x03).encode() + chr(len(host)).encode() + host + req = req + struct.pack(">H", 8444) + self.sendall(req) + # Get the response + ip = "" + resp = self.__recvall(4) + if resp[0:1] != chr(0x05).encode(): + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + elif resp[1:2] != chr(0x00).encode(): + # Connection failed + self.close() + if ord(resp[1:2])<=8: + raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])])) + else: + raise Socks5Error((9, _socks5errors[9])) + # Get the bound address/port + elif resp[3:4] == chr(0x01).encode(): + ip = socket.inet_ntoa(self.__recvall(4)) + elif resp[3:4] == chr(0x03).encode(): + resp = resp + self.recv(1) + ip = self.__recvall(ord(resp[4:5])) + else: + self.close() + raise GeneralProxyError((1,_generalerrors[1])) + boundport = struct.unpack(">H", self.__recvall(2))[0] + return ip + def getproxysockname(self): """getsockname() -> address info Returns the bound IP address and port number at the proxy. @@ -361,7 +394,8 @@ class socksocket(socket.socket): else: portnum = 1080 _orgsocket.connect(self, (self.__proxy[1], portnum)) - self.__negotiatesocks5(destpair[0], destpair[1]) + self.__negotiatesocks5() + self.__connectsocks5(destpair[0], destpair[1]) elif self.__proxy[0] == PROXY_TYPE_SOCKS4: if self.__proxy[2] != None: portnum = self.__proxy[2] @@ -380,3 +414,15 @@ class socksocket(socket.socket): _orgsocket.connect(self, (destpair[0], destpair[1])) else: raise GeneralProxyError((4, _generalerrors[4])) + + def resolve(self, host): + if self.__proxy[0] == PROXY_TYPE_SOCKS5: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 1080 + _orgsocket.connect(self, (self.__proxy[1], portnum)) + self.__negotiatesocks5() + return self.__resolvesocks5(host) + else: + return None \ No newline at end of file