getaddrinfo() โ€” The hints Argument

 

getaddrinfo() โ€” The hints Argument
Part 3 โ€” Controlling Results with hints & AI_* Flags
๐Ÿ“š Chapter 59 โ€“ TLPI
๐ŸŽ›๏ธ Filter Criteria
๐Ÿšฉ AI_* Flag Details

What Is the hints Argument?

The hints argument to getaddrinfo() is an addrinfo structure that you fill in to tell the function what kind of results you want. Think of it as a filter or preference list.

For example: do you want only IPv6 addresses? Only TCP? Only passive (listening) sockets? Or should numeric strings be treated as hostnames? All of this is controlled through the hints argument using AI_* flags and specific field values.

If you pass hints = NULL, getaddrinfo() uses default settings: accept any address family, any socket type, and any protocol.

Key Terms

hints argument AI_PASSIVE AI_CANONNAME AI_NUMERICHOST AI_NUMERICSERV AI_ADDRCONFIG AI_V4MAPPED AI_ALL AF_UNSPEC INADDR_ANY IN6ADDR_ANY_INIT Wildcard Address

1. Which Fields Can You Set in hints?

When using an addrinfo structure as the hints argument (input), only four fields are meaningful. All other fields must be set to 0 or NULL.

Field in hints What it controls Valid values
ai_flags Bitwise OR of AI_* flag constants to control special behaviour. AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST | AI_NUMERICSERV | AI_ADDRCONFIG | AI_V4MAPPED | AI_ALL (or 0)
ai_family Which address families to return. AF_INET (IPv4 only), AF_INET6 (IPv6 only), AF_UNSPEC (any)
ai_socktype Which socket type to match. SOCK_STREAM (TCP), SOCK_DGRAM (UDP), 0 (any)
ai_protocol Specific protocol. Usually left as 0. 0 (auto), IPPROTO_TCP, IPPROTO_UDP
โš ๏ธ Rule: All fields NOT in the table above (ai_addrlen, ai_canonname, ai_addr, ai_next) must be initialized to 0 or NULL when used in hints. Always use memset(&hints, 0, sizeof(hints)) before setting any fields.

Basic hints Setup Pattern

struct addrinfo hints;

memset(&hints, 0, sizeof(hints));  /* Zero EVERYTHING first */
hints.ai_family   = AF_UNSPEC;     /* Accept IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM;   /* TCP only */
/* hints.ai_protocol = 0; already zero from memset */
/* hints.ai_flags   = 0; already zero โ€” no special flags */

2. hints.ai_family โ€” Controlling Address Family

This field tells getaddrinfo() which IP version’s addresses you want in the results.

Value Meaning Use Case
AF_UNSPEC Return any address family โ€” both IPv4 and IPv6 if available. Best for protocol-independent programs. Most common choice.
AF_INET Return only IPv4 addresses. Legacy programs that must use IPv4. Avoid if possible.
AF_INET6 Return only IPv6 addresses. IPv6-only environments. Can combine with AI_V4MAPPED to also handle IPv4 clients.
/* Example: Get only IPv6 addresses */
memset(&hints, 0, sizeof(hints));
hints.ai_family   = AF_INET6;
hints.ai_socktype = SOCK_STREAM;

err = getaddrinfo("www.example.com", "https", &hints, &result);
/* result will only contain AF_INET6 entries */

3. hints.ai_socktype โ€” Controlling Socket Type

This field filters results by socket type. If you don’t set it (leave as 0), you may get both SOCK_STREAM and SOCK_DGRAM entries for the same address.

hints.ai_socktype value Effect on results
SOCK_STREAM Only return entries for TCP connections. Port lookup uses TCP.
SOCK_DGRAM Only return entries for UDP. Port lookup uses UDP.
0 (not set) Return both SOCK_STREAM and SOCK_DGRAM entries. If a service is available on both TCP and UDP, you get two nodes for each IP address.
๐Ÿ’ก Practical Tip: Always set ai_socktype to either SOCK_STREAM or SOCK_DGRAM. Leaving it as 0 doubles the number of result entries (one for TCP + one for UDP per IP address), which is usually not what you want and requires extra filtering in your loop.

4. AI_* Flags โ€” Complete Reference

The ai_flags field is formed by ORing together one or more of these constants. Each flag modifies the behaviour of getaddrinfo() in a specific way.

๐Ÿšฉ AI_PASSIVE โ€” For Listening/Server Sockets

When this flag is set, getaddrinfo() returns addresses suitable for a passive open โ€” meaning a server socket that will bind() and then accept() connections.

  • host must be NULL when AI_PASSIVE is used.
  • The IP address in the returned structures is set to the wildcard address: INADDR_ANY for IPv4 (= 0.0.0.0) or IN6ADDR_ANY_INIT for IPv6 (= ::). This means “listen on all interfaces”.
  • If AI_PASSIVE is NOT set and host is NULL, the address is set to the loopback address (127.0.0.1 for IPv4, ::1 for IPv6) โ€” suitable for connect(), not bind().
/* Server setup with AI_PASSIVE */
struct addrinfo hints, *result;

memset(&hints, 0, sizeof(hints));
hints.ai_family   = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags    = AI_PASSIVE;    /* We are a server */

/* Pass NULL for host โ€” AI_PASSIVE fills in wildcard address */
err = getaddrinfo(NULL, "8080", &hints, &result);
if (err != 0) { /* handle error */ }

/* result->ai_addr will contain INADDR_ANY:8080 (0.0.0.0:8080) */
/* Ready to use with bind() */
int sfd = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
bind(sfd, result->ai_addr, result->ai_addrlen);
listen(sfd, 10);

Condition IP in result Used for
AI_PASSIVE set, host = NULL 0.0.0.0 (IPv4) or :: (IPv6) โ€” wildcard = all interfaces Server bind() โ€” accept from any interface
AI_PASSIVE NOT set, host = NULL 127.0.0.1 (IPv4) or ::1 (IPv6) โ€” loopback Client connect() to localhost
host = “192.168.1.5” (any flag) 192.168.1.5 โ€” specific address Client connect() to specific server

๐Ÿšฉ AI_CANONNAME โ€” Get the Canonical Hostname

When this flag is set (and host is not NULL), getaddrinfo() fills the ai_canonname field in the first addrinfo node with the official canonical hostname of the host.

For example, “www.google.com” may actually be a CNAME (alias) for “www3.l.google.com”. The canonical name is the true, official hostname โ€” the one the DNS CNAME chain ultimately resolves to.

struct addrinfo hints, *result;

memset(&hints, 0, sizeof(hints));
hints.ai_family   = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags    = AI_CANONNAME;   /* Request canonical name */

err = getaddrinfo("www.example.com", "http", &hints, &result);

/* Canonical name is in the FIRST node only */
if (result != NULL && result->ai_canonname != NULL) {
    printf("Canonical name: %s\n", result->ai_canonname);
}
/* All subsequent nodes (result->ai_next etc.) have ai_canonname = NULL */

๐Ÿšฉ AI_NUMERICHOST โ€” Force Numeric Host String

This flag forces getaddrinfo() to treat the host argument as a numeric address string (like “192.168.1.1” or “::1”) and skip DNS name resolution entirely.

When to use it:

  • When you already have an IP address string (not a hostname) and want to avoid the overhead of a DNS lookup.
  • When you need guaranteed non-blocking behaviour (DNS lookup can block).
  • When you want to detect invalid numeric strings (getaddrinfo() returns EAI_NONAME if host is not a valid numeric address).
memset(&hints, 0, sizeof(hints));
hints.ai_family   = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags    = AI_NUMERICHOST;  /* No DNS lookup โ€” host must be numeric */

/* This works: "192.168.1.1" is a valid numeric IPv4 address */
err = getaddrinfo("192.168.1.1", "80", &hints, &result);

/* This FAILS (returns EAI_NONAME): "www.example.com" is not numeric */
err = getaddrinfo("www.example.com", "80", &hints, &result);
/* err == EAI_NONAME */

๐Ÿšฉ AI_NUMERICSERV โ€” Force Numeric Port String

Similar to AI_NUMERICHOST but for the service argument. When set, this flag forces getaddrinfo() to interpret service as a decimal port number string โ€” no /etc/services lookup is performed.

memset(&hints, 0, sizeof(hints));
hints.ai_family   = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags    = AI_NUMERICSERV;  /* service must be numeric port string */

/* Works: "443" is a valid port number */
err = getaddrinfo("www.example.com", "443", &hints, &result);

/* Fails: "https" is a service name, not a number */
err = getaddrinfo("www.example.com", "https", &hints, &result);

Combining AI_NUMERICHOST and AI_NUMERICSERV is useful when you know you have both a numeric IP and a numeric port โ€” it skips all name resolution completely and is fast.

๐Ÿšฉ AI_ADDRCONFIG โ€” Only Return Configured Protocols

When AI_ADDRCONFIG is set, getaddrinfo() only returns address types that your local system actually has configured. The rules are:

  • Return IPv4 addresses only if the local system has at least one non-loopback IPv4 address configured.
  • Return IPv6 addresses only if the local system has at least one non-loopback IPv6 address configured.

This is useful to avoid getting back IPv6 addresses when your machine has no IPv6 connectivity, which would cause connection attempts to fail.

System Configuration AI_ADDRCONFIG effect
Has IPv4 address (e.g., 192.168.1.5), no IPv6 Only IPv4 results returned, IPv6 results filtered out
Has both IPv4 and IPv6 addresses Both types returned (same as without this flag)
Only loopback (127.0.0.1, ::1) โ€” no real network Loopback does NOT count โ€” neither type returned
hints.ai_flags = AI_ADDRCONFIG;
/* On a machine with only IPv4: result will NOT include IPv6 entries */
/* On a dual-stack machine: result includes both IPv4 and IPv6 entries */

๐Ÿšฉ AI_V4MAPPED and AI_ALL โ€” IPv4-Mapped IPv6 Addresses

These two flags work together and are relevant only when hints.ai_family = AF_INET6. They deal with a concept called IPv4-mapped IPv6 addresses โ€” a way to represent an IPv4 address inside an IPv6 structure.

What is an IPv4-mapped IPv6 address?

An IPv4-mapped IPv6 address looks like ::ffff:192.168.1.5. The first 80 bits are zero, the next 16 bits are all-ones (0xffff), and the last 32 bits contain the IPv4 address. This allows an IPv6-only program to communicate with IPv4 hosts.

Flag Condition Result
AI_V4MAPPED hints.ai_family = AF_INET6 AND no native IPv6 address found Returns IPv4 addresses converted to IPv4-mapped IPv6 format (::ffff:x.x.x.x)
AI_V4MAPPED | AI_ALL hints.ai_family = AF_INET6 Returns BOTH native IPv6 addresses AND IPv4 addresses in mapped format โ€” regardless of whether IPv6 was found

IPv4-Mapped IPv6 Address Layout (128 bits total)
80 bits of zeros
0000:0000:0000:0000:0000
16 bits of ones
:ffff:
32-bit IPv4 address
192.168.1.5
= ::ffff:192.168.1.5
/* Request IPv6 results, fall back to IPv4-mapped if no IPv6 found */
memset(&hints, 0, sizeof(hints));
hints.ai_family   = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags    = AI_V4MAPPED;

err = getaddrinfo("ipv4only.example.com", "http", &hints, &result);
/* Even if the host has no IPv6, result will contain ::ffff:x.x.x.x */

5. Special Cases: NULL host and NULL service

You can pass NULL for either host or service, but not both.

host service AI_PASSIVE? IP in result Port in result Use case
NULL “80” Yes 0.0.0.0 or :: 80 Server listening on all interfaces, port 80
NULL “80” No 127.0.0.1 or ::1 80 Client connecting to localhost:80
“example.com” NULL No Resolved IP 0 Only resolving a hostname to IP (no port needed yet)
NULL NULL N/A โŒ NOT ALLOWED โ€” returns error Invalid โ€” at least one must be provided

Complete Server Example Using AI_PASSIVE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>

#define PORT "9000"
#define BACKLOG 10

int main() {
    struct addrinfo hints, *result, *rp;
    int listenfd, err;
    int optval = 1;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = AF_UNSPEC;   /* Support IPv4 and IPv6 */
    hints.ai_socktype = SOCK_STREAM; /* TCP */
    hints.ai_flags    = AI_PASSIVE;  /* We are a server โ€” wildcard bind */

    err = getaddrinfo(NULL, PORT, &hints, &result);
    if (err != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
        exit(EXIT_FAILURE);
    }

    /* Try each result โ€” bind to the first one that works */
    for (rp = result; rp != NULL; rp = rp->ai_next) {
        listenfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (listenfd == -1) continue;

        /* Allow reuse of port (avoids "address already in use" after restart) */
        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

        if (bind(listenfd, rp->ai_addr, rp->ai_addrlen) == 0)
            break;  /* bind succeeded */

        close(listenfd);
    }

    freeaddrinfo(result);

    if (rp == NULL) {
        fprintf(stderr, "Could not bind to port %s\n", PORT);
        exit(EXIT_FAILURE);
    }

    listen(listenfd, BACKLOG);
    printf("Server listening on port %s...\n", PORT);

    /* Accept loop would go here */
    close(listenfd);
    return 0;
}

6. Combining AI_* Flags

Multiple AI_* flags are combined using bitwise OR (|).

Combination Meaning Typical use
AI_PASSIVE Server wildcard bind Server programs โ€” bind to all interfaces
AI_CANONNAME Get official hostname Debugging, logging, or displaying resolved hostnames
AI_NUMERICHOST | AI_NUMERICSERV Both host and service are numeric strings โ€” skip all name lookups Performance-critical code with known IP and port
AI_ADDRCONFIG Only return address types the system can actually use Client programs on machines that may lack IPv6
AI_V4MAPPED | AI_ALL IPv6-only code that also needs to reach IPv4 hosts IPv6 servers using mapped addresses
AI_ADDRCONFIG | AI_V4MAPPED Common dual-stack client pattern: return configured types, plus map IPv4 into IPv6 if needed Dual-stack client programs
/* Dual-stack client: configured types + V4MAPPED fallback */
hints.ai_family = AF_INET6;
hints.ai_flags  = AI_ADDRCONFIG | AI_V4MAPPED;
/* On IPv6-capable system: get native IPv6 + IPv4-mapped addresses */
/* On IPv4-only system: get IPv4-mapped addresses */

๐ŸŽฏ Interview Questions โ€” hints & AI_* Flags

Q1. What is the purpose of the AI_PASSIVE flag?

Answer: AI_PASSIVE tells getaddrinfo() to return socket address structures suitable for a server (passive) socket โ€” one that will call bind() and listen(). When this flag is set and host is NULL, the IP address in the returned structures is the wildcard address (0.0.0.0 for IPv4 or :: for IPv6), which means the socket will accept connections on all network interfaces. Without AI_PASSIVE, NULL host returns the loopback address instead.

Q2. What is the difference between AI_NUMERICHOST and AI_NUMERICSERV?

Answer: AI_NUMERICHOST forces the host argument to be interpreted as a numeric IP address string (IPv4 dotted decimal or IPv6 hex notation) โ€” no DNS lookup is performed. AI_NUMERICSERV forces the service argument to be interpreted as a decimal port number string โ€” no /etc/services lookup is performed. Both flags skip name resolution, which is faster and avoids potential DNS timeouts.

Q3. What does AI_ADDRCONFIG do and when would you use it?

Answer: AI_ADDRCONFIG filters the returned addresses so that only address types that the local system has configured (excluding loopback) are included. If the machine has no IPv6 address, IPv6 results are filtered out even if the server has an IPv6 address. This is useful in client programs on machines with limited connectivity โ€” it prevents getting back IPv6 addresses that cannot actually be reached.

Q4. What is an IPv4-mapped IPv6 address and how does AI_V4MAPPED use it?

Answer: An IPv4-mapped IPv6 address encodes an IPv4 address inside an IPv6 address structure, using the format ::ffff:x.x.x.x (80 zeros, then 16 ones, then the 32-bit IPv4 address). AI_V4MAPPED is used when hints.ai_family = AF_INET6 and the host has no IPv6 address โ€” it tells getaddrinfo() to return the host’s IPv4 address converted to mapped format instead of failing. This allows IPv6-only code to communicate with IPv4-only hosts.

Q5. What happens if hints.ai_socktype is set to 0?

Answer: When ai_socktype is 0 in hints, getaddrinfo() returns entries for any socket type โ€” typically both SOCK_STREAM (TCP) and SOCK_DGRAM (UDP). If the service is available on both protocols, you get two entries per IP address: one for TCP and one for UDP. This doubles the size of the result list and usually requires extra filtering. Best practice is to always set ai_socktype explicitly.

Q6. What is the difference between AI_ALL and AI_V4MAPPED alone?

Answer: AI_V4MAPPED alone: returns IPv4-mapped IPv6 entries only if no native IPv6 addresses were found (as a fallback). AI_V4MAPPED combined with AI_ALL: returns both native IPv6 entries AND IPv4-mapped entries โ€” regardless of whether native IPv6 was found. AI_ALL has no effect without AI_V4MAPPED.

Q7. How do you make getaddrinfo() return an address for binding to all interfaces on a specific port?

Answer: Pass NULL for host and set AI_PASSIVE in hints.ai_flags:

hints.ai_flags = AI_PASSIVE;
getaddrinfo(NULL, "8080", &hints, &result);
/* result->ai_addr contains the wildcard address (0.0.0.0:8080 or :::8080) */

Q8. Can you combine AI_PASSIVE with a non-NULL host argument?

Answer: Technically the call may succeed, but it is semantically wrong. AI_PASSIVE is designed to be used with host = NULL to get a wildcard address. If you specify a hostname with AI_PASSIVE, the returned address will be the specific host address rather than a wildcard, which defeats the purpose. The standard practice is always to pass host = NULL when AI_PASSIVE is set.

Continue Learning

Next: getnameinfo() โ€” converting socket addresses back to names

โ†’ Part 4: getnameinfo() โ† Part 2: getaddrinfo() Basics

Leave a Reply

Your email address will not be published. Required fields are marked *