On Sat, 19 Mar 2022 at 16:52:30 +0200, Adrian Bunk wrote: > Running suite(s): > main > opts > Cannot resolve address '127.0.0.1' port '10443': Name or service not known
This reminds me of an issue that used to affect the dbus and GLib test suites: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=952740 The short version is that if the program calls getaddrinfo with the AI_ADDRCONFIG flag (which it seems sslsplit does), in a network namespace that only has loopback addresses for a particular address family, then getaddrinfo is documented to fail to resolve for that address family. This is what AI_ADDRCONFIG is for: the intention is that if you don't have IPv6 connectivity, then AI_ADDRCONFIG stops the program from wasting time trying and failing to do an IPv6 connection, so that it can get an IPv4 connection sooner. what you might not expect is that this remains true even if the address you are resolving is a special-purpose name like "localhost" (#952740), and it seems like it also applies equally to numeric addresses. The attached getaddrinfo.c test program is a convenient way to confirm this: sudo apt install bubblewrap # or use unshare cc -o getaddrinfo getaddrinfo.c bwrap \ --dev-bind / / \ --ro-bind basic-nsswitch.conf /etc/nsswitch.conf \ ./getaddrinfo 127.0.0.1 > with-shared-net bwrap --unshare-net \ --dev-bind / / \ --ro-bind basic-nsswitch.conf /etc/nsswitch.conf \ ./getaddrinfo 127.0.0.1 > with-unshared-net diff -u with-shared-net with-unshared-net (The reason I mention replacing /etc/nsswitch.conf with basic-nsswitch.conf is that some nss modules seem to act as a workaround for this: in particular, if you have systemd's libnss-resolve installed and enabled, that acts as a workaround.) Some possible strategies to avoid this class of bug: * Recognise localhost, numeric IPv4 and numeric IPv6 addresses in higher-level resolver interfaces, and resolve them differently: - resolve localhost without AI_ADDRCONFIG, like Firefox used to do - or special-case localhost names to resolve to 127.0.0.1 and ::1, bypassing glibc completely, like recent Firefox, Chromium and GLib do - recognise numeric IPv4/IPv6 addresses and resolve them with AI_NUMERICHOST, or by parsing the string in the higher-level resolver library and bypassing glibc, like recent GLib does * On name resolution failure with AI_ADDRCONFIG, retry without AI_ADDRCONFIG before giving up * Use AF_UNSPEC (or hints == NULL), and hope #854301 doesn't ever get fixed * Use libnss-wrapper to run tests as a workaround (see debian/rules in dbus) * Make tests that require resolving localhost skip themselves if it doesn't resolve; or don't run such tests at build-time at all, only in autopkgtest (IMO undesirable because it significantly reduces test coverage on non-amd64, non-arm64 architectures, where the buildds are our only opportunity to check that the built package is functional) I see sslsplit uses libevent utility functions for name resolution, so in the long term it might be more appropriate to implement these special-cases in libevent (libevent and GLib have a similar place in the stack) rather than in sslsplit itself. Of course, it might also be better if getaddrinfo() could be made to "do the right thing" automatically, but there's been no response to #952740 and I suspect there are probably standards-conformance concerns about doing this at the libc level. smcv
#define _GNU_SOURCE #include <errno.h> #include <netdb.h> #include <stdio.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> typedef struct { int value; const char *name; } NamedInt; #define ITEM(x) { x, #x } static const NamedInt ai_flags[] = { ITEM (AI_ADDRCONFIG), ITEM (AI_ALL), ITEM (AI_CANONIDN), ITEM (AI_CANONNAME), ITEM (AI_IDN), ITEM (AI_V4MAPPED), { 0, NULL } }; static const NamedInt families[] = { ITEM (AF_UNSPEC), ITEM (AF_INET), ITEM (AF_INET6), { 0, NULL } }; static const NamedInt protocols[] = { ITEM (IPPROTO_TCP), ITEM (IPPROTO_UDP), ITEM (IPPROTO_IP), { 0, NULL } }; static const NamedInt socktypes[] = { ITEM (SOCK_STREAM), ITEM (SOCK_DGRAM), ITEM (SOCK_RAW), { 0, NULL } }; static void print_flags (const char *indent, int flags) { int i; for (i = 0; ai_flags[i].name != NULL; i++) { if (flags & ai_flags[i].value) printf ("%s%s (0x%x)\n", indent, ai_flags[i].name, ai_flags[i].value); } } static const char * describe (int value, const NamedInt *names) { int i; for (i = 0; names[i].name != NULL; i++) { if (value == names[i].value) return names[i].name; } return "(unknown)"; } static void try_getaddrinfo(const char *name, const struct addrinfo *hints) { struct addrinfo *addrs = NULL; const struct addrinfo *a; int res; int saved_errno; printf ("==== trying getaddrinfo %s ====\n", name); if (hints == NULL) { printf ("\tno hints\n"); } else { printf ("hints:\n"); printf ("\tai_flags: 0x%x\n", hints->ai_flags); print_flags ("\t\t", hints->ai_flags); printf ("\tai_family: %d %s\n", hints->ai_family, describe (hints->ai_family, families)); printf ("\tai_socktype: %d %s\n", hints->ai_socktype, describe (hints->ai_socktype, socktypes)); printf ("\tai_protocol: %d %s\n", hints->ai_protocol, describe (hints->ai_protocol, protocols)); } errno = 0; res = getaddrinfo (name, NULL, hints, &addrs); saved_errno = errno; if (res != 0) { printf ("result %d: %s (errno %d: %s)\n", res, gai_strerror (res), saved_errno, strerror (saved_errno)); printf ("\n"); return; } printf ("results:\n"); for (a = addrs; a != NULL; a = a->ai_next) { char host[1024]; res = getnameinfo (a->ai_addr, a->ai_addrlen, host, sizeof (host), NULL, 0, NI_NUMERICHOST); if (res != 0) { printf ("\tai_addr: (getnameinfo failed: %d %s)\n", res, gai_strerror (res)); } else { printf ("\tai_addr: %s\n", host); } printf ("\tai_flags: 0x%x\n", a->ai_flags); print_flags ("\t\t", a->ai_flags); printf ("\tai_family: %d %s\n", a->ai_family, describe (a->ai_family, families)); printf ("\tai_socktype: %d %s\n", a->ai_socktype, describe (a->ai_socktype, socktypes)); printf ("\tai_protocol: %d %s\n", a->ai_protocol, describe (a->ai_protocol, protocols)); printf ("\tai_addrlen: %d\n", a->ai_addrlen); printf ("\tai_canonname: %s\n", a->ai_canonname); printf ("\n"); } freeaddrinfo (addrs); } #define N_ELEMENTS(arr) sizeof (arr) / sizeof (arr[0]) int main (int argc, char *argv[]) { struct addrinfo hints = { 0 }; int families[] = { AF_INET, AF_INET6, AF_UNSPEC }; int i, j, k; const char *name; if (argc > 1) name = argv[1]; else name = "localhost"; try_getaddrinfo (name, NULL); for (i = 0; i < N_ELEMENTS (families); i++) { hints.ai_family = families[i]; for (j = 0; j < 2; j++) { if (j) hints.ai_flags |= AI_ADDRCONFIG; else hints.ai_flags &= ~AI_ADDRCONFIG; for (k = 0; k < 2; k++) { if (k) { hints.ai_protocol = IPPROTO_TCP; hints.ai_socktype = SOCK_STREAM; } else { hints.ai_protocol = 0; hints.ai_socktype = 0; } if (j && k) printf ("(this next one is what GIO actually does)\n"); try_getaddrinfo (name, &hints); } } } return 0; }
# /etc/nsswitch.conf # # Example configuration of GNU Name Service Switch functionality. # If you have the `glibc-doc-reference' and `info' packages installed, try: # `info libc "Name Service Switch"' for information about this file. passwd: compat group: compat shadow: compat hosts: files dns networks: files protocols: db files services: db files ethers: db files rpc: db files netgroup: nis