getaddrinfo () с IPv6 не имеет смысла

Я не понимаю, почему getaddrinfo не возвращает действительный адрес IPv6.

В моей системе приведенный ниже код печатает 22:B8:00:00:00:00:00:00:00:00:00:00:00:00, но я ожидал где-то 01, поскольку localhost должно разрешиться в ::1.

В то же время sa_data составляет всего 14 байтов, тогда как IPv6-адреса - 16 байтов, поэтому кажется, что последняя пара байтов всегда отрубается, и функция не может вернуть IPv6-адрес?

Может кто-нибудь объяснить, что происходит? Как мне использовать эту функцию с IPv6?

#include <stdio.h>
#include <WinSock2.h>
#include <WS2TCPIP.h>
#pragma comment(lib, "WS2_32")

int main(int argc, char *argv[])
{
    WSADATA wsadata;
    WSAStartup(0x0002, &wsadata);
    addrinfo addr_hints = { 0, PF_INET6, SOCK_DGRAM, IPPROTO_UDP }, *addrs_out;
    getaddrinfo("localhost", "8888", &addr_hints, &addrs_out);
    fprintf(stderr,
        "%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\n",
        static_cast<unsigned char>(addrs_out->ai_addr->sa_data[ 0]),
        static_cast<unsigned char>(addrs_out->ai_addr->sa_data[ 1]),
        static_cast<unsigned char>(addrs_out->ai_addr->sa_data[ 2]),
        static_cast<unsigned char>(addrs_out->ai_addr->sa_data[ 3]),
        static_cast<unsigned char>(addrs_out->ai_addr->sa_data[ 4]),
        static_cast<unsigned char>(addrs_out->ai_addr->sa_data[ 5]),
        static_cast<unsigned char>(addrs_out->ai_addr->sa_data[ 6]),
        static_cast<unsigned char>(addrs_out->ai_addr->sa_data[ 7]),
        static_cast<unsigned char>(addrs_out->ai_addr->sa_data[ 8]),
        static_cast<unsigned char>(addrs_out->ai_addr->sa_data[ 9]),
        static_cast<unsigned char>(addrs_out->ai_addr->sa_data[10]),
        static_cast<unsigned char>(addrs_out->ai_addr->sa_data[11]),
        static_cast<unsigned char>(addrs_out->ai_addr->sa_data[12]),
        static_cast<unsigned char>(addrs_out->ai_addr->sa_data[13]));
    freeaddrinfo(addrs_out);
    return 0;
}

person user541686    schedule 26.08.2017    source источник
comment
Сначала обязательно проверьте возвращаемое значение getaddrinfo(). Если это не удастся, ваши адреса будут мусором.   -  person Jeff    schedule 27.08.2017
comment
@Jeff: я использую свой настоящий код; это не проблема.   -  person user541686    schedule 27.08.2017


Ответы (2)


sockaddr определения структур для справки:

struct sockaddr {
    ushort  sa_family;
    char    sa_data[14];
};


struct sockaddr_in6 {
    short   sin6_family;
    u_short sin6_port;
    u_long  sin6_flowinfo;
    struct  in6_addr sin6_addr;
    u_long  sin6_scope_id;
};

Когда ai_family == AF_INET6 ai_addr фактически указывает на struct sockaddr_in6. Первые несколько печатаемых байтов - это sin6_port и sin6_flowinfo. После идет адрес IPv6.

Отредактируйте, чтобы добавить:

Вы можете использовать ai_addr напрямую с такими функциями, как bind() и getnameinfo(). Обычно вам не нужно копаться в деталях определения структуры. Например, я бы использовал getnameinfo() с NI_NUMERICHOST, чтобы получить адрес для печати.

person Jeff    schedule 26.08.2017
comment
Ой, я думал, sockaddr был достаточно большим, чтобы вместить sockaddr_in6, но похоже, что SOCKADDR_STORAGE для этого! Наконец-то это имеет смысл, спасибо! - person user541686; 27.08.2017
comment
Дополнительный вопрос, но знаете ли вы, есть ли место, где указана длина каждого стандартного семейства адресов? - person user541686; 27.08.2017
comment
@ Mehrdad Насколько я знаю. Их более 40, и их собственные определения sockaddr разбросаны по различным файлам заголовков. - person Jeff; 27.08.2017

sockaddr не являются строго интерпретируется как указатель на структуру sockaddr. Структура интерпретируется по-разному в контексте разных семейств адресов.

поэтому нам нужно сначала проверить sa_family sockaddr или ai_family из addrinfo (в этой базе он должен быть равен) и исходя из этого нужно "переинтерпретировать" указатель из _ 5_ (это как указатель void*) на реальную структуру. скажем, используйте для этого union:

addrinfo *addrs_out, *addr;

if (getaddrinfo("localhost", "8888", 0, &addrs_out) == NOERROR)
{
    addr = addrs_out;

    CHAR buf[256], *sz, srv[128];
    ULONG n;
    PUCHAR Byte;

    do 
    {
        union {
            sockaddr* ai_addr;
            SOCKADDR_IN* pa;
            SOCKADDR_IN6* pa6;
        };

        ai_addr = addr->ai_addr;

        if (addr->ai_family != ai_addr->sa_family)
        {
            __debugbreak();
        }

        switch (addr->ai_family)
        {
        case AF_INET6:
            Byte = pa6->sin6_addr.u.Byte, n = RTL_NUMBER_OF(pa6->sin6_addr.u.Byte), sz = buf;
            do 
            {
                sz += sprintf(sz, "%02X:", *Byte++);
            } while (--n);

            sz[-1] = 0;
            DbgPrint("AF_INET6: %s\n", buf);
            break;

        case AF_INET:
            if (0 <= RtlIpv4AddressToStringExA(&pa->sin_addr.S_un.S_addr, pa->sin_port, buf, &(n = RTL_NUMBER_OF(buf))))
            {
                DbgPrint("AF_INET: %s\n", buf);
            }
            break;
        }

        // alt print
        if (getnameinfo(ai_addr, (socklen_t)addr->ai_addrlen, buf, RTL_NUMBER_OF(buf), srv, RTL_NUMBER_OF(srv), NI_NUMERICHOST ) == NOERROR)
        {
            DbgPrint("%s:%s\n", buf, srv);
        }

    } while (addr = addr->ai_next);

    freeaddrinfo(addrs_out);
}
person RbMm    schedule 27.08.2017