diff --git a/src/api.py b/src/api.py index 52fb8e6e..7c67f89f 100644 --- a/src/api.py +++ b/src/api.py @@ -55,6 +55,8 @@ class APIError(Exception): class StoppableXMLRPCServer(SimpleXMLRPCServer): + allow_reuse_address = True + def serve_forever(self): while state.shutdown == 0: self.handle_request() diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 388a6068..17e97ffd 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -28,6 +28,7 @@ import ctypes from struct import pack from subprocess import call from time import sleep +from random import randint from api import MySimpleXMLRPCRequestHandler, StoppableXMLRPCServer from helper_startup import isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections @@ -171,8 +172,25 @@ class singleAPI(threading.Thread, helper_threading.StoppableThread): pass def run(self): - se = StoppableXMLRPCServer((BMConfigParser().get('bitmessagesettings', 'apiinterface'), BMConfigParser().getint( - 'bitmessagesettings', 'apiport')), MySimpleXMLRPCRequestHandler, True, True) + port = BMConfigParser().getint('bitmessagesettings', 'apiport') + try: + from errno import WSAEADDRINUSE + except (ImportError, AttributeError): + errno.WSAEADDRINUSE = errno.EADDRINUSE + for attempt in range(50): + try: + if attempt > 0: + port = randint(32767, 65535) + se = StoppableXMLRPCServer((BMConfigParser().get('bitmessagesettings', 'apiinterface'), port), + MySimpleXMLRPCRequestHandler, True, True) + except socket.error as e: + if e.errno in (errno.EADDRINUSE, errno.WSAEADDRINUSE): + continue + else: + if attempt > 0: + BMConfigParser().set("bitmessagesettings", "apiport", str(port)) + BMConfigParser().save() + break se.register_introspection_functions() se.serve_forever() diff --git a/src/network/asyncore_pollchoose.py b/src/network/asyncore_pollchoose.py index 2b874c0f..c2568e9f 100644 --- a/src/network/asyncore_pollchoose.py +++ b/src/network/asyncore_pollchoose.py @@ -58,6 +58,7 @@ import os from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, EINVAL, \ ENOTCONN, ESHUTDOWN, EISCONN, EBADF, ECONNABORTED, EPIPE, EAGAIN, \ ECONNREFUSED, EHOSTUNREACH, ENETUNREACH, ENOTSOCK, EINTR, ETIMEDOUT, \ + EADDRINUSE, \ errorcode try: from errno import WSAEWOULDBLOCK @@ -71,6 +72,10 @@ try: from errno import WSAECONNRESET except (ImportError, AttributeError): WSAECONNRESET = ECONNRESET +try: + from errno import WSAEADDRINUSE +except (ImportError, AttributeError): + WSAEADDRINUSE = EADDRINUSE _DISCONNECTED = frozenset((ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED, EPIPE, EBADF, ECONNREFUSED, EHOSTUNREACH, ENETUNREACH, ETIMEDOUT, diff --git a/src/network/connectionpool.py b/src/network/connectionpool.py index 201432f0..8474e84c 100644 --- a/src/network/connectionpool.py +++ b/src/network/connectionpool.py @@ -119,7 +119,9 @@ class BMConnectionPool(object): def startListening(self): host = self.getListeningIP() port = BMConfigParser().safeGetInt("bitmessagesettings", "port") - self.listeningSockets[state.Peer(host, port)] = network.tcp.TCPServer(host=host, port=port) + # correct port even if it changed + ls = network.tcp.TCPServer(host=host, port=port) + self.listeningSockets[ls.destination] = ls def startUDPSocket(self, bind=None): if bind is None: diff --git a/src/network/tcp.py b/src/network/tcp.py index 01b19817..0fcbc160 100644 --- a/src/network/tcp.py +++ b/src/network/tcp.py @@ -252,10 +252,29 @@ class TCPServer(AdvancedDispatcher): AdvancedDispatcher.__init__(self) self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.set_reuse_addr() - self.bind((host, port)) + for attempt in range(50): + try: + if attempt > 0: + port = random.randint(32767, 65535) + self.bind((host, port)) + except socket.error as e: + if e.errno in (asyncore.EADDRINUSE, asyncore.WSAEADDRINUSE): + continue + else: + if attempt > 0: + BMConfigParser().set("bitmessagesettings", "port", str(port)) + BMConfigParser().save() + break self.destination = state.Peer(host, port) + self.bound = True self.listen(5) + def is_bound(self): + try: + return self.bound + except AttributeError: + return False + def handle_accept(self): pair = self.accept() if pair is not None: diff --git a/src/upnp.py b/src/upnp.py index 6c245345..ff657619 100644 --- a/src/upnp.py +++ b/src/upnp.py @@ -7,6 +7,7 @@ from struct import unpack, pack import threading import time from bmconfigparser import BMConfigParser +from network.connectionpool import BMConnectionPool from helper_threading import * import queues import shared @@ -179,7 +180,6 @@ class uPnPThread(threading.Thread, StoppableThread): def __init__ (self): threading.Thread.__init__(self, name="uPnPThread") - self.localPort = BMConfigParser().getint('bitmessagesettings', 'port') try: self.extPort = BMConfigParser().getint('bitmessagesettings', 'extport') except: @@ -199,6 +199,17 @@ class uPnPThread(threading.Thread, StoppableThread): logger.debug("Starting UPnP thread") logger.debug("Local IP: %s", self.localIP) lastSent = 0 + + # wait until asyncore binds so that we know the listening port + bound = False + while state.shutdown == 0 and not self._stopped and not bound: + for s in BMConnectionPool().listeningSockets.values(): + if s.is_bound(): + bound = True + if not bound: + time.sleep(1) + + self.localPort = BMConfigParser().getint('bitmessagesettings', 'port') while state.shutdown == 0 and BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp'): if time.time() - lastSent > self.sendSleep and len(self.routers) == 0: try: