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.socket_inheritance import set_socket_inherit
|
||||||
from calibre.utils.logging import ThreadSafeLog
|
from calibre.utils.logging import ThreadSafeLog
|
||||||
from calibre.utils.monotonic import monotonic
|
from calibre.utils.monotonic import monotonic
|
||||||
|
from calibre.utils.mdns import get_external_ip
|
||||||
|
|
||||||
READ, WRITE, RDWR, WAIT = 'READ', 'WRITE', 'RDWR', 'WAIT'
|
READ, WRITE, RDWR, WAIT = 'READ', 'WRITE', 'RDWR', 'WAIT'
|
||||||
WAKEUP, JOB_DONE = bytes(bytearray(xrange(2)))
|
WAKEUP, JOB_DONE = bytes(bytearray(xrange(2)))
|
||||||
@ -321,41 +322,53 @@ class ServerLoop(object):
|
|||||||
def num_active_connections(self):
|
def num_active_connections(self):
|
||||||
return len(self.connection_map)
|
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):
|
def serve_forever(self):
|
||||||
""" Listen for incoming connections. """
|
""" Listen for incoming connections. """
|
||||||
|
|
||||||
if self.pre_activated_socket is None:
|
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:
|
try:
|
||||||
info = socket.getaddrinfo(
|
self.do_bind()
|
||||||
host, port, socket.AF_UNSPEC,
|
except socket.error as err:
|
||||||
socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
|
if not self.opts.fallback_to_detected_interface:
|
||||||
except socket.gaierror:
|
raise
|
||||||
if ':' in host:
|
ip = get_external_ip()
|
||||||
info = [(socket.AF_INET6, socket.SOCK_STREAM,
|
if ip == self.bind_address[0]:
|
||||||
0, "", self.bind_address + (0, 0))]
|
raise
|
||||||
else:
|
self.log.warn('Failed to bind to %s with error: %s. Trying to bind to the default interface: %s instead' % (
|
||||||
info = [(socket.AF_INET, socket.SOCK_STREAM,
|
self.bind_address[0], as_unicode(err), ip))
|
||||||
0, "", self.bind_address)]
|
self.bind_address = (ip, self.bind_address[1])
|
||||||
|
self.do_bind()
|
||||||
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)
|
|
||||||
else:
|
else:
|
||||||
self.socket = self.pre_activated_socket
|
self.socket = self.pre_activated_socket
|
||||||
self.pre_activated_socket = None
|
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'
|
' 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.',
|
' 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',
|
'Enable/disable zero copy file transfers for increased performance',
|
||||||
'use_sendfile', True,
|
'use_sendfile', True,
|
||||||
'This will use zero-copy in-kernel transfers when sending files over the network,'
|
'This will use zero-copy in-kernel transfers when sending files over the network,'
|
||||||
|
@ -23,7 +23,7 @@ class TestServer(Thread):
|
|||||||
|
|
||||||
daemon = True
|
daemon = True
|
||||||
|
|
||||||
def __init__(self, handler, plugins=(), **kwargs):
|
def __init__(self, handler, plugins=(), specialize=lambda srv:None, **kwargs):
|
||||||
Thread.__init__(self, name='ServerMain')
|
Thread.__init__(self, name='ServerMain')
|
||||||
from calibre.srv.opts import Options
|
from calibre.srv.opts import Options
|
||||||
from calibre.srv.loop import ServerLoop
|
from calibre.srv.loop import ServerLoop
|
||||||
@ -38,6 +38,7 @@ class TestServer(Thread):
|
|||||||
log=ServerLog(level=ServerLog.WARN),
|
log=ServerLog(level=ServerLog.WARN),
|
||||||
)
|
)
|
||||||
self.log = self.loop.log
|
self.log = self.loop.log
|
||||||
|
specialize(self)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
|
@ -94,6 +94,13 @@ class LoopTest(BaseTest):
|
|||||||
server.join()
|
server.join()
|
||||||
self.ae(1, sum(int(w.is_alive()) for w in pool.workers))
|
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):
|
def test_ring_buffer(self):
|
||||||
'Test the ring buffer used for reads'
|
'Test the ring buffer used for reads'
|
||||||
class FakeSocket(object):
|
class FakeSocket(object):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user