diff --git a/src/calibre/srv/loop.py b/src/calibre/srv/loop.py index f260c36710..73713a1b80 100644 --- a/src/calibre/srv/loop.py +++ b/src/calibre/srv/loop.py @@ -23,6 +23,7 @@ from calibre.srv.utils import ( from calibre.utils.socket_inheritance import set_socket_inherit from calibre.utils.logging import ThreadSafeLog from calibre.utils.monotonic import monotonic +from calibre.utils.mdns import get_external_ip READ, WRITE, RDWR, WAIT = 'READ', 'WRITE', 'RDWR', 'WAIT' WAKEUP, JOB_DONE = bytes(bytearray(xrange(2))) @@ -321,41 +322,53 @@ class ServerLoop(object): def num_active_connections(self): return len(self.connection_map) + def do_bind(self): + # Get the correct address family for our host (allows IPv6 addresses) + host, port = self.bind_address + try: + info = socket.getaddrinfo( + host, port, socket.AF_UNSPEC, + socket.SOCK_STREAM, 0, socket.AI_PASSIVE) + except socket.gaierror: + if ':' in host: + info = [(socket.AF_INET6, socket.SOCK_STREAM, + 0, "", self.bind_address + (0, 0))] + else: + info = [(socket.AF_INET, socket.SOCK_STREAM, + 0, "", self.bind_address)] + + self.socket = None + msg = "No socket could be created" + for res in info: + af, socktype, proto, canonname, sa = res + try: + self.bind(af, socktype, proto) + except socket.error as serr: + msg = "%s -- (%s: %s)" % (msg, sa, as_unicode(serr)) + if self.socket: + self.socket.close() + self.socket = None + continue + break + if not self.socket: + raise socket.error(msg) + def serve_forever(self): """ Listen for incoming connections. """ if self.pre_activated_socket is None: - # AF_INET or AF_INET6 socket - # Get the correct address family for our host (allows IPv6 - # addresses) - host, port = self.bind_address try: - info = socket.getaddrinfo( - host, port, socket.AF_UNSPEC, - socket.SOCK_STREAM, 0, socket.AI_PASSIVE) - except socket.gaierror: - if ':' in host: - info = [(socket.AF_INET6, socket.SOCK_STREAM, - 0, "", self.bind_address + (0, 0))] - else: - info = [(socket.AF_INET, socket.SOCK_STREAM, - 0, "", self.bind_address)] - - self.socket = None - msg = "No socket could be created" - for res in info: - af, socktype, proto, canonname, sa = res - try: - self.bind(af, socktype, proto) - except socket.error, serr: - msg = "%s -- (%s: %s)" % (msg, sa, serr) - if self.socket: - self.socket.close() - self.socket = None - continue - break - if not self.socket: - raise socket.error(msg) + self.do_bind() + except socket.error as err: + if not self.opts.fallback_to_detected_interface: + raise + ip = get_external_ip() + if ip == self.bind_address[0]: + raise + self.log.warn('Failed to bind to %s with error: %s. Trying to bind to the default interface: %s instead' % ( + self.bind_address[0], as_unicode(err), ip)) + self.bind_address = (ip, self.bind_address[1]) + self.do_bind() else: self.socket = self.pre_activated_socket self.pre_activated_socket = None diff --git a/src/calibre/srv/opts.py b/src/calibre/srv/opts.py index 785e0a53bf..aed8ee82f4 100644 --- a/src/calibre/srv/opts.py +++ b/src/calibre/srv/opts.py @@ -67,6 +67,12 @@ raw_options = ( ' example, "127.0.0.1" to only listen for connections from the local machine, or' ' to "::" to listen to all incoming IPv6 and IPv4 connections.', + 'Fallback to auto-detected interface', + 'fallback_to_detected_interface', True, + 'If for some reason the server is unable to bind to the interface specified in' + ' the listen_on option, then it will try to detect an interface that connects' + ' to the outside world and bind to that.', + 'Enable/disable zero copy file transfers for increased performance', 'use_sendfile', True, 'This will use zero-copy in-kernel transfers when sending files over the network,' diff --git a/src/calibre/srv/tests/base.py b/src/calibre/srv/tests/base.py index 59528afce2..702d9d1393 100644 --- a/src/calibre/srv/tests/base.py +++ b/src/calibre/srv/tests/base.py @@ -23,7 +23,7 @@ class TestServer(Thread): daemon = True - def __init__(self, handler, plugins=(), **kwargs): + def __init__(self, handler, plugins=(), specialize=lambda srv:None, **kwargs): Thread.__init__(self, name='ServerMain') from calibre.srv.opts import Options from calibre.srv.loop import ServerLoop @@ -38,6 +38,7 @@ class TestServer(Thread): log=ServerLog(level=ServerLog.WARN), ) self.log = self.loop.log + specialize(self) def run(self): try: diff --git a/src/calibre/srv/tests/loop.py b/src/calibre/srv/tests/loop.py index 3d8f844393..3fa07466f0 100644 --- a/src/calibre/srv/tests/loop.py +++ b/src/calibre/srv/tests/loop.py @@ -94,6 +94,13 @@ class LoopTest(BaseTest): server.join() self.ae(1, sum(int(w.is_alive()) for w in pool.workers)) + def test_fallback_interface(self): + 'Test falling back to default interface' + def specialize(server): + server.loop.log.filter_level = server.loop.log.ERROR + with TestServer(lambda data:(data.path[0] + data.read()), listen_on='1.1.1.1', fallback_to_detected_interface=True, specialize=specialize) as server: + self.assertNotEqual('1.1.1.1', server.address[0]) + def test_ring_buffer(self): 'Test the ring buffer used for reads' class FakeSocket(object):