getaddrinfo() returns 使用 AF_UNSPEC 时只有 ipv6

getaddrinfo() returns only ipv6 when using AF_UNSPEC

当我想连接到服务器(运行 本地)并且不知道应用程序是否使用 ipv4、ipv6 或两者时,我应该调用 getaddrinfo() 两次,一次使用 AF_INET 和一次 AF_INET6,并尝试所有 returned 地址?

一些背景: getaddrinfo() 为 ipv4/ipv6-agnostic 主机名解析提供了一种方法。我在网上找到了一些指导方针,指出连接到服务器的常见算法是使用 getaddrinfo() 和 AF_UNSPEC 提示,然后尝试在列表中输入 return 的地址。

但是,在我的设置中,当我使用 AF_UNSPEC 时,getaddrinfo() return 是一个 ipv6 条目,主机是 "localhost"。 另一方面,如果我明确要求 IPv4,getaddrinfo() 会 return 一个 IPv4 地址。

此示例使用 AF_UNSPEC 调用 getaddrinfo() 一次,使用 AF_INET 调用一次,并遍历 returned 列表并打印条目的地址族:

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

const char* family_to_string(int family) {
    switch (family) {
        case AF_INET:
            return "AF_INET";
        case AF_INET6:
            return "AF_INET6";
        case AF_UNSPEC:
            return "AF_UNSPEC";
        default:
            return "UNKNOWN";
    }
}

int main(void) {
    struct addrinfo hints;
    struct addrinfo *res, *it;
    static const char* host = "localhost";
    static const char* port = "42420";

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = 0;
    hints.ai_family = AF_UNSPEC;
    printf("getaddrinfo(.. %s ..):\n", family_to_string(hints.ai_family));
    int ret = getaddrinfo(host, port, &hints, &res);
    if (ret != 0) {
        return 1;
    }

    for (it = res; it != NULL; it = it->ai_next) {
        printf("entry for %s\n", family_to_string(it->ai_family));
    }
    printf("\n");
    freeaddrinfo(res);

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = 0;
    hints.ai_family = AF_INET;
    printf("getaddrinfo(.. %s ..):\n", family_to_string(hints.ai_family));
    ret = getaddrinfo(host, port, &hints, &res);
    if (ret != 0) {
        return 1;
    }

    for (it = res; it != NULL; it = it->ai_next) {
        printf("entry for %s\n", family_to_string(it->ai_family));
    }
    printf("\n");
    freeaddrinfo(res);

    return 0;
}

当我收到这个输出时,我感到很惊讶:

getaddrinfo(.. AF_UNSPEC ..):
entry for AF_INET6

getaddrinfo(.. AF_INET ..):
entry for AF_INET

在深入了解 getaddrinfo() 的行为后,它似乎首先检查 /etc/hosts 中的条目,如果找到匹配的条目,它只会 return 那些,并且不会尝试以不同方式解析主机名。

因为我的 /etc/hosts 文件只包含一个用于 localhost 的 ipv6 条目,只有 returned 用于 AF_UNSPEC。对于 AF_INET,条目不被视为符合条件,localhost 被正确解析为 127.0.0.1

这确实是一个有趣的问题:

这是一个案例,由glibc的nss-files模块专门处理;如果请求地址族 AF_INET 的本地主机并且解析了 /etc/hosts 的 v6 本地主机条目,它会隐式转换为地址 127.0.0.1.[=17 的 v4 本地主机条目=]

请参阅 nss/nss_files/files-hosts.c(第 70 行左右),其中执行此转换:

else if (IN6_IS_ADDR_LOOPBACK (entdata->host_addr))
  {
    in_addr_t localhost = htonl (INADDR_LOOPBACK);
    memcpy (entdata->host_addr, &localhost, sizeof (localhost));
  }

当请求 AF_UNSPEC 时,不会采用此分支,因此只有在 /etc/hosts 中有明确条目时,您才会解析 v4 本地主机地址。