getaddrinfo () возвращает только ipv6 при использовании AF_UNSPEC - PullRequest
1 голос
/ 11 апреля 2019

Когда я хочу подключиться к серверу (работающему локально) и не знаю, использует ли приложение ipv4, ipv6 или оба, следует ли мне вызывать getaddrinfo() дважды, один раз с AF_INET и один раз с AF_INET6,и попробовать все возвращенные адреса?

Некоторый контекст: getaddrinfo() предоставляет способ разрешения имен хостов, не зависящих от ipv4 / ipv6.В сети я нашел рекомендации, согласно которым общий алгоритм подключения к серверу должен использовать getaddrinfo() с подсказкой AF_UNSPEC и пробовать адреса, возвращенные в списке.

Однако в моей настройке getaddrinfo() только возвращает запись ipv6, когда я использую AF_UNSPEC, хостом является "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 толькосодержит запись ipv6 для localhost, только возвращаемую для AF_UNSPEC.Для AF_INET запись не считается приемлемой, а localhost правильно разрешено до 127.0.0.1.

1 Ответ

2 голосов
/ 11 апреля 2019

Это действительно интересный вопрос:

Это случай, который специально обрабатывается модулем nss-files из glibc; если сделан запрос на localhost с семейством адресов AF_INET и разобрана запись localhost v6 в / etc / hosts, она неявно преобразуется в запись localhost v4 с адресом 127.0.0.1.

См. 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, поэтому вы разрешите адрес локального хоста v4 только при наличии явной записи в /etc/hosts.

...