1

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?

milanHrabos
  • 2,010
  • 3
  • 11
  • 45

1 Answers1

-1

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.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • But that contradicts what the book says "...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." – milanHrabos Jan 25 '21 at 16:56
  • On dual-stack systems, accepting IPv4 clients on an IPv6 server *typically* (may vary by system) requires disabling the IPV6_IPV6ONLY socket option explicitly, which `getaddrinfo()` has no way of knowing whether the calling app will actually do that or not. So, I think the best option is to simply loop through the returned addresses twice, the 1st loop binding only IPv6 addresses and enabling dual-stacking as needed, and the 2nd loop binding only IPv4 addresses that are not already handled by dual-stacking. – Remy Lebeau Jan 25 '21 at 18:29
  • @RemyLebeau: `IPV6_IPV6ONLY` is supposed to be off by default. A few systems change this default, contrary to documented best practices. IIRC OpenBSD has it wrong and Linux has it right, but nowadays systemd or distro-specific sysctl config may be breaking the default... – R.. GitHub STOP HELPING ICE Jan 25 '21 at 18:43
  • @R..GitHubSTOPHELPINGICE `IPV6_V6ONLY` is enabled by default on Windows, too. – Remy Lebeau Jan 25 '21 at 19:18
  • You can't bind an `AF_INET6` socket to an `AF_INET` address and vice versa. So to bind to both the IPv4 and IPv6 address you would need 2 sockets. Binding the IPv6 socket to the IPv6 address seems to be the only way to get dual-stack support. -- Which makes the use of getaddrinfo totally pointless. You have to create and bind an INET6 socket and if that fails you can fallback to INET it seems. – Goswin von Brederlow May 29 '23 at 14:46