diff --git a/src/calibre/gui2/actions/device.py b/src/calibre/gui2/actions/device.py index 9779e05cad..f8515e7059 100644 --- a/src/calibre/gui2/actions/device.py +++ b/src/calibre/gui2/actions/device.py @@ -17,15 +17,23 @@ from calibre.utils.smtp import config as email_config def local_url_for_content_server(): from calibre.srv.opts import server_config + from calibre.utils.network import is_ipv6_addr, get_fallback_server_addr + opts = server_config() - interface = opts.listen_on or '0.0.0.0' - interface = {'0.0.0.0': '127.0.0.1', '::':'::1'}.get(interface) + interface = opts.listen_on or get_fallback_server_addr() + + addr_map = {'0.0.0.0': '127.0.0.1', + '::': '::1'} + if interface in addr_map: + interface = addr_map[interface] + protocol = 'https' if opts.ssl_certfile and opts.ssl_keyfile else 'http' prefix = opts.url_prefix or '' + port = opts.port - if ':' in interface: - interface = f'[{interface}]' - return f'{protocol}://{interface}:{port}{prefix}' + addr = f'[{interface}]' if is_ipv6_addr(interface) else f'{interface}' + + return f'{protocol}://{addr}:{port}{prefix}' def open_in_browser(): diff --git a/src/calibre/gui2/preferences/server.py b/src/calibre/gui2/preferences/server.py index e7c10a8883..d3812377dc 100644 --- a/src/calibre/gui2/preferences/server.py +++ b/src/calibre/gui2/preferences/server.py @@ -1299,10 +1299,20 @@ class ConfigWidget(ConfigWidgetBase): self.stopping_msg.accept() def test_server(self): + from calibre.utils.network import is_ipv6_addr, get_fallback_server_addr + prefix = self.advanced_tab.get('url_prefix') or '' protocol = 'https' if self.advanced_tab.has_ssl else 'http' - lo = self.advanced_tab.get('listen_on') or '0.0.0.0' - lo = {'0.0.0.0': '127.0.0.1', '::':'::1'}.get(lo) + lo = self.advanced_tab.get('listen_on') or get_fallback_server_addr() + + addr_map = {'0.0.0.0': '127.0.0.1', + '::': '::1'} + if lo in addr_map: + lo = addr_map[lo] + + if is_ipv6_addr(lo): + lo = f'[{lo}]' + url = '{protocol}://{interface}:{port}{prefix}'.format( protocol=protocol, interface=lo, port=self.main_tab.opt_port.value(), prefix=prefix) diff --git a/src/calibre/srv/loop.py b/src/calibre/srv/loop.py index c1f46f6305..61abdb35f4 100644 --- a/src/calibre/srv/loop.py +++ b/src/calibre/srv/loop.py @@ -29,6 +29,7 @@ from calibre.utils.localization import _ from calibre.utils.logging import ThreadSafeLog from calibre.utils.mdns import get_external_ip from calibre.utils.monotonic import monotonic +from calibre.utils.network import get_fallback_server_addr from calibre.utils.socket_inheritance import set_socket_inherit from polyglot.builtins import iteritems from polyglot.queue import Empty, Full @@ -399,7 +400,7 @@ class ServerLoop: ba = (self.opts.listen_on, int(self.opts.port)) if not ba[0]: # AI_PASSIVE does not work with host of '' or None - ba = ('0.0.0.0', ba[1]) + ba = (get_fallback_server_addr(), ba[1]) self.bind_address = ba self.bound_address = None self.connection_map = {} @@ -508,17 +509,23 @@ class ServerLoop: self.setup_socket() def serve(self): + from calibre.utils.network import is_ipv6_addr + self.connection_map = {} if not self.socket_was_preactivated: self.socket.listen(min(socket.SOMAXCONN, 128)) self.bound_address = ba = self.socket.getsockname() if isinstance(ba, tuple): - ba = ':'.join(map(str, ba)) + if is_ipv6_addr(ba[0]): + addr = f'[{ba[0]}]' + else: + addr = f'{ba[0]}' + ba_str = f'{addr}:' + ':'.join(map(str, ba[1:])) self.pool.start() with TemporaryDirectory(prefix='srv-') as tdir: self.tdir = tdir if self.LISTENING_MSG: - self.log(self.LISTENING_MSG, ba) + self.log(self.LISTENING_MSG, ba_str) self.plugin_pool.start() self.ready = True diff --git a/src/calibre/srv/opts.py b/src/calibre/srv/opts.py index b9ebffe6e7..f6e3fd7e27 100644 --- a/src/calibre/srv/opts.py +++ b/src/calibre/srv/opts.py @@ -110,8 +110,8 @@ raw_options = ( ' there are more than this number of items. Set to zero to disable.'), _('The interface on which to listen for connections'), - 'listen_on', '0.0.0.0', - _('The default is to listen on all available IPv4 interfaces. You can change this to, for' + 'listen_on', None, + _('The default is to listen on all available IPv6 and IPv4 interfaces. You can change this to, for' ' 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.'), diff --git a/src/calibre/utils/mdns.py b/src/calibre/utils/mdns.py index 3e401e9572..f2fbe1a190 100644 --- a/src/calibre/utils/mdns.py +++ b/src/calibre/utils/mdns.py @@ -77,6 +77,9 @@ def _get_external_ip(): def verify_ipV4_address(ip_address): + if ip_address == None: + return None + result = None if ip_address != '0.0.0.0' and ip_address != '::': # do some more sanity checks on the address diff --git a/src/calibre/utils/network.py b/src/calibre/utils/network.py index 410206df81..e2d071f594 100644 --- a/src/calibre/utils/network.py +++ b/src/calibre/utils/network.py @@ -108,3 +108,18 @@ def internet_connected(): DummyNetworkStatus() return internet_connected.checker() + +def is_ipv6_addr(addr): + import socket + try: + socket.inet_pton(socket.AF_INET6, addr) + return True + except OSError: + return False + +def get_fallback_server_addr(): + from socket import has_dualstack_ipv6 + if has_dualstack_ipv6(): + return '::' + else: + return '0.0.0.0'