Add a 'trustedpeer' option to keys.dat

If this option is specified in keys.dat then Bitmessage will connect
to the host specified there instead of connecting to the hosts in the
list of known nodes. It will also stop listening for incoming
connections and the timing attack mitigation will be disabled.

The expected use case is for example where a user is running a daemon
on a dedicated machine in their local network and they occasionally
want to check for messages using a second instance of the client on
their laptop. In that case it would be much faster to catch up with
the messages by directly downloading from the dedicated machine over
the LAN. There is no need to connect to multiple peers or to do the
timing attack mitigation because the daemon is trusted.

The host is specified as hostname:port. Eg, ‘192.168.1.8:8444’.
This commit is contained in:
Neil Roberts 2014-02-06 13:16:07 +00:00
parent 44f31d7bf9
commit 03263156de
5 changed files with 59 additions and 20 deletions

View File

@ -22,26 +22,37 @@ class outgoingSynSender(threading.Thread):
self.streamNumber = streamNumber self.streamNumber = streamNumber
self.selfInitiatedConnections = selfInitiatedConnections 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): def run(self):
while shared.safeConfigGetBoolean('bitmessagesettings', 'dontconnect'): while shared.safeConfigGetBoolean('bitmessagesettings', 'dontconnect'):
time.sleep(2) time.sleep(2)
while shared.safeConfigGetBoolean('bitmessagesettings', 'sendoutgoingconnections'): 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) time.sleep(10)
if shared.shutdown: if shared.shutdown:
break break
random.seed() random.seed()
shared.knownNodesLock.acquire() peer = self._getPeer()
peer, = random.sample(shared.knownNodes[self.streamNumber], 1)
shared.knownNodesLock.release()
shared.alreadyAttemptedConnectionsListLock.acquire() shared.alreadyAttemptedConnectionsListLock.acquire()
while peer in shared.alreadyAttemptedConnectionsList or peer.host in shared.connectedHostsList: while peer in shared.alreadyAttemptedConnectionsList or peer.host in shared.connectedHostsList:
shared.alreadyAttemptedConnectionsListLock.release() shared.alreadyAttemptedConnectionsListLock.release()
# print 'choosing new sample' # print 'choosing new sample'
random.seed() random.seed()
shared.knownNodesLock.acquire() peer = self._getPeer()
peer, = random.sample(shared.knownNodes[self.streamNumber], 1)
shared.knownNodesLock.release()
time.sleep(1) time.sleep(1)
# Clear out the shared.alreadyAttemptedConnectionsList every half # Clear out the shared.alreadyAttemptedConnectionsList every half
# hour so that this program will again attempt a connection # hour so that this program will again attempt a connection

View File

@ -313,6 +313,14 @@ class receiveDataThread(threading.Thread):
print 'Sending huge inv message with', numberOfObjects, 'objects to just this one peer' print 'Sending huge inv message with', numberOfObjects, 'objects to just this one peer'
self.sendDataThreadQueue.put((0, 'sendRawData', headerData + payload)) 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 # We have received a broadcast message
def recbroadcast(self, data): def recbroadcast(self, data):
@ -341,10 +349,7 @@ class receiveDataThread(threading.Thread):
sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - \ sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - \
(time.time() - self.messageProcessingStartTime) (time.time() - self.messageProcessingStartTime)
if sleepTime > 0 and doTimingAttackMitigation: self._sleepForTimingAttackMitigation(sleepTime)
with shared.printLock:
print 'Timing attack mitigation: Sleeping for', sleepTime, 'seconds.'
time.sleep(sleepTime)
# We have received a msg message. # We have received a msg message.
def recmsg(self, data): def recmsg(self, data):
@ -373,10 +378,7 @@ class receiveDataThread(threading.Thread):
sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - \ sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - \
(time.time() - self.messageProcessingStartTime) (time.time() - self.messageProcessingStartTime)
if sleepTime > 0 and doTimingAttackMitigation: self._sleepForTimingAttackMitigation(sleepTime)
with shared.printLock:
print 'Timing attack mitigation: Sleeping for', sleepTime, 'seconds.'
time.sleep(sleepTime)
# We have received a pubkey # We have received a pubkey
def recpubkey(self, data): def recpubkey(self, data):
@ -387,11 +389,7 @@ class receiveDataThread(threading.Thread):
lengthOfTimeWeShouldUseToProcessThisMessage = .1 lengthOfTimeWeShouldUseToProcessThisMessage = .1
sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - \ sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - \
(time.time() - self.pubkeyProcessingStartTime) (time.time() - self.pubkeyProcessingStartTime)
if sleepTime > 0 and doTimingAttackMitigation: self._sleepForTimingAttackMitigation(sleepTime)
with shared.printLock:
print 'Timing attack mitigation: Sleeping for', sleepTime, 'seconds.'
time.sleep(sleepTime)
# We have received an inv message # We have received an inv message
def recinv(self, data): def recinv(self, data):

View File

@ -22,6 +22,11 @@ class singleListener(threading.Thread):
self.selfInitiatedConnections = selfInitiatedConnections self.selfInitiatedConnections = selfInitiatedConnections
def run(self): 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'): while shared.safeConfigGetBoolean('bitmessagesettings', 'dontconnect'):
time.sleep(1) time.sleep(1)
helper_bootstrap.dns() helper_bootstrap.dns()

View File

@ -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. 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(): def loadConfig():
if shared.appdata: if shared.appdata:
shared.config.read(shared.appdata + 'keys.dat') shared.config.read(shared.appdata + 'keys.dat')
@ -122,6 +133,8 @@ def loadConfig():
with open(shared.appdata + 'keys.dat', 'wb') as configfile: with open(shared.appdata + 'keys.dat', 'wb') as configfile:
shared.config.write(configfile) shared.config.write(configfile)
_loadTrustedPeer()
def isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections(): def isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections():
try: try:
VER_THIS=StrictVersion(platform.version()) VER_THIS=StrictVersion(platform.version())

View File

@ -92,6 +92,18 @@ namecoinDefaultRpcPort = "8336"
# binary distributions vs source distributions. # binary distributions vs source distributions.
frozen = getattr(sys,'frozen', None) 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): def isInSqlInventory(hash):
queryreturn = sqlQuery('''select hash from inventory where hash=?''', hash) queryreturn = sqlQuery('''select hash from inventory where hash=?''', hash)
return queryreturn != [] return queryreturn != []