getaddrinfo() returns 几个相同的结果

getaddrinfo() returns several identical results

/etc/hosts 我有:

127.0.0.1   localhost.localdomain   localhost
::1     localhost.localdomain   localhost

一个测试程序:

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>

const char* family_to_string(int family)
{
    return family == AF_INET ? "AF_INET"
        : family == AF_INET6 ? "AF_INET6"
        : "<unknown>";
}

const char* socktype_to_string(int socktype)
{
    return socktype == SOCK_STREAM ? "SOCK_STREAM"
        : socktype == SOCK_DGRAM ? "SOCK_DGRAM"
        : "<unknown>";
}

const char* protocol_to_string(int protocol)
{
    return protocol == IPPROTO_TCP ? "IPPROTO_TCP"
        : protocol == IPPROTO_UDP ? "IPPROTO_UDP"
        : "<unknown>";
}

void print_addrinfo(struct addrinfo *ai)
{
    int r;
    char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
    r = getnameinfo(ai->ai_addr, ai->ai_addrlen, hbuf, sizeof(hbuf), sbuf,
                    sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV);
    if (r)
    {
        fputs("getnameinfo\n", stderr);
        exit(EXIT_FAILURE);
    }
    const char *family = family_to_string(ai->ai_family);
    const char *socktype = socktype_to_string(ai->ai_socktype);
    const char *protocol = protocol_to_string(ai->ai_protocol);
    const char *family2 = family_to_string(ai->ai_addr->sa_family);
    printf("flags=%i, family=%s, socktype=%s, protocol=%s, family=%s, host=%s, serv=%s\n",
           ai->ai_flags, family, socktype, protocol, family2, hbuf, sbuf);
}

int main(int argc, char *argv[])
{
    (void)argc;
    (void)argv;
    int r;
    struct addrinfo hints;
    struct addrinfo *result, *ai;

    memset(&hints, 0, sizeof(struct addrinfo));
    // hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    r = getaddrinfo("localhost", "8000", &hints, &result);
    if (r)
    {
        fputs("getaddrinfo\n", stderr);
        exit(EXIT_FAILURE);
    }

    ai = result;
    while (ai)
    {
        print_addrinfo(ai);
        ai = ai->ai_next;
    }

    exit(EXIT_SUCCESS);
}

当我不设置hints.ai_family时,如预期的那样:

$ gcc -Wall -Wextra -Werror -pedantic -pedantic-errors -g -gdwarf-2 -g3 % && ./a.out
flags=0, family=AF_INET6, socktype=SOCK_STREAM, protocol=IPPROTO_TCP, family=AF_INET6, host=::1, serv=8000
flags=0, family=AF_INET, socktype=SOCK_STREAM, protocol=IPPROTO_TCP, family=AF_INET, host=127.0.0.1, serv=8000

hints.ai_family它returns两个相同的addrinfo结构:

flags=0, family=AF_INET, socktype=SOCK_STREAM, protocol=IPPROTO_TCP, family=AF_INET, host=127.0.0.1, serv=8000
flags=0, family=AF_INET, socktype=SOCK_STREAM, protocol=IPPROTO_TCP, family=AF_INET, host=127.0.0.1, serv=8000

你能解释一下这是怎么回事吗?我认为 hints 充当过滤器。即本例只返回一个结果。

UPD /etc/nsswitch.conf

# Begin /etc/nsswitch.conf

passwd: compat mymachines systemd
group: compat mymachines systemd
shadow: compat

publickey: files

hosts: files mymachines resolve [!UNAVAIL=return] dns myhostname
networks: files

protocols: files
services: files
ethers: files
rpc: files

netgroup: files

# End /etc/nsswitch.conf

/etc/resolv.conf:

# Generated by resolvconf
search Dlink
nameserver 192.168.0.1

192.168.0.1 是我的路由器。我的 IP 地址是 192.168.0.39.

据我所知:

sysdeps/posix/getaddrinfo.c:2206

int
getaddrinfo (const char *name, const char *service,
             const struct addrinfo *hints, struct addrinfo **pai)

sysdeps/posix/getaddrinfo.c:2304

last_i = gaih_inet (name, pservice, hints, end, &naddrs, &tmpbuf);

sysdeps/posix/getaddrinfo.c:342

static int
gaih_inet (const char *name, const struct gaih_service *service,
           const struct addrinfo *req, struct addrinfo **pai,
           unsigned int *naddrs, struct scratch_buffer *tmpbuf)

sysdeps/posix/getaddrinfo.c:595

rc = __gethostbyname2_r (name, AF_INET, &th,
                         tmpbuf->data, tmpbuf->length,
                         &h, &h_errno);

nss/getXXbyYY_r.c:189

int
INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer,
                           size_t buflen, LOOKUP_TYPE **result H_ERRNO_PARM
                           EXTRA_PARAMS)

nss/getXXbyYY_r.c:316

status = DL_CALL_FCT (fct.l, (ADD_VARIABLES, resbuf, buffer, buflen,
                              &errno H_ERRNO_VAR EXTRA_VARIABLES));

nss/nss_files/files-hosts.c:385

enum nss_status
_nss_files_gethostbyname2_r (const char *name, int af, struct hostent *result,
                             char *buffer, size_t buflen, int *errnop,
                             int *herrnop)

nss/nss_files/files-hosts.c:389

return _nss_files_gethostbyname3_r (name, af, result, buffer, buflen,
                                    errnop, herrnop, NULL, NULL);

nss/nss_files/files-hosts.c:334

enum nss_status
_nss_files_gethostbyname3_r (const char *name, int af, struct hostent *result,
                             char *buffer, size_t buflen, int *errnop,
                             int *herrnop, int32_t *ttlp, char **canonp)

这里我们得到第一个匹配项,如果 multi/etc/host.conf 中是 on - 其余的:

nss/nss_files/files-hosts.c:352

while ((status = internal_getent (stream, result, buffer, buflen, errnop,
                                  herrnop, af, flags))
       == NSS_STATUS_SUCCESS)
  {
    LOOKUP_NAME_CASE (h_name, h_aliases)
  }

if (status == NSS_STATUS_SUCCESS
    && _res_hconf.flags & HCONF_FLAG_MULTI)
  status = gethostbyname3_multi
    (stream, name, af, result, buffer, buflen, errnop, herrnop, flags);

nss/nss_files/files-hosts.c:127

static enum nss_status
gethostbyname3_multi (FILE * stream, const char *name, int af,
                      struct hostent *result, char *buffer, size_t buflen,
                      int *errnop, int *herrnop, int flags)

nss/nss_files/files-hosts.c:162

status = internal_getent (stream, &tmp_result_buf, tmp_buffer.data,
                          tmp_buffer.length, errnop, herrnop, af,
                          flags);

nss/nss_files/files-XXX.c:178

static enum nss_status
internal_getent (FILE *stream, struct STRUCTURE *result,
                 char *buffer, size_t buflen, int *errnop H_ERRNO_PROTO
                 EXTRA_ARGS_DECL)

nss/nss_files/files-XXX.c:222

|| ! (parse_result = parse_line (p, result, data, buflen, errnop
                                 EXTRA_ARGS)));

这里我们解析“::1 localhost.localdomain localhost”这一行。我们尝试 运行 inet_pton 超过 ::1。那失败了,因为 af == AF_INET。然后我们 注意 ::1 是 IPv6 环回地址,所以我们 return IPv4 环回 地址:

nss/nss_files/files-hosts.c:51

LINE_PARSER

nss/nss_files/files-hosts.c:59

if (inet_pton (af == AF_UNSPEC ? AF_INET : af, addr, entdata->host_addr)
    > 0)
  af = af == AF_UNSPEC ? AF_INET : af;
else
  {
    if (...)
      ...
    else if (af == AF_INET
             && inet_pton (AF_INET6, addr, entdata->host_addr) > 0)
      {
        if (...)
          ...
        else if (IN6_IS_ADDR_LOOPBACK (entdata->host_addr))
          {
            in_addr_t localhost = htonl (INADDR_LOOPBACK);
            memcpy (entdata->host_addr, &localhost, sizeof (localhost));
          }

也许 mailing list 上有人会回复,我们会学到更多。