From d4b52a009892ea2a0d3520ec1be2a5a407b9662f Mon Sep 17 00:00:00 2001 From: YOKOTA Hiroshi Date: Sat, 22 Oct 2022 14:48:49 +0900 Subject: [PATCH 01/11] Use IPv6 address for content server default value --- src/calibre/srv/opts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/srv/opts.py b/src/calibre/srv/opts.py index b9ebffe6e7..811e624c2a 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', '::', + _('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.'), From 080e61d1286824c6afd196f1bd6dda81f005804f Mon Sep 17 00:00:00 2001 From: YOKOTA Hiroshi Date: Sat, 22 Oct 2022 18:33:07 +0900 Subject: [PATCH 02/11] IPv6 requires [] for host:port style notation ex) IPv4: 127.0.0.1:8080 IPv6: [::1]:8080 --- src/calibre/gui2/actions/device.py | 12 ++++++++++++ src/calibre/gui2/preferences/server.py | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/calibre/gui2/actions/device.py b/src/calibre/gui2/actions/device.py index 9779e05cad..ea636fef6f 100644 --- a/src/calibre/gui2/actions/device.py +++ b/src/calibre/gui2/actions/device.py @@ -16,10 +16,22 @@ from calibre.utils.smtp import config as email_config def local_url_for_content_server(): + def is_ipv6_addr(addr): + import socket + try: + socket.inet_pton(socket.AF_INET6, addr) + return True + except OSError: + return False + from calibre.srv.opts import server_config opts = server_config() interface = opts.listen_on or '0.0.0.0' interface = {'0.0.0.0': '127.0.0.1', '::':'::1'}.get(interface) + + if is_ipv6_addr(interface): + interface = f'[{interface}]' + protocol = 'https' if opts.ssl_certfile and opts.ssl_keyfile else 'http' prefix = opts.url_prefix or '' port = opts.port diff --git a/src/calibre/gui2/preferences/server.py b/src/calibre/gui2/preferences/server.py index e7c10a8883..ef9f1307f6 100644 --- a/src/calibre/gui2/preferences/server.py +++ b/src/calibre/gui2/preferences/server.py @@ -1299,10 +1299,22 @@ class ConfigWidget(ConfigWidgetBase): self.stopping_msg.accept() def test_server(self): + def is_ipv6_addr(addr): + import socket + try: + socket.inet_pton(socket.AF_INET6, addr) + return True + except OSError: + return False + 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) + + 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) From 70ba016b1f98da29634e8187e853b4658c19d194 Mon Sep 17 00:00:00 2001 From: YOKOTA Hiroshi Date: Sun, 23 Oct 2022 14:42:41 +0900 Subject: [PATCH 03/11] Add [] for IPv6 address in log file --- src/calibre/srv/loop.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/calibre/srv/loop.py b/src/calibre/srv/loop.py index c1f46f6305..4fc4f68738 100644 --- a/src/calibre/srv/loop.py +++ b/src/calibre/srv/loop.py @@ -508,17 +508,29 @@ class ServerLoop: self.setup_socket() def serve(self): + def is_ipv6_addr(addr): + import socket + try: + socket.inet_pton(socket.AF_INET6, addr) + return True + except OSError: + return False + 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 From b5cdb0e6ea90f8375e180e678421d22d1cfca250 Mon Sep 17 00:00:00 2001 From: YOKOTA Hiroshi Date: Sat, 22 Oct 2022 22:04:42 +0900 Subject: [PATCH 04/11] Keep original IP address if the IP address is not in address map dict.get() returns None if the key is not in dict. --- src/calibre/gui2/actions/device.py | 6 +++++- src/calibre/gui2/preferences/server.py | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/actions/device.py b/src/calibre/gui2/actions/device.py index ea636fef6f..1fc26ca647 100644 --- a/src/calibre/gui2/actions/device.py +++ b/src/calibre/gui2/actions/device.py @@ -27,7 +27,11 @@ def local_url_for_content_server(): from calibre.srv.opts import server_config opts = server_config() interface = opts.listen_on or '0.0.0.0' - interface = {'0.0.0.0': '127.0.0.1', '::':'::1'}.get(interface) + + addr_map = {'0.0.0.0': '127.0.0.1', + '::': '::1'} + if interface in addr_map: + interface = addr_map[interface] if is_ipv6_addr(interface): interface = f'[{interface}]' diff --git a/src/calibre/gui2/preferences/server.py b/src/calibre/gui2/preferences/server.py index ef9f1307f6..98a3b783d3 100644 --- a/src/calibre/gui2/preferences/server.py +++ b/src/calibre/gui2/preferences/server.py @@ -1310,7 +1310,11 @@ class ConfigWidget(ConfigWidgetBase): 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) + + 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}]' From 70de4cc6a27e014f07668238b7692dd00acd2cf4 Mon Sep 17 00:00:00 2001 From: YOKOTA Hiroshi Date: Sun, 23 Oct 2022 01:57:14 +0900 Subject: [PATCH 05/11] Return None if IP address string is None "ip_address" parameter is None when content server config value "listen_on" is blank string. --- src/calibre/utils/mdns.py | 3 +++ 1 file changed, 3 insertions(+) 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 From 8a400c83b26e2e0223512ac743ec072a0c2f06bb Mon Sep 17 00:00:00 2001 From: YOKOTA Hiroshi Date: Sat, 18 Nov 2023 12:24:18 +0900 Subject: [PATCH 06/11] Refactor IPv6 handling Move is_ipv6_addr() to calibre.utils.network module --- src/calibre/gui2/actions/device.py | 10 ++-------- src/calibre/gui2/preferences/server.py | 8 +------- src/calibre/srv/loop.py | 8 +------- src/calibre/utils/network.py | 8 ++++++++ 4 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/calibre/gui2/actions/device.py b/src/calibre/gui2/actions/device.py index 1fc26ca647..656fc5be79 100644 --- a/src/calibre/gui2/actions/device.py +++ b/src/calibre/gui2/actions/device.py @@ -16,15 +16,9 @@ from calibre.utils.smtp import config as email_config def local_url_for_content_server(): - def is_ipv6_addr(addr): - import socket - try: - socket.inet_pton(socket.AF_INET6, addr) - return True - except OSError: - return False - from calibre.srv.opts import server_config + from calibre.utils.network import is_ipv6_addr + opts = server_config() interface = opts.listen_on or '0.0.0.0' diff --git a/src/calibre/gui2/preferences/server.py b/src/calibre/gui2/preferences/server.py index 98a3b783d3..65aa700d55 100644 --- a/src/calibre/gui2/preferences/server.py +++ b/src/calibre/gui2/preferences/server.py @@ -1299,13 +1299,7 @@ class ConfigWidget(ConfigWidgetBase): self.stopping_msg.accept() def test_server(self): - def is_ipv6_addr(addr): - import socket - try: - socket.inet_pton(socket.AF_INET6, addr) - return True - except OSError: - return False + from calibre.utils.network import is_ipv6_addr prefix = self.advanced_tab.get('url_prefix') or '' protocol = 'https' if self.advanced_tab.has_ssl else 'http' diff --git a/src/calibre/srv/loop.py b/src/calibre/srv/loop.py index 4fc4f68738..beb02b8bdf 100644 --- a/src/calibre/srv/loop.py +++ b/src/calibre/srv/loop.py @@ -508,13 +508,7 @@ class ServerLoop: self.setup_socket() def serve(self): - def is_ipv6_addr(addr): - import socket - try: - socket.inet_pton(socket.AF_INET6, addr) - return True - except OSError: - return False + from calibre.utils.network import is_ipv6_addr self.connection_map = {} if not self.socket_was_preactivated: diff --git a/src/calibre/utils/network.py b/src/calibre/utils/network.py index 410206df81..ca469f9c89 100644 --- a/src/calibre/utils/network.py +++ b/src/calibre/utils/network.py @@ -108,3 +108,11 @@ 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 From dc4ba2e18b4c86d23e08a664bd7a86fa02c15624 Mon Sep 17 00:00:00 2001 From: YOKOTA Hiroshi Date: Mon, 20 Nov 2023 20:43:56 +0900 Subject: [PATCH 07/11] Check '::' really supports both IPv6 and IPv4 socket.has_dualstack_ipv6() checks current runtime environment really works with both IPv6 and IPv4. --- src/calibre/srv/opts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/srv/opts.py b/src/calibre/srv/opts.py index 811e624c2a..afc27afa39 100644 --- a/src/calibre/srv/opts.py +++ b/src/calibre/srv/opts.py @@ -11,6 +11,7 @@ from collections import OrderedDict, namedtuple from functools import partial from itertools import zip_longest from operator import attrgetter +from socket import has_dualstack_ipv6 from calibre.constants import config_dir from calibre.utils.localization import _ @@ -110,7 +111,7 @@ 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', '::', + 'listen_on', '::' if has_dualstack_ipv6() else '0.0.0.0', _('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.'), From 091404eb0da6e958544fa461fdc7fcf45206db9c Mon Sep 17 00:00:00 2001 From: YOKOTA Hiroshi Date: Tue, 21 Nov 2023 00:24:30 +0900 Subject: [PATCH 08/11] Use automatic server address if listen_on value is '' --- src/calibre/gui2/actions/device.py | 4 ++-- src/calibre/gui2/preferences/server.py | 4 ++-- src/calibre/srv/opts.py | 3 +-- src/calibre/utils/network.py | 7 +++++++ 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/actions/device.py b/src/calibre/gui2/actions/device.py index 656fc5be79..295a5e378d 100644 --- a/src/calibre/gui2/actions/device.py +++ b/src/calibre/gui2/actions/device.py @@ -17,10 +17,10 @@ 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 + 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 = opts.listen_on or get_fallback_server_addr() addr_map = {'0.0.0.0': '127.0.0.1', '::': '::1'} diff --git a/src/calibre/gui2/preferences/server.py b/src/calibre/gui2/preferences/server.py index 65aa700d55..d3812377dc 100644 --- a/src/calibre/gui2/preferences/server.py +++ b/src/calibre/gui2/preferences/server.py @@ -1299,11 +1299,11 @@ class ConfigWidget(ConfigWidgetBase): self.stopping_msg.accept() def test_server(self): - from calibre.utils.network import is_ipv6_addr + 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 = self.advanced_tab.get('listen_on') or get_fallback_server_addr() addr_map = {'0.0.0.0': '127.0.0.1', '::': '::1'} diff --git a/src/calibre/srv/opts.py b/src/calibre/srv/opts.py index afc27afa39..a6a8f1470d 100644 --- a/src/calibre/srv/opts.py +++ b/src/calibre/srv/opts.py @@ -11,7 +11,6 @@ from collections import OrderedDict, namedtuple from functools import partial from itertools import zip_longest from operator import attrgetter -from socket import has_dualstack_ipv6 from calibre.constants import config_dir from calibre.utils.localization import _ @@ -111,7 +110,7 @@ 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', '::' if has_dualstack_ipv6() else '0.0.0.0', + 'listen_on', '', _('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/network.py b/src/calibre/utils/network.py index ca469f9c89..e2d071f594 100644 --- a/src/calibre/utils/network.py +++ b/src/calibre/utils/network.py @@ -116,3 +116,10 @@ def is_ipv6_addr(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' From 4d3553a11510b4f29965257b657abe87c1dee6b8 Mon Sep 17 00:00:00 2001 From: YOKOTA Hiroshi Date: Tue, 21 Nov 2023 12:50:30 +0900 Subject: [PATCH 09/11] Listen IPv6 address if available --- src/calibre/srv/loop.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/srv/loop.py b/src/calibre/srv/loop.py index beb02b8bdf..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 = {} From 2f751d3dabe9a287f31aa1e2881eb683d3ad75a0 Mon Sep 17 00:00:00 2001 From: YOKOTA Hiroshi Date: Tue, 21 Nov 2023 20:30:40 +0900 Subject: [PATCH 10/11] Use None for empty value --- src/calibre/srv/opts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/srv/opts.py b/src/calibre/srv/opts.py index a6a8f1470d..f6e3fd7e27 100644 --- a/src/calibre/srv/opts.py +++ b/src/calibre/srv/opts.py @@ -110,7 +110,7 @@ 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', '', + '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.'), From 9e1721e8bc930193fd7523299e0932f7c33698fb Mon Sep 17 00:00:00 2001 From: YOKOTA Hiroshi Date: Tue, 21 Nov 2023 20:56:29 +0900 Subject: [PATCH 11/11] Don't enclose [] twice when IPv6 addr --- src/calibre/gui2/actions/device.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/actions/device.py b/src/calibre/gui2/actions/device.py index 295a5e378d..f8515e7059 100644 --- a/src/calibre/gui2/actions/device.py +++ b/src/calibre/gui2/actions/device.py @@ -27,15 +27,13 @@ def local_url_for_content_server(): if interface in addr_map: interface = addr_map[interface] - if is_ipv6_addr(interface): - interface = f'[{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():