diff --git a/src/class_outgoingSynSender.py b/src/class_outgoingSynSender.py index f9343f62..fef781d3 100644 --- a/src/class_outgoingSynSender.py +++ b/src/class_outgoingSynSender.py @@ -22,26 +22,37 @@ class outgoingSynSender(threading.Thread): self.streamNumber = streamNumber self.selfInitiatedConnections = selfInitiatedConnections + def _getPeer(self): + # 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 + shared.knownNodesLock.acquire() + if shared.trustedPeer: + peer = shared.trustedPeer + shared.knownNodes[self.streamNumber][peer] = time.time() + else: + peer, = random.sample(shared.knownNodes[self.streamNumber], 1) + shared.knownNodesLock.release() + + return peer + def run(self): while shared.safeConfigGetBoolean('bitmessagesettings', 'dontconnect'): time.sleep(2) while shared.safeConfigGetBoolean('bitmessagesettings', 'sendoutgoingconnections'): - while len(self.selfInitiatedConnections[self.streamNumber]) >= 8: # maximum number of outgoing connections = 8 + maximumConnections = 1 if shared.trustedPeer else 8 # maximum number of outgoing connections = 8 + while len(self.selfInitiatedConnections[self.streamNumber]) >= maximumConnections: time.sleep(10) if shared.shutdown: break random.seed() - shared.knownNodesLock.acquire() - peer, = random.sample(shared.knownNodes[self.streamNumber], 1) - shared.knownNodesLock.release() + peer = self._getPeer() shared.alreadyAttemptedConnectionsListLock.acquire() while peer in shared.alreadyAttemptedConnectionsList or peer.host in shared.connectedHostsList: shared.alreadyAttemptedConnectionsListLock.release() # print 'choosing new sample' random.seed() - shared.knownNodesLock.acquire() - peer, = random.sample(shared.knownNodes[self.streamNumber], 1) - shared.knownNodesLock.release() + peer = self._getPeer() time.sleep(1) # Clear out the shared.alreadyAttemptedConnectionsList every half # hour so that this program will again attempt a connection diff --git a/src/class_receiveDataThread.py b/src/class_receiveDataThread.py index 2551f223..31ddce14 100644 --- a/src/class_receiveDataThread.py +++ b/src/class_receiveDataThread.py @@ -313,6 +313,14 @@ class receiveDataThread(threading.Thread): print 'Sending huge inv message with', numberOfObjects, 'objects to just this one peer' self.sendDataThreadQueue.put((0, 'sendRawData', headerData + payload)) + def _sleepForTimingAttackMitigation(self, sleepTime): + # 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: + with shared.printLock: + print 'Timing attack mitigation: Sleeping for', sleepTime, 'seconds.' + time.sleep(sleepTime) # We have received a broadcast message def recbroadcast(self, data): @@ -341,10 +349,7 @@ class receiveDataThread(threading.Thread): sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - \ (time.time() - self.messageProcessingStartTime) - if sleepTime > 0 and doTimingAttackMitigation: - with shared.printLock: - print 'Timing attack mitigation: Sleeping for', sleepTime, 'seconds.' - time.sleep(sleepTime) + self._sleepForTimingAttackMitigation(sleepTime) # We have received a msg message. def recmsg(self, data): @@ -373,10 +378,7 @@ class receiveDataThread(threading.Thread): sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - \ (time.time() - self.messageProcessingStartTime) - if sleepTime > 0 and doTimingAttackMitigation: - with shared.printLock: - print 'Timing attack mitigation: Sleeping for', sleepTime, 'seconds.' - time.sleep(sleepTime) + self._sleepForTimingAttackMitigation(sleepTime) # We have received a pubkey def recpubkey(self, data): @@ -387,11 +389,7 @@ class receiveDataThread(threading.Thread): lengthOfTimeWeShouldUseToProcessThisMessage = .1 sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - \ (time.time() - self.pubkeyProcessingStartTime) - if sleepTime > 0 and doTimingAttackMitigation: - with shared.printLock: - print 'Timing attack mitigation: Sleeping for', sleepTime, 'seconds.' - time.sleep(sleepTime) - + self._sleepForTimingAttackMitigation(sleepTime) # We have received an inv message def recinv(self, data): diff --git a/src/class_singleListener.py b/src/class_singleListener.py index ec1f47d4..49dbf9ce 100644 --- a/src/class_singleListener.py +++ b/src/class_singleListener.py @@ -22,6 +22,11 @@ class singleListener(threading.Thread): self.selfInitiatedConnections = selfInitiatedConnections 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: + return + while shared.safeConfigGetBoolean('bitmessagesettings', 'dontconnect'): time.sleep(1) helper_bootstrap.dns() diff --git a/src/helper_startup.py b/src/helper_startup.py index abc4958f..f9b2ff36 100644 --- a/src/helper_startup.py +++ b/src/helper_startup.py @@ -12,6 +12,17 @@ from namecoin import ensureNamecoinOptions storeConfigFilesInSameDirectoryAsProgramByDefault = False # The user may de-select Portable Mode in the settings if they want the config files to stay in the application data folder. +def _loadTrustedPeer(): + try: + trustedPeer = shared.config.get('bitmessagesettings', 'trustedpeer') + except ConfigParser.Error: + # This probably means the trusted peer wasn't specified so we + # can just leave it as None + return + + host, port = trustedPeer.split(':') + shared.trustedPeer = shared.Peer(host, int(port)) + def loadConfig(): if shared.appdata: shared.config.read(shared.appdata + 'keys.dat') @@ -122,6 +133,8 @@ def loadConfig(): with open(shared.appdata + 'keys.dat', 'wb') as configfile: shared.config.write(configfile) + _loadTrustedPeer() + def isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections(): try: VER_THIS=StrictVersion(platform.version()) diff --git a/src/shared.py b/src/shared.py index e6642c70..1ce570d6 100644 --- a/src/shared.py +++ b/src/shared.py @@ -92,6 +92,18 @@ namecoinDefaultRpcPort = "8336" # binary distributions vs source distributions. frozen = getattr(sys,'frozen', None) +# 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 isInSqlInventory(hash): queryreturn = sqlQuery('''select hash from inventory where hash=?''', hash) return queryreturn != []