getaddrinfo() with IPv6 not making sense

1k views Asked by At

I don't understand why getaddrinfo is not returning a valid IPv6 address.

On my system the code below is printing 22:B8:00:00:00:00:00:00:00:00:00:00:00:00, but I expected a 01 somewhere, since localhost should resolve to ::1.

At the same time, sa_data is only 14 bytes, whereas IPv6 addresses are 16 bytes, so it seems that the last couple of bytes are always chopped off, and the function can't return an IPv6 address?

Can someone explain what's going on? How am I supposed to use this function with 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;
}
2

There are 2 answers

3
Jeff On BEST ANSWER

sockaddr struct definitions for reference:

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;
};

When ai_family == AF_INET6 ai_addr actually points to a struct sockaddr_in6. The first few bytes you are printing are sin6_port and sin6_flowinfo. The IPv6 address comes after.

Edit to add:

You can use ai_addr directly with functions like bind() and getnameinfo(). You typically won't need to dig down into the struct definition details. For example, I would use getnameinfo() with NI_NUMERICHOST to get a printable address.

4
RbMm On

sockaddr are not strictly interpreted to be pointers to a sockaddr structure. The structure is interpreted differently in the context of different address families.

so we need first check sa_family of sockaddr or ai_family from addrinfo (in this base it must be equal) and based on this need "reinterpret" pointer from sockaddr (this like void* pointer) to real structure. say use union for this:

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);
}