From bd520a340f78c1255d6fc6fb1818b4d090174893 Mon Sep 17 00:00:00 2001 From: Peter Surda Date: Thu, 12 Jan 2017 06:58:35 +0100 Subject: [PATCH] Trustedpeer fix and more refactoring - fixed trustedPeer (thanks to anonymous bug reporter) - moved trustedPeer and Peer into state.py --- src/class_outgoingSynSender.py | 8 ++++---- src/class_receiveDataThread.py | 16 ++++++++-------- src/class_sendDataThread.py | 2 +- src/class_singleListener.py | 4 +++- src/defaultKnownNodes.py | 21 +++++++++++---------- src/helper_bootstrap.py | 12 ++++++------ src/helper_startup.py | 2 +- src/shared.py | 14 -------------- src/state.py | 16 ++++++++++++++++ 9 files changed, 50 insertions(+), 45 deletions(-) diff --git a/src/class_outgoingSynSender.py b/src/class_outgoingSynSender.py index 1754962e..b2e0f984 100644 --- a/src/class_outgoingSynSender.py +++ b/src/class_outgoingSynSender.py @@ -32,9 +32,9 @@ class outgoingSynSender(threading.Thread, StoppableThread): # If the user has specified a trusted peer then we'll only # ever connect to that. Otherwise we'll pick a random one from # the known nodes - if shared.trustedPeer: + if state.trustedPeer: shared.knownNodesLock.acquire() - peer = shared.trustedPeer + peer = state.trustedPeer shared.knownNodes[self.streamNumber][peer] = time.time() shared.knownNodesLock.release() else: @@ -65,7 +65,7 @@ class outgoingSynSender(threading.Thread, StoppableThread): try: return peer except NameError: - return shared.Peer('127.0.0.1', 8444) + return state.Peer('127.0.0.1', 8444) def stopThread(self): super(outgoingSynSender, self).stopThread() @@ -79,7 +79,7 @@ class outgoingSynSender(threading.Thread, StoppableThread): self.stop.wait(2) while BMConfigParser().safeGetBoolean('bitmessagesettings', 'sendoutgoingconnections') and not self._stopped: self.name = "outgoingSynSender" - maximumConnections = 1 if shared.trustedPeer else 8 # maximum number of outgoing connections = 8 + maximumConnections = 1 if state.trustedPeer else 8 # maximum number of outgoing connections = 8 while len(self.selfInitiatedConnections[self.streamNumber]) >= maximumConnections: self.stop.wait(10) if shared.shutdown: diff --git a/src/class_receiveDataThread.py b/src/class_receiveDataThread.py index 3ce3b35e..396b9b8c 100644 --- a/src/class_receiveDataThread.py +++ b/src/class_receiveDataThread.py @@ -58,7 +58,7 @@ class receiveDataThread(threading.Thread): objectHashHolderInstance): self.sock = sock - self.peer = shared.Peer(HOST, port) + self.peer = state.Peer(HOST, port) self.name = "receiveData-" + self.peer.host.replace(":", ".") # ":" log parser field separator self.streamNumber = streamNumber self.objectsThatWeHaveYetToGetFromThisPeer = {} @@ -380,7 +380,7 @@ class receiveDataThread(threading.Thread): # We don't need to do the timing attack mitigation if we are # only connected to the trusted peer because we can trust the # peer not to attack - if sleepTime > 0 and doTimingAttackMitigation and shared.trustedPeer == None: + if sleepTime > 0 and doTimingAttackMitigation and state.trustedPeer == None: logger.debug('Timing attack mitigation: Sleeping for ' + str(sleepTime) + ' seconds.') time.sleep(sleepTime) @@ -450,7 +450,7 @@ class receiveDataThread(threading.Thread): logger.info('inv message doesn\'t contain enough data. Ignoring.') return if numberOfItemsInInv == 1: # we'll just request this data from the person who advertised the object. - if totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers > 200000 and len(self.objectsThatWeHaveYetToGetFromThisPeer) > 1000 and shared.trustedPeer == None: # inv flooding attack mitigation + if totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers > 200000 and len(self.objectsThatWeHaveYetToGetFromThisPeer) > 1000 and state.trustedPeer == None: # inv flooding attack mitigation logger.debug('We already have ' + str(totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers) + ' items yet to retrieve from peers and over 1000 from this node in particular. Ignoring this inv message.') return self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware[ @@ -470,7 +470,7 @@ class receiveDataThread(threading.Thread): objectsNewToMe = advertisedSet - Inventory().hashes_by_stream(self.streamNumber) logger.info('inv message lists %s objects. Of those %s are new to me. It took %s seconds to figure that out.', numberOfItemsInInv, len(objectsNewToMe), time.time()-startTime) for item in objectsNewToMe: - if totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers > 200000 and len(self.objectsThatWeHaveYetToGetFromThisPeer) > 1000 and shared.trustedPeer == None: # inv flooding attack mitigation + if totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers > 200000 and len(self.objectsThatWeHaveYetToGetFromThisPeer) > 1000 and state.trustedPeer == None: # inv flooding attack mitigation logger.debug('We already have ' + str(totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers) + ' items yet to retrieve from peers and over ' + str(len(self.objectsThatWeHaveYetToGetFromThisPeer)), ' from this node in particular. Ignoring the rest of this inv message.') break self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware[item] = 0 # helps us keep from sending inv messages to peers that already know about the objects listed therein @@ -593,7 +593,7 @@ class receiveDataThread(threading.Thread): if recaddrStream not in shared.knownNodes: # knownNodes is a dictionary of dictionaries with one outer dictionary for each stream. If the outer stream dictionary doesn't exist yet then we must make it. with shared.knownNodesLock: shared.knownNodes[recaddrStream] = {} - peerFromAddrMessage = shared.Peer(hostStandardFormat, recaddrPort) + peerFromAddrMessage = state.Peer(hostStandardFormat, recaddrPort) if peerFromAddrMessage not in shared.knownNodes[recaddrStream]: if len(shared.knownNodes[recaddrStream]) < 20000 and timeSomeoneElseReceivedMessageFromThisNode > (int(time.time()) - 10800) and timeSomeoneElseReceivedMessageFromThisNode < (int(time.time()) + 10800): # If we have more than 20000 nodes in our list already then just forget about adding more. Also, make sure that the time that someone else received a message from this node is within three hours from now. with shared.knownNodesLock: @@ -637,7 +637,7 @@ class receiveDataThread(threading.Thread): # if current connection is over a proxy, sent our own onion address at a random position if ownPosition == i and ".onion" in BMConfigParser().get("bitmessagesettings", "onionhostname") and \ hasattr(self.sock, "getproxytype") and self.sock.getproxytype() != "none" and not sentOwn: - peer = shared.Peer(BMConfigParser().get("bitmessagesettings", "onionhostname"), BMConfigParser().getint("bitmessagesettings", "onionport")) + peer = state.Peer(BMConfigParser().get("bitmessagesettings", "onionhostname"), BMConfigParser().getint("bitmessagesettings", "onionport")) else: # still may contain own onion address, but we don't change it peer, = random.sample(shared.knownNodes[self.streamNumber], 1) @@ -802,9 +802,9 @@ class receiveDataThread(threading.Thread): if not isHostInPrivateIPRange(self.peer.host): with shared.knownNodesLock: - shared.knownNodes[self.streamNumber][shared.Peer(self.peer.host, self.remoteNodeIncomingPort)] = int(time.time()) + shared.knownNodes[self.streamNumber][state.Peer(self.peer.host, self.remoteNodeIncomingPort)] = int(time.time()) if not self.initiatedConnection: - shared.knownNodes[self.streamNumber][shared.Peer(self.peer.host, self.remoteNodeIncomingPort)] -= 162000 # penalise inbound, 2 days minus 3 hours + shared.knownNodes[self.streamNumber][state.Peer(self.peer.host, self.remoteNodeIncomingPort)] -= 162000 # penalise inbound, 2 days minus 3 hours shared.needToWriteKnownNodesToDisk = True self.sendverack() diff --git a/src/class_sendDataThread.py b/src/class_sendDataThread.py index 60babb1f..f6d15c26 100644 --- a/src/class_sendDataThread.py +++ b/src/class_sendDataThread.py @@ -38,7 +38,7 @@ class sendDataThread(threading.Thread): streamNumber, someObjectsOfWhichThisRemoteNodeIsAlreadyAware): self.sock = sock - self.peer = shared.Peer(HOST, PORT) + self.peer = state.Peer(HOST, PORT) self.name = "sendData-" + self.peer.host.replace(":", ".") # log parser field separator self.streamNumber = streamNumber self.services = 0 diff --git a/src/class_singleListener.py b/src/class_singleListener.py index 896a34bc..0ad82333 100644 --- a/src/class_singleListener.py +++ b/src/class_singleListener.py @@ -10,6 +10,8 @@ import protocol import errno import re +import state + # Only one singleListener thread will ever exist. It creates the # receiveDataThread and sendDataThread for each incoming connection. Note # that it cannot set the stream number because it is not known yet- the @@ -60,7 +62,7 @@ class singleListener(threading.Thread, StoppableThread): def run(self): # If there is a trusted peer then we don't want to accept # incoming connections so we'll just abandon the thread - if shared.trustedPeer: + if state.trustedPeer: return while BMConfigParser().safeGetBoolean('bitmessagesettings', 'dontconnect') and shared.shutdown == 0: diff --git a/src/defaultKnownNodes.py b/src/defaultKnownNodes.py index f91e6fe6..986fbf6d 100644 --- a/src/defaultKnownNodes.py +++ b/src/defaultKnownNodes.py @@ -6,21 +6,22 @@ import random import sys from time import strftime, localtime import shared +import state def createDefaultKnownNodes(appdata): ############## Stream 1 ################ stream1 = {} - #stream1[shared.Peer('2604:2000:1380:9f:82e:148b:2746:d0c7', 8080)] = int(time.time()) - stream1[shared.Peer('5.45.99.75', 8444)] = int(time.time()) - stream1[shared.Peer('75.167.159.54', 8444)] = int(time.time()) - stream1[shared.Peer('95.165.168.168', 8444)] = int(time.time()) - stream1[shared.Peer('85.180.139.241', 8444)] = int(time.time()) - stream1[shared.Peer('158.222.211.81', 8080)] = int(time.time()) - stream1[shared.Peer('178.62.12.187', 8448)] = int(time.time()) - stream1[shared.Peer('24.188.198.204', 8111)] = int(time.time()) - stream1[shared.Peer('109.147.204.113', 1195)] = int(time.time()) - stream1[shared.Peer('178.11.46.221', 8444)] = int(time.time()) + #stream1[state.Peer('2604:2000:1380:9f:82e:148b:2746:d0c7', 8080)] = int(time.time()) + stream1[state.Peer('5.45.99.75', 8444)] = int(time.time()) + stream1[state.Peer('75.167.159.54', 8444)] = int(time.time()) + stream1[state.Peer('95.165.168.168', 8444)] = int(time.time()) + stream1[state.Peer('85.180.139.241', 8444)] = int(time.time()) + stream1[state.Peer('158.222.211.81', 8080)] = int(time.time()) + stream1[state.Peer('178.62.12.187', 8448)] = int(time.time()) + stream1[state.Peer('24.188.198.204', 8111)] = int(time.time()) + stream1[state.Peer('109.147.204.113', 1195)] = int(time.time()) + stream1[state.Peer('178.11.46.221', 8444)] = int(time.time()) ############# Stream 2 ################# stream2 = {} diff --git a/src/helper_bootstrap.py b/src/helper_bootstrap.py index 4acf7105..5361b9be 100644 --- a/src/helper_bootstrap.py +++ b/src/helper_bootstrap.py @@ -24,7 +24,7 @@ def knownNodes(): for node_tuple in nodes.items(): try: host, (port, lastseen) = node_tuple - peer = shared.Peer(host, port) + peer = state.Peer(host, port) except: peer, lastseen = node_tuple shared.knownNodes[stream][peer] = lastseen @@ -32,7 +32,7 @@ def knownNodes(): shared.knownNodes = defaultKnownNodes.createDefaultKnownNodes(state.appdata) # your own onion address, if setup if BMConfigParser().has_option('bitmessagesettings', 'onionhostname') and ".onion" in BMConfigParser().get('bitmessagesettings', 'onionhostname'): - shared.knownNodes[1][shared.Peer(BMConfigParser().get('bitmessagesettings', 'onionhostname'), BMConfigParser().getint('bitmessagesettings', 'onionport'))] = int(time.time()) + shared.knownNodes[1][state.Peer(BMConfigParser().get('bitmessagesettings', 'onionhostname'), BMConfigParser().getint('bitmessagesettings', 'onionport'))] = int(time.time()) if BMConfigParser().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 @@ -47,17 +47,17 @@ def dns(): 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()) + shared.knownNodes[1][state.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()) + shared.knownNodes[1][state.Peer(item[4][0], 8444)] = int(time.time()) except: logger.error('bootstrap8444.bitmessage.org DNS bootstrapping failed.') elif BMConfigParser().get('bitmessagesettings', 'socksproxytype') == 'SOCKS5': - shared.knownNodes[1][shared.Peer('quzwelsuziwqgpt2.onion', 8444)] = int(time.time()) + shared.knownNodes[1][state.Peer('quzwelsuziwqgpt2.onion', 8444)] = int(time.time()) logger.debug("Adding quzwelsuziwqgpt2.onion:8444 to knownNodes.") for port in [8080, 8444]: logger.debug("Resolving %i through SOCKS...", port) @@ -90,7 +90,7 @@ def dns(): else: 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() + shared.knownNodes[1][state.Peer(ip, port)] = time.time() else: logger.info('DNS bootstrap skipped because the proxy type does not support DNS resolution.') diff --git a/src/helper_startup.py b/src/helper_startup.py index 86d3de42..a07bffb8 100644 --- a/src/helper_startup.py +++ b/src/helper_startup.py @@ -25,7 +25,7 @@ def _loadTrustedPeer(): return host, port = trustedPeer.split(':') - shared.trustedPeer = shared.Peer(host, int(port)) + state.trustedPeer = state.Peer(host, int(port)) def loadConfig(): if state.appdata: diff --git a/src/shared.py b/src/shared.py index d5438b1c..f67a2d07 100644 --- a/src/shared.py +++ b/src/shared.py @@ -93,18 +93,6 @@ ridiculousDifficulty = 20000000 # namecoin integration to "namecoind". namecoinDefaultRpcPort = "8336" -# If the trustedpeer option is specified in keys.dat then this will -# contain a Peer which will be connected to instead of using the -# addresses advertised by other peers. The client will only connect to -# this peer and the timing attack mitigation will be disabled in order -# to download data faster. The expected use case is where the user has -# a fast connection to a trusted server where they run a BitMessage -# daemon permanently. If they then run a second instance of the client -# on a local machine periodically when they want to check for messages -# it will sync with the network a lot faster without compromising -# security. -trustedPeer = None - def isAddressInMyAddressBook(address): queryreturn = sqlQuery( '''select address from addressbook where address=?''', @@ -442,8 +430,6 @@ def decryptAndCheckPubkeyPayload(data, address): logger.critical('Pubkey decryption was UNsuccessful because of an unhandled exception! This is definitely a bug! \n%s' % traceback.format_exc()) return 'failed' -Peer = collections.namedtuple('Peer', ['host', 'port']) - def checkAndShareObjectWithPeers(data): """ This function is called after either receiving an object off of the wire diff --git a/src/state.py b/src/state.py index f3fb8a53..9e546b51 100644 --- a/src/state.py +++ b/src/state.py @@ -1,3 +1,5 @@ +import collections + neededPubkeys = {} streamsInWhichIAmParticipating = {} sendDataQueues = [] #each sendData thread puts its queue in this list. @@ -12,3 +14,17 @@ socksIP = None networkProtocolLastFailed = {'IPv4': 0, 'IPv6': 0, 'onion': 0} appdata = '' #holds the location of the application data storage directory + +# If the trustedpeer option is specified in keys.dat then this will +# contain a Peer which will be connected to instead of using the +# addresses advertised by other peers. The client will only connect to +# this peer and the timing attack mitigation will be disabled in order +# to download data faster. The expected use case is where the user has +# a fast connection to a trusted server where they run a BitMessage +# daemon permanently. If they then run a second instance of the client +# on a local machine periodically when they want to check for messages +# it will sync with the network a lot faster without compromising +# security. +trustedPeer = None + +Peer = collections.namedtuple('Peer', ['host', 'port'])