Server: Auto-fallback to detected interface if binding to specified interface fails

This commit is contained in:
Kovid Goyal 2015-06-03 19:17:40 +05:30
parent 35dec94173
commit 143d78ea47
4 changed files with 58 additions and 31 deletions

View File

@ -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,13 +322,8 @@ class ServerLoop(object):
def num_active_connections(self):
return len(self.connection_map)
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)
def do_bind(self):
# Get the correct address family for our host (allows IPv6 addresses)
host, port = self.bind_address
try:
info = socket.getaddrinfo(
@ -347,8 +343,8 @@ class ServerLoop(object):
af, socktype, proto, canonname, sa = res
try:
self.bind(af, socktype, proto)
except socket.error, serr:
msg = "%s -- (%s: %s)" % (msg, sa, serr)
except socket.error as serr:
msg = "%s -- (%s: %s)" % (msg, sa, as_unicode(serr))
if self.socket:
self.socket.close()
self.socket = None
@ -356,6 +352,23 @@ class ServerLoop(object):
break
if not self.socket:
raise socket.error(msg)
def serve_forever(self):
""" Listen for incoming connections. """
if self.pre_activated_socket is None:
try:
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

View File

@ -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,'

View File

@ -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:

View File

@ -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):