diff --git a/setup.py b/setup.py index 0851b78f..ba34f6df 100644 --- a/setup.py +++ b/setup.py @@ -83,7 +83,7 @@ if __name__ == "__main__": 'qrcode': ['qrcode'], 'pyopencl': ['pyopencl'], 'notify2': ['notify2'], - 'sound:platform_system=="Windows"': ['winsound'] + 'sound;platform_system=="Windows"': ['winsound'] }, classifiers=[ "License :: OSI Approved :: MIT License" diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 91032fe5..82a36ad8 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -342,13 +342,21 @@ class Main: sleep(1) def daemonize(self): + grandfatherPid = os.getpid() + parentPid = None try: if os.fork(): + # unlock + shared.thisapp.cleanup() + # wait until grandchild ready + while True: + sleep(1) os._exit(0) except AttributeError: # fork not implemented pass else: + parentPid = os.getpid() shared.thisapp.lock() # relock os.umask(0) try: @@ -358,6 +366,11 @@ class Main: pass try: if os.fork(): + # unlock + shared.thisapp.cleanup() + # wait until child ready + while True: + sleep(1) os._exit(0) except AttributeError: # fork not implemented @@ -374,6 +387,10 @@ class Main: os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) + if parentPid: + # signal ready + os.kill(parentPid, signal.SIGTERM) + os.kill(grandfatherPid, signal.SIGTERM) def setSignalHandler(self): signal.signal(signal.SIGINT, helper_generic.signal_handler) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 64b1177b..a3a79b75 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -69,7 +69,7 @@ import queues import shutdown import state from statusbar import BMStatusBar -import throttle +from network.asyncore_pollchoose import set_rates from version import softwareVersion import sound @@ -2287,16 +2287,16 @@ class MyForm(settingsmixin.SMainWindow): int(float(self.settingsDialogInstance.ui.lineEditMaxDownloadRate.text())))) BMConfigParser().set('bitmessagesettings', 'maxuploadrate', str( int(float(self.settingsDialogInstance.ui.lineEditMaxUploadRate.text())))) - except: + except ValueError: QMessageBox.about(self, _translate("MainWindow", "Number needed"), _translate( "MainWindow", "Your maximum download and upload rate must be numbers. Ignoring what you typed.")) + else: + set_rates(BMConfigParser().safeGetInt("bitmessagesettings", "maxdownloadrate"), + BMConfigParser().safeGetInt("bitmessagesettings", "maxuploadrate")) BMConfigParser().set('bitmessagesettings', 'maxoutboundconnections', str( int(float(self.settingsDialogInstance.ui.lineEditMaxOutboundConnections.text())))) - throttle.SendThrottle().resetLimit() - throttle.ReceiveThrottle().resetLimit() - BMConfigParser().set('bitmessagesettings', 'namecoinrpctype', self.settingsDialogInstance.getNamecoinType()) BMConfigParser().set('bitmessagesettings', 'namecoinrpchost', str( diff --git a/src/helper_generic.py b/src/helper_generic.py index b750e519..4f7a1299 100644 --- a/src/helper_generic.py +++ b/src/helper_generic.py @@ -51,7 +51,7 @@ def signal_handler(signal, frame): raise SystemExit if "PoolWorker" in current_process().name: raise SystemExit - if current_thread().name != "PyBitmessage": + if current_thread().name not in ("PyBitmessage", "MainThread"): return logger.error("Got signal %i", signal) if BMConfigParser().safeGetBoolean('bitmessagesettings', 'daemon'): diff --git a/src/network/advanceddispatcher.py b/src/network/advanceddispatcher.py index c50b6a43..3e84ed85 100644 --- a/src/network/advanceddispatcher.py +++ b/src/network/advanceddispatcher.py @@ -75,7 +75,7 @@ class AdvancedDispatcher(asyncore.dispatcher): def writable(self): self.uploadChunk = AdvancedDispatcher._buf_len if asyncore.maxUploadRate > 0: - self.uploadChunk = asyncore.uploadBucket + self.uploadChunk = int(asyncore.uploadBucket) self.uploadChunk = min(self.uploadChunk, len(self.write_buf)) return asyncore.dispatcher.writable(self) and \ (self.connecting or (self.connected and self.uploadChunk > 0)) @@ -83,7 +83,7 @@ class AdvancedDispatcher(asyncore.dispatcher): def readable(self): self.downloadChunk = AdvancedDispatcher._buf_len if asyncore.maxDownloadRate > 0: - self.downloadChunk = asyncore.downloadBucket + self.downloadChunk = int(asyncore.downloadBucket) try: if self.expectBytes > 0 and not self.fullyEstablished: self.downloadChunk = min(self.downloadChunk, self.expectBytes - len(self.read_buf)) diff --git a/src/network/asyncore_pollchoose.py b/src/network/asyncore_pollchoose.py index caa9d650..5717ff78 100644 --- a/src/network/asyncore_pollchoose.py +++ b/src/network/asyncore_pollchoose.py @@ -112,6 +112,8 @@ uploadBucket = 0 sentBytes = 0 def read(obj): + if not can_receive(): + return try: obj.handle_read_event() except _reraised_exceptions: @@ -120,6 +122,8 @@ def read(obj): obj.handle_error() def write(obj): + if not can_send(): + return try: obj.handle_write_event() except _reraised_exceptions: @@ -129,19 +133,25 @@ def write(obj): def set_rates(download, upload): global maxDownloadRate, maxUploadRate, downloadBucket, uploadBucket, downloadTimestamp, uploadTimestamp - maxDownloadRate = float(download) - maxUploadRate = float(upload) + maxDownloadRate = float(download) * 1024 + maxUploadRate = float(upload) * 1024 downloadBucket = maxDownloadRate uploadBucket = maxUploadRate downloadTimestamp = time.time() uploadTimestamp = time.time() +def can_receive(): + return maxDownloadRate == 0 or downloadBucket > 0 + +def can_send(): + return maxUploadRate == 0 or uploadBucket > 0 + def update_received(download=0): global receivedBytes, downloadBucket, downloadTimestamp currentTimestamp = time.time() receivedBytes += download if maxDownloadRate > 0: - bucketIncrease = int(maxDownloadRate * (currentTimestamp - downloadTimestamp)) + bucketIncrease = maxDownloadRate * (currentTimestamp - downloadTimestamp) downloadBucket += bucketIncrease if downloadBucket > maxDownloadRate: downloadBucket = int(maxDownloadRate) @@ -153,7 +163,7 @@ def update_sent(upload=0): currentTimestamp = time.time() sentBytes += upload if maxUploadRate > 0: - bucketIncrease = int(maxUploadRate * (currentTimestamp - uploadTimestamp)) + bucketIncrease = maxUploadRate * (currentTimestamp - uploadTimestamp) uploadBucket += bucketIncrease if uploadBucket > maxUploadRate: uploadBucket = int(maxUploadRate) @@ -170,9 +180,9 @@ def _exception(obj): def readwrite(obj, flags): try: - if flags & select.POLLIN: + if flags & select.POLLIN and can_receive(): obj.handle_read_event() - if flags & select.POLLOUT: + if flags & select.POLLOUT and can_send(): obj.handle_write_event() if flags & select.POLLPRI: obj.handle_expt_event() diff --git a/src/network/bmobject.py b/src/network/bmobject.py index 249ec2ab..267cac58 100644 --- a/src/network/bmobject.py +++ b/src/network/bmobject.py @@ -8,23 +8,28 @@ from network.dandelion import Dandelion import protocol import state -class BMObjectInsufficientPOWError(Exception): pass +class BMObjectInsufficientPOWError(Exception): + errorCodes = ("Insufficient proof of work") -class BMObjectInvalidDataError(Exception): pass +class BMObjectInvalidDataError(Exception): + errorCodes = ("Data invalid") -class BMObjectExpiredError(Exception): pass +class BMObjectExpiredError(Exception): + errorCodes = ("Object expired") -class BMObjectUnwantedStreamError(Exception): pass +class BMObjectUnwantedStreamError(Exception): + errorCodes = ("Object in unwanted stream") -class BMObjectInvalidError(Exception): pass +class BMObjectInvalidError(Exception): + errorCodes = ("Invalid object") class BMObjectAlreadyHaveError(Exception): - pass + errorCodes = ("Already have this object") class BMObject(object): diff --git a/src/network/bmproto.py b/src/network/bmproto.py index 17b2c761..21ec692c 100644 --- a/src/network/bmproto.py +++ b/src/network/bmproto.py @@ -24,13 +24,16 @@ import shared import state import protocol -class BMProtoError(ProxyError): pass +class BMProtoError(ProxyError): + errorCodes = ("Protocol error") -class BMProtoInsufficientDataError(BMProtoError): pass +class BMProtoInsufficientDataError(BMProtoError): + errorCodes = ("Insufficient data") -class BMProtoExcessiveDataError(BMProtoError): pass +class BMProtoExcessiveDataError(BMProtoError): + errorCodes = ("Too much data") class BMProto(AdvancedDispatcher, ObjectTracker): @@ -494,6 +497,20 @@ class BMProto(AdvancedDispatcher, ObjectTracker): return False except: pass + if not self.isOutbound: + # incoming from a peer we're connected to as outbound, or server full + # report the same error to counter deanonymisation + if state.Peer(self.destination.host, self.peerNode.port) in \ + network.connectionpool.BMConnectionPool().inboundConnections or \ + len(network.connectionpool.BMConnectionPool().inboundConnections) + \ + len(network.connectionpool.BMConnectionPool().outboundConnections) > \ + BMConfigParser().safeGetInt("bitmessagesettings", "maxtotalconnections") + \ + BMConfigParser().safeGetInt("bitmessagesettings", "maxbootstrapconnections"): + self.append_write_buf(protocol.assembleErrorMessage(fatal=2, + errorText="Server full, please try again later.")) + logger.debug ("Closed connection to %s due to server full or duplicate inbound/outbound.", + str(self.destination)) + return False if network.connectionpool.BMConnectionPool().isAlreadyConnected(self.nonce): self.append_write_buf(protocol.assembleErrorMessage(fatal=2, errorText="I'm connected to myself. Closing connection.")) diff --git a/src/network/connectionpool.py b/src/network/connectionpool.py index 2f937a15..44534a76 100644 --- a/src/network/connectionpool.py +++ b/src/network/connectionpool.py @@ -22,8 +22,8 @@ import state class BMConnectionPool(object): def __init__(self): asyncore.set_rates( - BMConfigParser().safeGetInt("bitmessagesettings", "maxdownloadrate") * 1024, - BMConfigParser().safeGetInt("bitmessagesettings", "maxuploadrate") * 1024) + BMConfigParser().safeGetInt("bitmessagesettings", "maxdownloadrate"), + BMConfigParser().safeGetInt("bitmessagesettings", "maxuploadrate")) self.outboundConnections = {} self.inboundConnections = {} self.listeningSockets = {} @@ -65,12 +65,18 @@ class BMConnectionPool(object): def getConnectionByAddr(self, addr): if addr in self.inboundConnections: return self.inboundConnections[addr] - if addr.host in self.inboundConnections: - return self.inboundConnections[addr.host] + try: + if addr.host in self.inboundConnections: + return self.inboundConnections[addr.host] + except AttributeError: + pass if addr in self.outboundConnections: return self.outboundConnections[addr] - if addr.host in self.udpSockets: - return self.udpSockets[addr.host] + try: + if addr.host in self.udpSockets: + return self.udpSockets[addr.host] + except AttributeError: + pass raise KeyError def isAlreadyConnected(self, nodeid): diff --git a/src/network/downloadthread.py b/src/network/downloadthread.py index 5fe1ee25..35616f1b 100644 --- a/src/network/downloadthread.py +++ b/src/network/downloadthread.py @@ -43,7 +43,7 @@ class DownloadThread(threading.Thread, StoppableThread): connections = BMConnectionPool().inboundConnections.values() + BMConnectionPool().outboundConnections.values() random.shuffle(connections) try: - requestChunk = max(int(DownloadThread.maxRequestChunk / len(connections)), 1) + requestChunk = max(int(min(DownloadThread.maxRequestChunk, len(missingObjects)) / len(connections)), 1) except ZeroDivisionError: requestChunk = 1 for i in connections: @@ -63,15 +63,14 @@ class DownloadThread(threading.Thread, StoppableThread): except KeyError: continue random.shuffle(request) + if len(request) > requestChunk - downloadPending: + request = request[:max(1, requestChunk - downloadPending)] if not request: continue - if len(request) > requestChunk - downloadPending: - request = request[:requestChunk - downloadPending] # mark them as pending for k in request: i.objectsNewToMe[k] = False missingObjects[k] = now - payload = bytearray() payload.extend(addresses.encodeVarint(len(request))) for chunk in request: diff --git a/src/network/objectracker.py b/src/network/objectracker.py index f846e7d5..62f01e4f 100644 --- a/src/network/objectracker.py +++ b/src/network/objectracker.py @@ -29,6 +29,7 @@ class ObjectTracker(object): invInitialCapacity = 50000 invErrorRate = 0.03 trackingExpires = 3600 + initialTimeOffset = 60 def __init__(self): self.objectsNewToMe = {} @@ -87,7 +88,7 @@ class ObjectTracker(object): if hashId in Dandelion().hashMap: Dandelion().fluffTrigger(hashId) if hashId not in missingObjects: - missingObjects[hashId] = time.time() + missingObjects[hashId] = time.time() - ObjectTracker.initialTimeOffset with self.objectsNewToMeLock: self.objectsNewToMe[hashId] = True diff --git a/src/network/proxy.py b/src/network/proxy.py index 7d46cd86..96930c18 100644 --- a/src/network/proxy.py +++ b/src/network/proxy.py @@ -10,7 +10,7 @@ import state class ProxyError(Exception): errorCodes = ("UnknownError") - def __init__(self, code): + def __init__(self, code=-1): self.code = code try: self.message = self.__class__.errorCodes[self.code] diff --git a/src/network/tcp.py b/src/network/tcp.py index 70e22e08..5a27aca3 100644 --- a/src/network/tcp.py +++ b/src/network/tcp.py @@ -180,7 +180,7 @@ class TCPConnection(BMProto, TLSDispatcher): payload += hash objectCount += 1 if objectCount >= BMProto.maxObjectCount: - self.sendChunk() + sendChunk() payload = b'' objectCount = 0 @@ -292,7 +292,10 @@ class TCPServer(AdvancedDispatcher): if len(network.connectionpool.BMConnectionPool().inboundConnections) + \ len(network.connectionpool.BMConnectionPool().outboundConnections) > \ BMConfigParser().safeGetInt("bitmessagesettings", "maxtotalconnections") + \ - BMConfigParser().safeGetInt("bitmessagesettings", "maxbootstrapconnections"): + BMConfigParser().safeGetInt("bitmessagesettings", "maxbootstrapconnections") + 10: + # 10 is a sort of buffer, in between it will go through the version handshake + # and return an error to the peer + logger.warning("Server full, dropping connection") sock.close() return try: diff --git a/src/shutdown.py b/src/shutdown.py index 278759e5..49c2fb9b 100644 --- a/src/shutdown.py +++ b/src/shutdown.py @@ -9,14 +9,12 @@ from helper_sql import sqlQuery, sqlStoredProcedure from helper_threading import StoppableThread from knownnodes import saveKnownNodes from inventory import Inventory -import protocol from queues import addressGeneratorQueue, objectProcessorQueue, UISignalQueue, workerQueue import shared import state def doCleanShutdown(): state.shutdown = 1 #Used to tell proof of work worker threads and the objectProcessorThread to exit. - protocol.broadcastToSendDataQueues((0, 'shutdown', 'no data')) objectProcessorQueue.put(('checkShutdownVariable', 'no data')) for thread in threading.enumerate(): if thread.isAlive() and isinstance(thread, StoppableThread): diff --git a/src/singleinstance.py b/src/singleinstance.py index 883c83cc..7f5d0282 100644 --- a/src/singleinstance.py +++ b/src/singleinstance.py @@ -78,6 +78,15 @@ class singleinstance: return if self.daemon and self.lockPid == os.getpid(): # these are the two initial forks while daemonizing + try: + if sys.platform == 'win32': + if hasattr(self, 'fd'): + os.close(self.fd) + else: + fcntl.lockf(self.fp, fcntl.LOCK_UN) + except Exception, e: + pass + return print "Cleaning up lockfile" try: diff --git a/src/version.py b/src/version.py index bd06ca9b..694f71e4 100644 --- a/src/version.py +++ b/src/version.py @@ -1 +1,2 @@ +softwareName = 'PyBitmessage' softwareVersion = '0.6.2'