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

Reply via email to