Why does getaddrinfo() return first IPv4 instead of IPv6 when AI_PASSIVE is set?

781 Views Asked by At

According to https://www.amazon.com/Unix-Network-Programming-Sockets-Networking/dp/0131411551:

This statement in the POSIX specification also implies that if the AI_PASSIVE flag is specified without a hostname, then the IPv6 wildcard address (IN6ADDR_ANY_INIT or 0::0) should be returned as a sockaddr_in6 structure, along with the IPv4 wildcard address (INADDR_ANY or 0.0.0.0), which is returned as a sockaddr_in structure. It also makes sense to return the IPv6 wildcard address first because we will see in Section 12.2 that an IPv6 server socket can handle both IPv6 and IPv4 clients on a dual-stack host.

However, if I try to set the AI_PASSIVE flag (and having null hostname of course), as is a case for passive servers, then I will get first IPv4 and not IPv6 as book suggest:

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

#define HOSTLEN 128
#define PORTLEN 16

int main()
{
    int retcode;
    struct addrinfo hints, *res;
    char output[HOSTLEN], portbuf[PORTLEN];

    memset(&hints, 0, sizeof(hints));
    hints.ai_flags = AI_PASSIVE;
    hints.ai_family = AF_UNSPEC;
    //exmaple of socktype to be SOCK_STREAM
    hints.ai_socktype = SOCK_STREAM;

    //example port "ftp"
    if ((retcode = getaddrinfo(NULL, "ftp", &hints, &res)) != 0)
    {
        printf("getaddrinfo error: %s\n", gai_strerror(retcode));
    }

    do
    {
        switch (res->ai_family)
        {
        case AF_INET:;
            struct sockaddr_in *sin = (struct sockaddr_in *)res->ai_addr;
            inet_ntop(res->ai_family, &sin->sin_addr, output, HOSTLEN);
            snprintf(portbuf, PORTLEN, ":%d", ntohs(sin->sin_port));
            strcat(output, portbuf);
            break;

        case AF_INET6:;
            struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)res->ai_addr;
            output[0] = '[';
            inet_ntop(res->ai_family, &sin6->sin6_addr, output + 1, HOSTLEN - 1);
            snprintf(portbuf, PORTLEN, "]:%d", ntohs(sin6->sin6_port));
            strcat(output, portbuf);
            break;
        }

        printf("address returned from getaddrinfo: %s\n", output);
    } while ((res = res->ai_next) != NULL);
}

output:

address returned from getaddrinfo: 0.0.0.0:21
address returned from getaddrinfo: [::]:21

So I can see, the first address retuned from getaddrinfo(), is sockaddr_in(ipv4) and not sockaddr_in6(ipv6). So is the book incorrect or is it just because of some configutation?

The reason I ask is becuase it causes problem when trying to connect client, that specifies IPv6 address for the server (which has address returned from getaddrinfo(), set AI_PASSIVE) and returning 0.0.0.0 instead if 0::0 and thus the client will never connect. So how to make getaddrinfo to return 0::0 instead of 0.0.0.0 when AI_PASSIVE is set?

1

There are 1 best solutions below

5
R.. GitHub STOP HELPING ICE On

The intended usage model of getaddrinfo with AI_PASSIVE is that you bind all addresses it returns to listen on, rather than just the first.

As for why a particular implementation returns the v4 result first, I'm not sure. One plausible motivation is the default behavior of v6 sockets for the "any" address accepting v4 connections (via v4mapped addresses). If you bind the v6 first, it's possible you'll get such v4mapped clients between the first and second binding. If you bind the v4 first, you ensure that all v4 connections come on the v4 socket. It might even be the case that binding the v4 fails if the v6 any is already bound.