getaddrinfo(3) with specified hints->ai_socktype doesn't return IPv6 addresses

c, getaddrinfo, linux, unix, unix-socket

Solution

Your for loop check is wrong, you're always skipping the last entry - which in your case happens to be the IPv6 address.

 for(; res->ai_next; res = res->ai_next) {

needs to be

 for(; res; res = res->ai_next) { 

Problem

Suppose the following code, which mimics the basic functionality of the `resolveip` utility: ``` #define _POSIX_SOURCE /* getaddrinfo() */ #include <sys/types.h> /* getaddrinfo(), struct addrinfo, struct sockaddr */ #include <sys/socket.h> /* getaddrinfo(), struct addrinfo, struct sockaddr, AF_* */ #include <netdb.h> /* getaddrinfo(), struct addrinfo, struct sockaddr */ #include <arpa/inet.h> /* inet_ntop() */ #include <stdio.h> /* fprintf(), printf(), perror(), stderr */ #include <stdlib.h> /* EXIT_SUCCESS */ int main(int argc, char** argv) { for(int i = 1; i < argc; ++i) { /* For each hostname */ char* hostname = argv[i]; struct addrinfo* res; /* We retrieve the addresses */ if(getaddrinfo(hostname, NULL, NULL, &res) == -1) { perror("getaddrinfo"); continue; } for(; res->ai_next; res = res->ai_next) { /* We print the addresses */ switch(res->ai_addr->sa_family) { case AF_INET: { struct in_addr addr = ((struct sockaddr_in*)(res->ai_addr))->sin_addr; char buffer[17]; printf("%s: %s\n", hostname, inet_ntop(AF_INET, &addr, buffer, 17)); break; } case AF_INET6: { struct in6_addr addr = ((struct sockaddr_in6*)(res->ai_addr))->sin6_addr; char buffer[40]; printf("%s: %s\n", hostname, inet_ntop(AF_INET6, &addr, buffer, 40)); break; } default: { fprintf(stderr, "%s: Unknown address family\n", hostname); } } } freeaddrinfo(res); /* We release the allocated resources */ } return EXIT_SUCCESS; } ``` The above code invoked with `google.com` as its first and only argument will output something akin to: ``` google.com: 173.194.35.87 google.com: 173.194.35.87 google.com: 173.194.35.87 google.com: 173.194.35.95 google.com: 173.194.35.95 google.com: 173.194.35.95 google.com: 173.194.35.88 google.com: 173.194.35.88 google.com: 173.194.35.88 google.com: 2a00:1450:4008:800::101f google.com: 2a00:1450:4008:800::101f ``` Suppose we want to get rid of the duplicate entries. Let us thus create a structure containing hints about what sort of results we wish to retrieve. The following modification does not affect the output in any way, since the structure is initialized to default values as mandated by the `getaddrinfo(3)` manpage: ``` struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = 0, .ai_protocol = 0, .ai_flags = (AI_V4MAPPED | AI_ADDRCONFIG) }; if(getaddrinfo(hostname, NULL, &hints, &res) == -1) { perror("getaddrinfo"); continue; } ``` Let us now filter out the duplicate entries by specifying the `ai_socktype` field to an arbitrary value: ``` struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_DGRAM, .ai_protocol = 0, .ai_flags = (AI_V4MAPPED | AI_ADDRCONFIG) }; ``` Alas, we have now lost the IPv6 address: ``` google.com: 173.194.35.87 google.com: 173.194.35.95 google.com: 173.194.35.88 ``` Let us now revert back to the original hint-less version: ``` if(getaddrinfo(hostname, NULL, NULL, &res) == -1) { perror("getaddrinfo"); continue; } ``` And let us now use manual filtering instead: ``` for(; res->ai_next; res = res->ai_next) { if(res->ai_socktype != SOCK_DGRAM) continue; ... } ``` And now everything works the way it is supposed to: ``` google.com: 173.194.35.87 google.com: 173.194.35.95 google.com: 173.194.35.88 google.com: 2a00:1450:4008:801::101f ``` I am curious whence the discrepancy between passing a hint to the `getaddrinfo(3)` function and manual filtering of the returned records arises. Tested on linux kernel 3.8.0-32 with glibc 2.17.

Original source