使用AF_UNSPEC

时间:2019-04-11 07:53:40

标签: c networking ipv6 ipv4 getaddrinfo

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

某些情况: getaddrinfo()提供了一种与ipv4 / ipv6无关的主机名解析的方法。在网上,我发现了一些准则,指出连接到服务器的通用算法是将getaddrinfo()与AF_UNSPEC提示一起使用,并尝试列表中返回的地址。

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

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

#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中的条目,如果找到匹配的条目,则仅返回这些条目,而不尝试解析主机名不同。

由于我的/etc/hosts文件仅包含localhost的ipv6条目,因此仅返回AF_UNSPEC的ipv6条目。对于AF_INET,该条目不符合条件,并且localhost已正确解析为127.0.0.1

1 个答案:

答案 0 :(得分:2)

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

这种情况由glibc的nss-files模块专门处理;如果发出了对地址族为AF_INET的localhost的请求,并且解析了/ etc / hosts的v6 localhost条目,则将其隐式转换为地址为127.0.0.1的v4 localhost条目。

请参见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本地主机地址。