From 3ed1220abd2e9ec39bb2655175a547686074ba7c Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 5 Aug 2023 18:56:54 +0200 Subject: [PATCH 01/15] server/upsd.c, NEWS, UPGRADING: listen_add(): firstaddr should remain first [#2012] Signed-off-by: Jim Klimov --- NEWS | 4 ++++ UPGRADING | 6 ++++++ server/upsd.c | 10 ++++++++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 3334786ef7..6dffcfea54 100644 --- a/NEWS +++ b/NEWS @@ -278,6 +278,10 @@ as part of https://github.com/networkupstools/nut/issues/1410 solution. * added support for `NUT_QUIET_INIT_SSL` environment variable to hide the infamous "Init SSL without certificate database" warning [#1662] + - The `upsd.conf` listing of `LISTEN` addresses was previously inverted + (the last listed address was applied first), which was counter-intuitive + and fixed for this release [#2012] + - sstate (server state, e.g. upsd) should now "PING" drivers also if they last reported themselves as "stale" (and might later crash) so their connections would be terminated if really no longer active [#1626] diff --git a/UPGRADING b/UPGRADING index 10b87c0968..72a1de5de5 100644 --- a/UPGRADING +++ b/UPGRADING @@ -65,6 +65,12 @@ Changes from 2.8.0 to 2.8.1 the packaging recipes may use NUT source-code facilities and package just symlinks as relevant for each distro separately [#1462, #1504] +- The `upsd.conf` listing of `LISTEN` addresses was previously inverted + (the last listed address was applied first), which was counter-intuitive + and fixed for this release. If user configurations somehow relied on this + order (e.g. to prioritize IPv6 vs IPv4 listeners), configuration changes + may be needed. [#2012] + - Added support for `make sockdebug` for easier developer access to the tool; also if `configure --with-dev` is in effect, it would now be installed to the configured `libexec` location. A man page was also added. [#1936] diff --git a/server/upsd.c b/server/upsd.c index 8c66661b62..f2de2dc233 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -237,9 +237,15 @@ void listen_add(const char *addr, const char *port) server->addr = xstrdup(addr); server->port = xstrdup(port); server->sock_fd = ERROR_FD_SOCK; - server->next = firstaddr; + server->next = NULL; - firstaddr = server; + if (firstaddr) { + stype_t *tmp; + for (tmp = firstaddr; tmp->next; tmp = tmp->next); + tmp->next = server; + } else { + firstaddr = server; + } upsdebugx(3, "listen_add: added %s:%s", server->addr, server->port); } From a575c809f86ee7458acf69eb3e2c09aa5c20d01d Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 5 Aug 2023 19:04:09 +0200 Subject: [PATCH 02/15] NEWS: typo fix Signed-off-by: Jim Klimov --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 6dffcfea54..2a99a07bfc 100644 --- a/NEWS +++ b/NEWS @@ -62,7 +62,7 @@ as part of https://github.com/networkupstools/nut/issues/1410 solution. too low. As issue #1455 and PR #1495 found, in two cases the called commands did "meaningfully" modify data -- so without debug logs the program misbehaved. A known regression for `upscode2` driver; might - be or not be a problem with `upsd` driver in NUT v2.8.0 release, + be or not be a problem with `upsd` server in NUT v2.8.0 release, fixed for NUT v2.8.1. * A table in `cyberpower-mib` (for `snmp-ups` driver) sources was arranged in NUT v2.8.0 release in a way that precluded the driver From 7bf7f0c6a9f78e9a6bd8d7277a24874a472405c3 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 5 Aug 2023 19:04:51 +0200 Subject: [PATCH 03/15] server/upsd.c: server_load(): log if defaulting to localhost (IPv4/IPv6) due to lack of LISTEN directive [#2012] Signed-off-by: Jim Klimov --- server/upsd.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/upsd.c b/server/upsd.c index f2de2dc233..2004f8c9bb 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -694,13 +694,16 @@ void server_load(void) { stype_t *server; - /* default behaviour if no LISTEN addres has been specified */ + /* default behaviour if no LISTEN address has been specified */ if (!firstaddr) { + /* Note: default opt_af==AF_UNSPEC so not constrained to only one protocol */ if (opt_af != AF_INET) { + upsdebugx(1, "%s: No LISTEN configuration provided, will try IPv6 localhost", __func__); listen_add("::1", string_const(PORT)); } if (opt_af != AF_INET6) { + upsdebugx(1, "%s: No LISTEN configuration provided, will try IPv4 localhost", __func__); listen_add("127.0.0.1", string_const(PORT)); } } From f068e8a8a9f3e2b3fee91794990c89096415cab6 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 5 Aug 2023 19:40:03 +0200 Subject: [PATCH 04/15] server/upsd.c: setuptcp(): warn if we bound to one address for a name and end the loop while there seem to be more resolved addresses for it [#2012] Signed-off-by: Jim Klimov --- server/upsd.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/server/upsd.c b/server/upsd.c index 2004f8c9bb..17155dd9c7 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -312,6 +312,20 @@ static void setuptcp(stype_t *server) continue; } + if (ai->ai_next) { + char ipaddrbuf[SMALLBUF]; + const char *ipaddr; + snprintf(ipaddrbuf, sizeof(ipaddrbuf), " as "); + ipaddr = inet_ntop(ai->ai_family, ai->ai_addr, + ipaddrbuf + strlen(ipaddrbuf), + sizeof(ipaddrbuf)); + upslogx(LOG_WARNING, + "setuptcp: bound to %s%s but there seem to be " + "further (ignored) addresses resolved for this name", + server->addr, + ipaddr == NULL ? "" : ipaddrbuf); + } + server->sock_fd = sock_fd; break; } From 534f153f2005bfdb421d1440bc7a823ac38845a9 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 5 Aug 2023 20:36:11 +0200 Subject: [PATCH 05/15] server/upsd.c: refactor server_free() with a new stype_free() [#2012] Signed-off-by: Jim Klimov --- server/upsd.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/server/upsd.c b/server/upsd.c index 17155dd9c7..eb99df810c 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -250,6 +250,20 @@ void listen_add(const char *addr, const char *port) upsdebugx(3, "listen_add: added %s:%s", server->addr, server->port); } +/* Close the connection if needed and free the allocated memory. + * WARNING: it is up to the caller to rewrite the "next" pointer + * in whoever points to this server instance (if needed)! */ +static void stype_free(stype_t *server) +{ + if (VALID_FD_SOCK(server->sock_fd)) { + close(server->sock_fd); + } + + free(server->addr); + free(server->port); + free(server); +} + /* create a listening socket for tcp connections */ static void setuptcp(stype_t *server) { @@ -739,14 +753,7 @@ void server_free(void) /* cleanup server fds */ for (server = firstaddr; server; server = snext) { snext = server->next; - - if (VALID_FD_SOCK(server->sock_fd)) { - close(server->sock_fd); - } - - free(server->addr); - free(server->port); - free(server); + stype_free(server); } firstaddr = NULL; From 22b51fc79b345861722dd52b06bdd783d2d36dd3 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 5 Aug 2023 20:51:25 +0200 Subject: [PATCH 06/15] server/upsd.c, NEWS, UPGRADING: setuptcp(): when asked to LISTEN on IPv6 addresses, try to disable IPv4-mapping support [#2012] Signed-off-by: Jim Klimov --- NEWS | 5 +++++ UPGRADING | 8 ++++++++ server/upsd.c | 11 +++++++++++ 3 files changed, 24 insertions(+) diff --git a/NEWS b/NEWS index 2a99a07bfc..5b21a56099 100644 --- a/NEWS +++ b/NEWS @@ -282,6 +282,11 @@ as part of https://github.com/networkupstools/nut/issues/1410 solution. (the last listed address was applied first), which was counter-intuitive and fixed for this release [#2012] + - The `upsd` configured to listen on IPv6 addresses should handle only + IPv6 (and not IPv4-mappings) to avoid surprises and insecurity; it + would warn if a hostname resolves to several addresses (and would only + listen on the first hit, as before in such cases) [#2012] + - sstate (server state, e.g. upsd) should now "PING" drivers also if they last reported themselves as "stale" (and might later crash) so their connections would be terminated if really no longer active [#1626] diff --git a/UPGRADING b/UPGRADING index 72a1de5de5..03471a0660 100644 --- a/UPGRADING +++ b/UPGRADING @@ -71,6 +71,14 @@ Changes from 2.8.0 to 2.8.1 order (e.g. to prioritize IPv6 vs IPv4 listeners), configuration changes may be needed. [#2012] +- The `upsd` configured to listen on IPv6 addresses should handle only + IPv6 (and not IPv4-mappings like it might have done before) to avoid + surprises and insecurity -- if user configurations somehow relied on + this dual support, configuration changes may be needed to specify both + desired IP addresses. Note that the daemon logs would warn if a hostname + resolves to several addresses (and would only listen on the first hit, + as it did before in such cases) [#2012] + - Added support for `make sockdebug` for easier developer access to the tool; also if `configure --with-dev` is in effect, it would now be installed to the configured `libexec` location. A man page was also added. [#1936] diff --git a/server/upsd.c b/server/upsd.c index eb99df810c..23ec4df4e8 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -303,6 +303,17 @@ static void setuptcp(stype_t *server) fatal_with_errno(EXIT_FAILURE, "setuptcp: setsockopt"); } + /* Ordinarily we request that IPv6 listeners handle only IPv6. + * TOTHINK: Does any platform need `#ifdef IPV6_V6ONLY` given + * that we apparently already have AF_INET6 OS support everywhere? + */ + if (ai->ai_family == AF_INET6) { + if (setsockopt(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&one, sizeof(one)) != 0) { + upsdebug_with_errno(3, "setuptcp: setsockopt IPV6_V6ONLY"); + /* ack, ignore */ + } + } + if (bind(sock_fd, ai->ai_addr, ai->ai_addrlen) < 0) { upsdebug_with_errno(3, "setuptcp: bind"); close(sock_fd); From a904962dfe7075697f42bfb883f97ccc1b5815a7 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 5 Aug 2023 22:04:40 +0200 Subject: [PATCH 07/15] server/upsd.c and docs/config examples: handle "LISTEN *" deterministically (for both IPv4 and IPv6 if we can) [#2012] Signed-off-by: Jim Klimov --- NEWS | 6 ++ UPGRADING | 6 ++ conf/upsd.conf.sample | 12 +++ docs/config-notes.txt | 6 ++ docs/man/upsd.conf.txt | 14 ++- docs/security.txt | 6 ++ scripts/augeas/nutupsdconf.aug.in | 10 +- server/upsd.c | 173 +++++++++++++++++++++++++++++- 8 files changed, 227 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index 5b21a56099..1525607348 100644 --- a/NEWS +++ b/NEWS @@ -287,6 +287,12 @@ as part of https://github.com/networkupstools/nut/issues/1410 solution. would warn if a hostname resolves to several addresses (and would only listen on the first hit, as before in such cases) [#2012] + - A definitive behavior for `LISTEN *` directives became specified, to try + handling both IPv4 and IPv6 "any" address (subject to `upsd` CLI options + to only choose one, and to OS abilities); this use-case may be practically + implemented as a single IPv6 socket on systems with enabled "dual-stack" + support or as two separate listening sockets [#2012] + - sstate (server state, e.g. upsd) should now "PING" drivers also if they last reported themselves as "stale" (and might later crash) so their connections would be terminated if really no longer active [#1626] diff --git a/UPGRADING b/UPGRADING index 03471a0660..7a92aae3d4 100644 --- a/UPGRADING +++ b/UPGRADING @@ -79,6 +79,12 @@ Changes from 2.8.0 to 2.8.1 resolves to several addresses (and would only listen on the first hit, as it did before in such cases) [#2012] +- A definitive behavior for `LISTEN *` directives became specified, to try + handling both IPv4 and IPv6 "any" address (subject to `upsd` CLI options + to only choose one, and to OS abilities); this use-case may be practically + implemented as a single IPv6 socket on systems with enabled "dual-stack" + support or as two separate listening sockets [#2012] + - Added support for `make sockdebug` for easier developer access to the tool; also if `configure --with-dev` is in effect, it would now be installed to the configured `libexec` location. A man page was also added. [#1936] diff --git a/conf/upsd.conf.sample b/conf/upsd.conf.sample index 8c8f95a634..29ed500127 100644 --- a/conf/upsd.conf.sample +++ b/conf/upsd.conf.sample @@ -64,6 +64,12 @@ # Note that it is not true for Windows platforms. You shouldn't use IPv6 in # your configuration files unless you have IPv6 installed. # +# As a special case, `LISTEN * ` (with an asterisk) would try +# to listen on "ANY" IP address for both IPv4 (0.0.0.0) and IPv6 (::0), +# subject to `upsd` command-line arguments or OS/kernel configuration. +# If the system supports "dual-stack" mode (IPv4-mapped IPv6) per RFC-3493 +# then there would be one listening socket for both address types. +# # One or more LISTEN statements give the IP address (or name that # resolves to such an address) for upsd to listen on, optionally with # a port number. @@ -74,6 +80,12 @@ # # This will only be read at startup of upsd. If you make changes here, # you'll need to restart upsd, as reload will have no effect. +# +# Please note that older NUT releases could have been using the "dual-stack" +# mode, if provided by the system. Current versions (since NUT v2.8.1 release) +# try to restrict their listening sockets to only support IPv6 addresses and +# so avoid IPv4-mapped mode, except when handling the special `LISTEN * ` +# directive. # ======================================================================= # MAXCONN diff --git a/docs/config-notes.txt b/docs/config-notes.txt index 5f9f7a236d..ec24e1515f 100644 --- a/docs/config-notes.txt +++ b/docs/config-notes.txt @@ -295,6 +295,12 @@ want `upsd` to listen on for connections, optionally with a port number. LISTEN 127.0.0.1 3493 LISTEN ::1 3493 +As a special case, `LISTEN * ` (with an asterisk) would try +to listen on "ANY" IP address for both IPv4 (`0.0.0.0`) and IPv6 (`::0`), +subject to `upsd` command-line arguments or OS/kernel configuration. +If the system supports "dual-stack" mode (IPv4-mapped IPv6) per RFC-3493 +then there would be one listening socket for both address types. + NOTE: Refer to the NUT user manual <> for information on how to access and secure upsd clients connections. diff --git a/docs/man/upsd.conf.txt b/docs/man/upsd.conf.txt index 8287e54a56..57618b9b0b 100644 --- a/docs/man/upsd.conf.txt +++ b/docs/man/upsd.conf.txt @@ -70,8 +70,12 @@ Multiple LISTEN addresses may be specified. The default is to bind to compiled in). + To listen on all available interfaces, you may also use '0.0.0.0' for IPv4 and -and '::' for IPv6. - +and '::' for IPv6. As a special case, `LISTEN * ` (with an asterisk) +would try to listen on both IPv4 (`0.0.0.0`) and IPv6 (`::0`) wild-card IP +addresses, subject to `upsd` command-line arguments or OS/kernel configuration. +If the system supports "dual-stack" mode (IPv4-mapped IPv6) per RFC-3493 +then there would be one listening socket for both address types. ++ LISTEN 127.0.0.1 LISTEN 192.168.50.1 LISTEN myhostname.mydomain @@ -80,6 +84,12 @@ and '::' for IPv6. + This parameter will only be read at startup. You'll need to restart (rather than reload) upsd to apply any changes made here. ++ +Please note that older NUT releases could have been using the "dual-stack" +mode, if provided by the system. Current versions (since NUT v2.8.1 release) +try to restrict their listening sockets to only support IPv6 addresses and +so avoid IPv4-mapped mode, except when handling the special `LISTEN * ` +directive. "MAXCONN 'connections'":: diff --git a/docs/security.txt b/docs/security.txt index 51a50c5fcc..7d69aa0bf3 100644 --- a/docs/security.txt +++ b/docs/security.txt @@ -234,6 +234,12 @@ compiled in). LISTEN ::1 LISTEN 2001:0db8:1234:08d3:1319:8a2e:0370:7344 +As a special case, `LISTEN * ` (with an asterisk) would try +to listen on "ANY" IP address for both IPv4 (`0.0.0.0`) and IPv6 (`::0`), +subject to `upsd` command-line arguments or OS/kernel configuration. +If the system supports "dual-stack" mode (IPv4-mapped IPv6) per RFC-3493 +then there would be one listening socket for both address types. + This parameter will only be read at startup. You'll need to restart (rather than reload) `upsd` to apply any changes made here. diff --git a/scripts/augeas/nutupsdconf.aug.in b/scripts/augeas/nutupsdconf.aug.in index 35bf896f09..dff4fef435 100644 --- a/scripts/augeas/nutupsdconf.aug.in +++ b/scripts/augeas/nutupsdconf.aug.in @@ -55,8 +55,14 @@ let upsd_certfile = [ opt_spc . key "CERTFILE" . sep_spc . store path . eol ] * ALLOW_NO_DEVICE Boolean * STATEPATH path * LISTEN interface port - * Multiple LISTEN addresses may be specified. The default is to bind to 0.0.0.0 if no LISTEN addresses are specified. - * LISTEN 127.0.0.1 LISTEN 192.168.50.1 LISTEN ::1 LISTEN 2001:0db8:1234:08d3:1319:8a2e:0370:7344 + * Multiple lines each with one LISTEN address (or host name) and an optional + * port may be specified. The default is to bind to IPv4 and IPv6 "localhost" + * addresses (subject to CLI options `-4` or `-6` constraining IP version or + * OS kernel/configuration support), if no LISTEN addresses are specified. + * LISTEN 127.0.0.1 + * LISTEN 192.168.50.1 + * LISTEN ::1 + * LISTEN 2001:0db8:1234:08d3:1319:8a2e:0370:7344 * *************************************************************************) let upsd_other = upsd_maxage | upsd_trackingdelay | upsd_allow_no_device | upsd_statepath | upsd_listen_list | upsd_maxconn | upsd_certfile diff --git a/server/upsd.c b/server/upsd.c index 23ec4df4e8..3d1da59340 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -267,6 +267,8 @@ static void stype_free(stype_t *server) /* create a listening socket for tcp connections */ static void setuptcp(stype_t *server) { + /* Well, currently it is more a request than requirement... */ + static int require_IPV6_V6ONLY = 1; #ifdef WIN32 WSADATA WSAdata; WSAStartup(2,&WSAdata); @@ -275,8 +277,169 @@ static void setuptcp(stype_t *server) struct addrinfo hints, *res, *ai; int v = 0, one = 1; + if (VALID_FD_SOCK(server->sock_fd)) { + /* Alredy bound, e.g. thanks to 'LISTEN *' handling and injection + * into the list we loop over */ + upsdebugx(6, "setuptcp: SKIP bind to %s port %s: entry already initialized", + server->addr, server->port); + return; + } + upsdebugx(3, "setuptcp: try to bind to %s port %s", server->addr, server->port); + /* Special handling for `LISTEN * ` directive with literal asterisk: + * on systems with RFC-3493 (no relation!) support for "IPv4-mapped addresses" + * it suffices to LISTEN on "::" (aka "::0" or "0:0:0:0:0:0:0:0") and also + * get an IPv4 any-address listener. More so, they would conflict and + * listening on one such socket precludes listening on the other. On other + * systems (or with disabled mapping so IPv6 means IPv6 only) we need both. + * So we jump through some hoops: + * * Try to get IPv4 any-address, just to know if it is available right now; + * * Free it and try to get IPv6 any-address, and try to get again that + * IPv4 any-address (IFF it was available before but is not available now - + * not a problem). + * * Remember the entries used, to release later. + * For more details see https://github.com/networkupstools/nut/issues/2012 + */ + if (!strcmp(server->addr, "*")) { + stype_t *serverAnyV4 = NULL, *serverAnyV6 = NULL; + int canhaveAnyV4 = 0; + int canhaveAnyV6 = 0; + + /* For this use-case, we allow IPv6 to handle IPv4 if it can */ + int old_require_IPV6_V6ONLY = require_IPV6_V6ONLY; + require_IPV6_V6ONLY = (opt_af == AF_INET6); + + /* Note: default opt_af==AF_UNSPEC so not constrained to only one protocol */ + if (opt_af != AF_INET6) { + /* Not constrained to IPv6 */ + upsdebugx(1, "%s: handling 'LISTEN * %s' with IPv4 any-address support", + __func__, server->port); + serverAnyV4 = xcalloc(1, sizeof(*serverAnyV4)); + serverAnyV4->addr = xstrdup("0.0.0.0"); + serverAnyV4->port = xstrdup(server->port); + serverAnyV4->sock_fd = ERROR_FD_SOCK; + serverAnyV4->next = NULL; + } + + if (opt_af != AF_INET) { + /* Not constrained to IPv4 */ + upsdebugx(1, "%s: handling 'LISTEN * %s' with IPv6 any-address support", + __func__, server->port); + serverAnyV6 = xcalloc(1, sizeof(*serverAnyV6)); + serverAnyV6->addr = xstrdup("::0"); + serverAnyV6->port = xstrdup(server->port); + serverAnyV6->sock_fd = ERROR_FD_SOCK; + serverAnyV6->next = NULL; + } + + if (serverAnyV4) { + /* First pass to just check if we CAN have this listener now */ + setuptcp(serverAnyV4); + if (serverAnyV6) { + if (VALID_FD_SOCK(serverAnyV4->sock_fd)) { + upsdebugx(3, + "%s: Could bind to %s:%s trying to handle a 'LISTEN *' directive" + "; will release it for now to try IPv6", + __func__, serverAnyV4->addr, serverAnyV4->port); + canhaveAnyV4 = 1; + close(serverAnyV4->sock_fd); + serverAnyV4->sock_fd = ERROR_FD_SOCK; + /* Let the system know about the change: */ + /* usleep(100); */ + } else { + upsdebugx(3, + "%s: Could not bind to %s:%s trying to handle a 'LISTEN *' directive", + __func__, serverAnyV4->addr, serverAnyV4->port); + } + } /* else: just keep it, all done */ + } + + if (serverAnyV6) { + setuptcp(serverAnyV6); + if (VALID_FD_SOCK(serverAnyV6->sock_fd)) { + canhaveAnyV6 = 1; + } else { + upsdebugx(3, + "%s: Could not bind to %s:%s trying to handle a 'LISTEN *' directive", + __func__, serverAnyV6->addr, serverAnyV6->port); + } + + if (serverAnyV4 && canhaveAnyV4) { + /* Second pass to get this listener if we can (no IPv4-mapped IPv6 + * support was in force on this platform or its configuration) */ + upsdebugx(3, "%s: try taking IPv4 'ANY' again " + "(if dual-stack IPv6 'ANY' did not grab it)", __func__); + setuptcp(serverAnyV4); + if (INVALID_FD_SOCK(serverAnyV4->sock_fd)) { + upsdebugx(3, + "%s: Could not bind to IPv4 %s:%s after trying to bind to IPv6: " + "assuming dual-stack support on this system", + __func__, serverAnyV4->addr, serverAnyV4->port); + canhaveAnyV4 = 0; + } + } + } + + if (!canhaveAnyV4 && !canhaveAnyV6) { + fatalx(EXIT_FAILURE, + "Handling of 'LISTEN * %s' directive failed to bind to 'ANY' address", + server->port); + } + + /* Finalize our findings and reset to normal operation + * Note that at least one of these addresses is usable + * and we keep it (and replace original "server" entry + * keeping its place in the list). + */ + free(server->addr); + free(server->port); + if (canhaveAnyV4) { + upsdebugx(3, "%s: remembering IPv4 'ANY' instead of 'LISTEN *'", __func__); + server->addr = serverAnyV4->addr; + server->port = serverAnyV4->port; + server->sock_fd = serverAnyV4->sock_fd; + /* ...and keep whatever server->next there was */ + + /* Free the ghost, all needed info was relocated */ + free(serverAnyV4); + } else { + if (serverAnyV4) { + /* Free any contents there were too */ + stype_free(serverAnyV4); + } + } + serverAnyV4 = NULL; + + if (canhaveAnyV6) { + if (canhaveAnyV4) { + /* "server" already populated by excerpts from V4, attach to it */ + upsdebugx(3, "%s: also remembering IPv6 'ANY' instead of 'LISTEN *'", __func__); + serverAnyV6->next = server->next; + server->next = serverAnyV6; + } else { + /* Only retain V6 info */ + upsdebugx(3, "%s: remembering IPv6 'ANY' instead of 'LISTEN *'", __func__); + server->addr = serverAnyV6->addr; + server->port = serverAnyV6->port; + server->sock_fd = serverAnyV6->sock_fd; + /* ...and keep whatever server->next there was */ + + /* Free the ghost, all needed info was relocated */ + free(serverAnyV6); + } + } else { + if (serverAnyV6) { + /* Free any contents there were too */ + stype_free(serverAnyV6); + } + } + serverAnyV6 = NULL; + + require_IPV6_V6ONLY = old_require_IPV6_V6ONLY; + return; + } + memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_PASSIVE; hints.ai_family = opt_af; @@ -303,11 +466,17 @@ static void setuptcp(stype_t *server) fatal_with_errno(EXIT_FAILURE, "setuptcp: setsockopt"); } - /* Ordinarily we request that IPv6 listeners handle only IPv6. + /* Ordinarily we request that IPv6 listeners handle only IPv6 + * (except when we handle `LISTEN *` as detailed above). + * Note we specifically try to ensure this when CLI requires + * IPv6-only behavior (even if we want "any" addr for `LISTEN *`). * TOTHINK: Does any platform need `#ifdef IPV6_V6ONLY` given * that we apparently already have AF_INET6 OS support everywhere? + * TOTHINK: Do we want to setsockopt() to explicitly allow dual-stack + * (perhaps counteracting OS default or customized configuration) + * when handling `LISTEN *` use-cases? */ - if (ai->ai_family == AF_INET6) { + if (ai->ai_family == AF_INET6 && (require_IPV6_V6ONLY || (opt_af == AF_INET6))) { if (setsockopt(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&one, sizeof(one)) != 0) { upsdebug_with_errno(3, "setuptcp: setsockopt IPV6_V6ONLY"); /* ack, ignore */ From 7e6c76e83c7a974de0fea95a411726f98338fbbc Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 13 Aug 2023 21:10:46 +0200 Subject: [PATCH 08/15] Revise doc changes about LISTEN * support per PR #2013 discussion Notably: * move from "would" to "will" verbiage; * specify IPv6 listening first, IPv4 next, as it goes in code; * lean towards trying to listen on two sockets separately always (unless the OS refuses to avoid IPv4-mapped IPv6 addressing). Signed-off-by: Jim Klimov --- NEWS | 11 +++++++---- UPGRADING | 16 ++++++++++------ conf/upsd.conf.sample | 21 +++++++++++---------- docs/config-notes.txt | 11 ++++++----- docs/man/upsd.conf.txt | 28 +++++++++++++++------------- docs/security.txt | 11 ++++++----- scripts/augeas/nutupsdconf.aug.in | 4 ++-- 7 files changed, 57 insertions(+), 45 deletions(-) diff --git a/NEWS b/NEWS index 1525607348..d7a9d571ac 100644 --- a/NEWS +++ b/NEWS @@ -284,14 +284,17 @@ as part of https://github.com/networkupstools/nut/issues/1410 solution. - The `upsd` configured to listen on IPv6 addresses should handle only IPv6 (and not IPv4-mappings) to avoid surprises and insecurity; it - would warn if a hostname resolves to several addresses (and would only + will now warn if a host name resolves to several addresses (and will only listen on the first hit, as before in such cases) [#2012] - A definitive behavior for `LISTEN *` directives became specified, to try handling both IPv4 and IPv6 "any" address (subject to `upsd` CLI options - to only choose one, and to OS abilities); this use-case may be practically - implemented as a single IPv6 socket on systems with enabled "dual-stack" - support or as two separate listening sockets [#2012] + to only choose one, and to OS abilities). When both address families are + enabled, the `upsd` data server will first try to open an IPv6 socket + asking for disabled IPv4-mapped IPv6 address support (if the OS honors + that), and then an IPv4 socket (which may fail if the IPv6 socket already + covers it anyway); in other words, you can end up with one or two separate + listening sockets [#2012] - sstate (server state, e.g. upsd) should now "PING" drivers also if they last reported themselves as "stale" (and might later crash) so their diff --git a/UPGRADING b/UPGRADING index 7a92aae3d4..339ff59900 100644 --- a/UPGRADING +++ b/UPGRADING @@ -75,15 +75,19 @@ Changes from 2.8.0 to 2.8.1 IPv6 (and not IPv4-mappings like it might have done before) to avoid surprises and insecurity -- if user configurations somehow relied on this dual support, configuration changes may be needed to specify both - desired IP addresses. Note that the daemon logs would warn if a hostname - resolves to several addresses (and would only listen on the first hit, - as it did before in such cases) [#2012] + desired IP addresses. Note that the daemon logs will now warn if a + host name resolves to several addresses (and will only listen on the + first hit, as it did before in such cases) [#2012] - A definitive behavior for `LISTEN *` directives became specified, to try handling both IPv4 and IPv6 "any" address (subject to `upsd` CLI options - to only choose one, and to OS abilities); this use-case may be practically - implemented as a single IPv6 socket on systems with enabled "dual-stack" - support or as two separate listening sockets [#2012] + to only choose one, and to OS abilities). This use-case may be practically + implemented as a single IPv6 socket on systems with enabled and required + IPv4-mapped IPv6 address support, or as two separate listening sockets - + logged messages to this effect (e.g. inability to listen on IPv4 after + opening IPv6) are expected on some platforms. End-users may also want to + reconfigure their `upsd.conf` files to remove some now-redundant `LISTEN` + lines. [#2012] - Added support for `make sockdebug` for easier developer access to the tool; also if `configure --with-dev` is in effect, it would now be installed to diff --git a/conf/upsd.conf.sample b/conf/upsd.conf.sample index 29ed500127..277e58417f 100644 --- a/conf/upsd.conf.sample +++ b/conf/upsd.conf.sample @@ -64,11 +64,12 @@ # Note that it is not true for Windows platforms. You shouldn't use IPv6 in # your configuration files unless you have IPv6 installed. # -# As a special case, `LISTEN * ` (with an asterisk) would try -# to listen on "ANY" IP address for both IPv4 (0.0.0.0) and IPv6 (::0), -# subject to `upsd` command-line arguments or OS/kernel configuration. -# If the system supports "dual-stack" mode (IPv4-mapped IPv6) per RFC-3493 -# then there would be one listening socket for both address types. +# As a special case, `LISTEN * ` (with an asterisk) will try +# to listen on "ANY" IP address for both IPv6 (::0) and IPv4 (0.0.0.0), +# subject to `upsd` command-line arguments, or system configuration. +# Note that if the system supports IPv4-mapped IPv6 addressing per RFC-3493, +# and does not allow to disable this mode, then there may be one listening +# socket to handle both address families. # # One or more LISTEN statements give the IP address (or name that # resolves to such an address) for upsd to listen on, optionally with @@ -81,11 +82,11 @@ # This will only be read at startup of upsd. If you make changes here, # you'll need to restart upsd, as reload will have no effect. # -# Please note that older NUT releases could have been using the "dual-stack" -# mode, if provided by the system. Current versions (since NUT v2.8.1 release) -# try to restrict their listening sockets to only support IPv6 addresses and -# so avoid IPv4-mapped mode, except when handling the special `LISTEN * ` -# directive. +# Please note that older NUT releases could have been using the IPv4-mapped +# IPv6 addressing (sometimes also known as "dual-stack") mode, if provided +# by the system. Current versions (since NUT v2.8.1 release) explicitly try +# to restrict their listening sockets to only support one address family on +# each socket, and so avoid IPv4-mapped mode where possible. # ======================================================================= # MAXCONN diff --git a/docs/config-notes.txt b/docs/config-notes.txt index ec24e1515f..8ed3269fd9 100644 --- a/docs/config-notes.txt +++ b/docs/config-notes.txt @@ -295,11 +295,12 @@ want `upsd` to listen on for connections, optionally with a port number. LISTEN 127.0.0.1 3493 LISTEN ::1 3493 -As a special case, `LISTEN * ` (with an asterisk) would try -to listen on "ANY" IP address for both IPv4 (`0.0.0.0`) and IPv6 (`::0`), -subject to `upsd` command-line arguments or OS/kernel configuration. -If the system supports "dual-stack" mode (IPv4-mapped IPv6) per RFC-3493 -then there would be one listening socket for both address types. +As a special case, `LISTEN * ` (with an asterisk) will try to +listen on "ANY" IP address for both and IPv6 (`::0`) and IPv4 (`0.0.0.0`), +subject to `upsd` command-line arguments, or system configuration or support. +Note that if the system supports IPv4-mapped IPv6 addressing per RFC-3493, +and does not allow to disable this mode, then there may be one listening +socket to handle both address families. NOTE: Refer to the NUT user manual <> for information on how to access and secure upsd clients connections. diff --git a/docs/man/upsd.conf.txt b/docs/man/upsd.conf.txt index 57618b9b0b..af8582a700 100644 --- a/docs/man/upsd.conf.txt +++ b/docs/man/upsd.conf.txt @@ -66,15 +66,17 @@ compiled into the code. This overrides any value you may have set with upsd will listen on port 3493 for this interface. + Multiple LISTEN addresses may be specified. The default is to bind to -127.0.0.1 if no LISTEN addresses are specified (and ::1 if IPv6 support is -compiled in). +`127.0.0.1` if no LISTEN addresses are specified (and also `::1` if IPv6 +support is compiled in). + -To listen on all available interfaces, you may also use '0.0.0.0' for IPv4 and -and '::' for IPv6. As a special case, `LISTEN * ` (with an asterisk) -would try to listen on both IPv4 (`0.0.0.0`) and IPv6 (`::0`) wild-card IP -addresses, subject to `upsd` command-line arguments or OS/kernel configuration. -If the system supports "dual-stack" mode (IPv4-mapped IPv6) per RFC-3493 -then there would be one listening socket for both address types. +To listen on all available interfaces and configured IP addresses of your +system, you may also use `::` for IPv6 and `0.0.0.0` for IPv4, respectively. +As a special case, a single `LISTEN * ` directive (with an asterisk) will +try to listen on both IPv6 (`::0`) and IPv4 (`0.0.0.0`) wild-card IP addresses, +subject to `upsd` command-line arguments or system configuration. +Note that if the system supports IPv4-mapped IPv6 addressing per RFC-3493, +and does not allow to disable this mode, then there may be one listening +socket to handle both address families. + LISTEN 127.0.0.1 LISTEN 192.168.50.1 @@ -85,11 +87,11 @@ then there would be one listening socket for both address types. This parameter will only be read at startup. You'll need to restart (rather than reload) upsd to apply any changes made here. + -Please note that older NUT releases could have been using the "dual-stack" -mode, if provided by the system. Current versions (since NUT v2.8.1 release) -try to restrict their listening sockets to only support IPv6 addresses and -so avoid IPv4-mapped mode, except when handling the special `LISTEN * ` -directive. +Please note that older NUT releases could have been using the IPv4-mapped +IPv6 addressing (sometimes also known as "dual-stack") mode, if provided +by the system. Current versions (since NUT v2.8.1 release) explicitly try +to restrict their listening sockets to only support one address family on +each socket, and so avoid IPv4-mapped mode where possible. "MAXCONN 'connections'":: diff --git a/docs/security.txt b/docs/security.txt index 7d69aa0bf3..a6594885fd 100644 --- a/docs/security.txt +++ b/docs/security.txt @@ -234,11 +234,12 @@ compiled in). LISTEN ::1 LISTEN 2001:0db8:1234:08d3:1319:8a2e:0370:7344 -As a special case, `LISTEN * ` (with an asterisk) would try -to listen on "ANY" IP address for both IPv4 (`0.0.0.0`) and IPv6 (`::0`), -subject to `upsd` command-line arguments or OS/kernel configuration. -If the system supports "dual-stack" mode (IPv4-mapped IPv6) per RFC-3493 -then there would be one listening socket for both address types. +As a special case, `LISTEN * ` (with an asterisk) will try to +listen on "ANY" IP address for both IPv6 (`::0`) and IPv4 (`0.0.0.0`), +subject to `upsd` command-line arguments, or system configuration or support. +Note that if the system supports IPv4-mapped IPv6 addressing per RFC-3493, +and does not allow to disable this mode, then there may be one listening +socket to handle both address families. This parameter will only be read at startup. You'll need to restart (rather than reload) `upsd` to apply any changes made here. diff --git a/scripts/augeas/nutupsdconf.aug.in b/scripts/augeas/nutupsdconf.aug.in index dff4fef435..1592048387 100644 --- a/scripts/augeas/nutupsdconf.aug.in +++ b/scripts/augeas/nutupsdconf.aug.in @@ -57,8 +57,8 @@ let upsd_certfile = [ opt_spc . key "CERTFILE" . sep_spc . store path . eol ] * LISTEN interface port * Multiple lines each with one LISTEN address (or host name) and an optional * port may be specified. The default is to bind to IPv4 and IPv6 "localhost" - * addresses (subject to CLI options `-4` or `-6` constraining IP version or - * OS kernel/configuration support), if no LISTEN addresses are specified. + * addresses (subject to CLI options `-4` or `-6` constraining IP version, + * or system configuration or support), if no LISTEN addresses are specified. * LISTEN 127.0.0.1 * LISTEN 192.168.50.1 * LISTEN ::1 From 7d04e37b1951b2c4a870122952adf44f47e8fad4 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 14 Aug 2023 13:45:14 +0200 Subject: [PATCH 09/15] server/upsd.c: simplify `LISTEN *` handling to not fiddle with optionality of `require_IPV6_V6ONLY` for one use-case [#2013 review, #2012] Signed-off-by: Jim Klimov --- server/upsd.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/server/upsd.c b/server/upsd.c index 3d1da59340..87a4ec3cf9 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -267,8 +267,6 @@ static void stype_free(stype_t *server) /* create a listening socket for tcp connections */ static void setuptcp(stype_t *server) { - /* Well, currently it is more a request than requirement... */ - static int require_IPV6_V6ONLY = 1; #ifdef WIN32 WSADATA WSAdata; WSAStartup(2,&WSAdata); @@ -306,10 +304,6 @@ static void setuptcp(stype_t *server) int canhaveAnyV4 = 0; int canhaveAnyV6 = 0; - /* For this use-case, we allow IPv6 to handle IPv4 if it can */ - int old_require_IPV6_V6ONLY = require_IPV6_V6ONLY; - require_IPV6_V6ONLY = (opt_af == AF_INET6); - /* Note: default opt_af==AF_UNSPEC so not constrained to only one protocol */ if (opt_af != AF_INET6) { /* Not constrained to IPv6 */ @@ -436,7 +430,6 @@ static void setuptcp(stype_t *server) } serverAnyV6 = NULL; - require_IPV6_V6ONLY = old_require_IPV6_V6ONLY; return; } @@ -467,16 +460,11 @@ static void setuptcp(stype_t *server) } /* Ordinarily we request that IPv6 listeners handle only IPv6 - * (except when we handle `LISTEN *` as detailed above). - * Note we specifically try to ensure this when CLI requires - * IPv6-only behavior (even if we want "any" addr for `LISTEN *`). + * and not IPv4 mapped addresses - if the OS would honour that. * TOTHINK: Does any platform need `#ifdef IPV6_V6ONLY` given * that we apparently already have AF_INET6 OS support everywhere? - * TOTHINK: Do we want to setsockopt() to explicitly allow dual-stack - * (perhaps counteracting OS default or customized configuration) - * when handling `LISTEN *` use-cases? */ - if (ai->ai_family == AF_INET6 && (require_IPV6_V6ONLY || (opt_af == AF_INET6))) { + if (ai->ai_family == AF_INET6) { if (setsockopt(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&one, sizeof(one)) != 0) { upsdebug_with_errno(3, "setuptcp: setsockopt IPV6_V6ONLY"); /* ack, ignore */ From a8b5896ab86e6e801766de4345f1c84407f7997f Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 14 Aug 2023 14:05:54 +0200 Subject: [PATCH 10/15] server/upsd.c: setuptcp(): drop commented-away optional sleep() [#2012] Signed-off-by: Jim Klimov --- server/upsd.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/upsd.c b/server/upsd.c index 87a4ec3cf9..c11257135c 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -339,8 +339,6 @@ static void setuptcp(stype_t *server) canhaveAnyV4 = 1; close(serverAnyV4->sock_fd); serverAnyV4->sock_fd = ERROR_FD_SOCK; - /* Let the system know about the change: */ - /* usleep(100); */ } else { upsdebugx(3, "%s: Could not bind to %s:%s trying to handle a 'LISTEN *' directive", From af53ec5384433ceb07a5b45b5cc14d0bf6aaa572 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 14 Aug 2023 14:16:24 +0200 Subject: [PATCH 11/15] server/upsd.c: setuptcp(): drop the trickery to get IPv4 first just to see if we can, then release it, then get IPv6 and then IPv4 again [#2013 review for #2012] Signed-off-by: Jim Klimov --- server/upsd.c | 56 +++++++++++++++++++-------------------------------- 1 file changed, 21 insertions(+), 35 deletions(-) diff --git a/server/upsd.c b/server/upsd.c index c11257135c..c03da60f53 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -301,8 +301,7 @@ static void setuptcp(stype_t *server) */ if (!strcmp(server->addr, "*")) { stype_t *serverAnyV4 = NULL, *serverAnyV6 = NULL; - int canhaveAnyV4 = 0; - int canhaveAnyV6 = 0; + int canhaveAnyV4 = 0, canhaveAnyV6 = 0; /* Note: default opt_af==AF_UNSPEC so not constrained to only one protocol */ if (opt_af != AF_INET6) { @@ -327,26 +326,6 @@ static void setuptcp(stype_t *server) serverAnyV6->next = NULL; } - if (serverAnyV4) { - /* First pass to just check if we CAN have this listener now */ - setuptcp(serverAnyV4); - if (serverAnyV6) { - if (VALID_FD_SOCK(serverAnyV4->sock_fd)) { - upsdebugx(3, - "%s: Could bind to %s:%s trying to handle a 'LISTEN *' directive" - "; will release it for now to try IPv6", - __func__, serverAnyV4->addr, serverAnyV4->port); - canhaveAnyV4 = 1; - close(serverAnyV4->sock_fd); - serverAnyV4->sock_fd = ERROR_FD_SOCK; - } else { - upsdebugx(3, - "%s: Could not bind to %s:%s trying to handle a 'LISTEN *' directive", - __func__, serverAnyV4->addr, serverAnyV4->port); - } - } /* else: just keep it, all done */ - } - if (serverAnyV6) { setuptcp(serverAnyV6); if (VALID_FD_SOCK(serverAnyV6->sock_fd)) { @@ -356,20 +335,27 @@ static void setuptcp(stype_t *server) "%s: Could not bind to %s:%s trying to handle a 'LISTEN *' directive", __func__, serverAnyV6->addr, serverAnyV6->port); } + } - if (serverAnyV4 && canhaveAnyV4) { - /* Second pass to get this listener if we can (no IPv4-mapped IPv6 - * support was in force on this platform or its configuration) */ - upsdebugx(3, "%s: try taking IPv4 'ANY' again " - "(if dual-stack IPv6 'ANY' did not grab it)", __func__); - setuptcp(serverAnyV4); - if (INVALID_FD_SOCK(serverAnyV4->sock_fd)) { - upsdebugx(3, - "%s: Could not bind to IPv4 %s:%s after trying to bind to IPv6: " - "assuming dual-stack support on this system", - __func__, serverAnyV4->addr, serverAnyV4->port); - canhaveAnyV4 = 0; - } + if (serverAnyV4) { + /* Try to get this listener if we can (no IPv4-mapped + * IPv6 support was in force on this platform or its + * configuration in some way that setsockopt(IPV6_V6ONLY) + * failed to cancel). + */ + upsdebugx(3, "%s: try taking IPv4 'ANY'%s", + __func__, + serverAnyV6 ? " (if dual-stack IPv6 'ANY' did not grab it)" : ""); + setuptcp(serverAnyV4); + if (VALID_FD_SOCK(serverAnyV4->sock_fd)) { + canhaveAnyV4 = 1; + } else { + upsdebugx(3, + "%s: Could not bind to IPv4 %s:%s%s", + __func__, serverAnyV4->addr, serverAnyV4->port, + serverAnyV6 ? (" after trying to bind to IPv6: " + "assuming dual-stack support on this " + "system could not be disabled") : ""); } } From dec2488d8de8332290f2ec3d24280ac07c747b24 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 14 Aug 2023 14:33:47 +0200 Subject: [PATCH 12/15] NEWS, UPGRADING: finish full sentences with a period [#2012] Signed-off-by: Jim Klimov --- NEWS | 2 +- UPGRADING | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index d7a9d571ac..fe8dff5db1 100644 --- a/NEWS +++ b/NEWS @@ -294,7 +294,7 @@ as part of https://github.com/networkupstools/nut/issues/1410 solution. asking for disabled IPv4-mapped IPv6 address support (if the OS honors that), and then an IPv4 socket (which may fail if the IPv6 socket already covers it anyway); in other words, you can end up with one or two separate - listening sockets [#2012] + listening sockets. [#2012] - sstate (server state, e.g. upsd) should now "PING" drivers also if they last reported themselves as "stale" (and might later crash) so their diff --git a/UPGRADING b/UPGRADING index 339ff59900..d29edea18b 100644 --- a/UPGRADING +++ b/UPGRADING @@ -77,7 +77,7 @@ Changes from 2.8.0 to 2.8.1 this dual support, configuration changes may be needed to specify both desired IP addresses. Note that the daemon logs will now warn if a host name resolves to several addresses (and will only listen on the - first hit, as it did before in such cases) [#2012] + first hit, as it did before in such cases). [#2012] - A definitive behavior for `LISTEN *` directives became specified, to try handling both IPv4 and IPv6 "any" address (subject to `upsd` CLI options From df7961be6909c09dd70763fd041ccb86e02d5f73 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 14 Aug 2023 14:39:33 +0200 Subject: [PATCH 13/15] configure.ac: explicitly check for inet_ntop() support along with both AF_INET and AF_INET6 (we have IPv4 and IPv6 capability everywhere, right?) [#2012] Signed-off-by: Jim Klimov --- configure.ac | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index f32de9e8b0..b438855634 100644 --- a/configure.ac +++ b/configure.ac @@ -1120,7 +1120,7 @@ NUT_TYPE_SOCKLEN_T NUT_CHECK_SOCKETLIB NUT_FUNC_GETNAMEINFO_ARGTYPES -AC_CACHE_CHECK([for inet_ntop()], +AC_CACHE_CHECK([for inet_ntop() with IPv4 and IPv6 support], [ac_cv_func_inet_ntop], [AC_LANG_PUSH([C]) dnl e.g. add "-lws2_32" for mingw builds @@ -1148,7 +1148,8 @@ AC_CACHE_CHECK([for inet_ntop()], ]], [[/* const char* inet_ntop(int af, const void* src, char* dst, size_t cnt); */ char buf[128]; -printf("%s", inet_ntop(AF_INET, "1.2.3.4", buf, 10)) +printf("%s", inet_ntop(AF_INET, "1.2.3.4", buf, 10)); +printf("%s", inet_ntop(AF_INET6, "::1", buf, 10)) /* autoconf adds ";return 0;" */ ]])], [ac_cv_func_inet_ntop=yes], [ac_cv_func_inet_ntop=no] From 8daab44213f97153296fde29c364f11f407abfd2 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 14 Aug 2023 14:49:17 +0200 Subject: [PATCH 14/15] server/upsd.c: setuptcp(): update big comment about "LISTEN *" nuances to match current logic; drop GitHub reference [#2013 review for #2012] Signed-off-by: Jim Klimov --- server/upsd.c | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/server/upsd.c b/server/upsd.c index c03da60f53..91419fc72c 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -285,19 +285,23 @@ static void setuptcp(stype_t *server) upsdebugx(3, "setuptcp: try to bind to %s port %s", server->addr, server->port); - /* Special handling for `LISTEN * ` directive with literal asterisk: - * on systems with RFC-3493 (no relation!) support for "IPv4-mapped addresses" - * it suffices to LISTEN on "::" (aka "::0" or "0:0:0:0:0:0:0:0") and also - * get an IPv4 any-address listener. More so, they would conflict and - * listening on one such socket precludes listening on the other. On other - * systems (or with disabled mapping so IPv6 means IPv6 only) we need both. - * So we jump through some hoops: - * * Try to get IPv4 any-address, just to know if it is available right now; - * * Free it and try to get IPv6 any-address, and try to get again that - * IPv4 any-address (IFF it was available before but is not available now - - * not a problem). - * * Remember the entries used, to release later. - * For more details see https://github.com/networkupstools/nut/issues/2012 + /* Special handling note for `LISTEN * ` directive with the + * literal asterisk on systems with RFC-3493 (no relation!) support + * for "IPv4-mapped addresses": it is possible (and technically + * suffices) to LISTEN on "::" (aka "::0" or "0:0:0:0:0:0:0:0") and + * also get an IPv4 any-address listener automatically. More so, + * they would conflict and listening on one such socket precludes + * listening on the other. On other systems (or with disabled + * mapping so IPv6 really means "IPv6 only") we need both sockets. + * NUT asks the system for "IPv6 only" mode when listening on any + * sort of IPv6 addresses; it is however up to the system to implement + * that ability and comply with our request. + * Here we jump through some hoops: + * * Try to get IPv6 any-address (unless constrained by CLI to IPv4); + * * Try to get IPv4 any-address (unless constrained by CLI to IPv6), + * log information for the sysadmin that it might conflict with the + * IPv6 listener (IFF we have just opened one); + * * Remember the one or two linked-list entries used, to release later. */ if (!strcmp(server->addr, "*")) { stype_t *serverAnyV4 = NULL, *serverAnyV6 = NULL; From b0c97f71c7eaf8dac358396277fafcc6ba77c3d9 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 14 Aug 2023 14:50:41 +0200 Subject: [PATCH 15/15] server/upsd.c: setuptcp(): when informing about possible IPv6/IPv4 "ANY" address listener conflict, consult actual state in "canhaveAnyV6" and not the possibility via "serverAnyV6!=null" [#2013] Signed-off-by: Jim Klimov --- server/upsd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/upsd.c b/server/upsd.c index 91419fc72c..7123d625d6 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -349,7 +349,7 @@ static void setuptcp(stype_t *server) */ upsdebugx(3, "%s: try taking IPv4 'ANY'%s", __func__, - serverAnyV6 ? " (if dual-stack IPv6 'ANY' did not grab it)" : ""); + canhaveAnyV6 ? " (if dual-stack IPv6 'ANY' did not grab it)" : ""); setuptcp(serverAnyV4); if (VALID_FD_SOCK(serverAnyV4->sock_fd)) { canhaveAnyV4 = 1; @@ -357,7 +357,7 @@ static void setuptcp(stype_t *server) upsdebugx(3, "%s: Could not bind to IPv4 %s:%s%s", __func__, serverAnyV4->addr, serverAnyV4->port, - serverAnyV6 ? (" after trying to bind to IPv6: " + canhaveAnyV6 ? (" after trying to bind to IPv6: " "assuming dual-stack support on this " "system could not be disabled") : ""); }