mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Server: Auto-fallback to detected interface if binding to specified interface fails
This commit is contained in:
parent
35dec94173
commit
143d78ea47
@ -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
|
||||
|
@ -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,'
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user