getaddrinfo() โ€” The Modern Address Resolver Function Signature, addrinfo Structure & Result Linked List

 

getaddrinfo() โ€” The Modern Address Resolver
Part 2 โ€” Function Signature, addrinfo Structure & Result Linked List
๐Ÿ“š Chapter 59 โ€“ TLPI
๐Ÿ” Name Resolution
๐Ÿ”— Linked List Output

What Does getaddrinfo() Do?

Imagine you want to connect to a server. You know the hostname (like “www.example.com”) and the service (like “http”). But your socket API needs an IP address and a port number in a very specific binary structure. getaddrinfo() does that entire translation for you โ€” from human-readable names to ready-to-use socket address structures.

It was defined in POSIX.1g as the modern, thread-safe, IPv6-capable replacement for the obsolete gethostbyname() and getservbyname() functions.

Key Terms

getaddrinfo() struct addrinfo ai_family ai_socktype ai_protocol ai_addr ai_next freeaddrinfo() AF_INET AF_INET6 SOCK_STREAM SOCK_DGRAM

1. Function Signature
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *host,
                const char *service,
                const struct addrinfo *hints,
                struct addrinfo **result);

/* Returns 0 on success, or a nonzero error code on failure */

Parameters Explained

Parameter Type What to pass
host const char * Hostname (“www.example.com”) OR numeric IP (“192.168.1.1” or “::1”). Can be NULL for passive/listen sockets.
service const char * Service name (“http”, “ssh”) OR decimal port number as a string (“80”, “22”). Can be NULL if only resolving a host.
hints const struct addrinfo * Optional filter criteria (address family, socket type, etc). Pass NULL to accept any.
result struct addrinfo ** OUTPUT: pointer to pointer. getaddrinfo() allocates and fills a linked list here.
โš ๏ธ Important: You cannot pass both host and service as NULL at the same time. At least one must be specified.

Return Value

Return Value Meaning
0 Success โ€” result points to a valid linked list of addrinfo structures.
Nonzero Error. Use gai_strerror(returnValue) to get a human-readable error message. Do NOT use perror() here.

Error Handling Example

#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>

int main() {
    struct addrinfo *result;
    int err;

    err = getaddrinfo("www.example.com", "http", NULL, &result);
    if (err != 0) {
        /* Use gai_strerror(), NOT perror() */
        fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(err));
        return 1;
    }

    /* ... use result ... */

    freeaddrinfo(result);   /* Always free when done! */
    return 0;
}

2. The addrinfo Structure โ€” Understanding Each Field

The struct addrinfo is the core data structure returned by getaddrinfo(). Each node in the linked list is one of these structures.

struct addrinfo {
    int              ai_flags;      /* Input flags (AI_* constants) โ€” used in hints */
    int              ai_family;     /* Address family: AF_INET or AF_INET6 */
    int              ai_socktype;   /* Socket type: SOCK_STREAM or SOCK_DGRAM */
    int              ai_protocol;   /* Protocol for the socket (usually 0) */
    size_t           ai_addrlen;    /* Size in bytes of the struct pointed by ai_addr */
    char            *ai_canonname; /* Canonical hostname (only in first node, if requested) */
    struct sockaddr *ai_addr;       /* Pointer to the actual socket address structure */
    struct addrinfo *ai_next;       /* Pointer to next node in linked list (NULL if last) */
};

Field-by-Field Explanation

Field What it tells you Typical Values
ai_family Which IP version this address uses. AF_INET (IPv4), AF_INET6 (IPv6)
ai_socktype Whether this address is for TCP or UDP. SOCK_STREAM (TCP), SOCK_DGRAM (UDP)
ai_protocol Protocol number (usually 0 = auto-select). 0, IPPROTO_TCP, IPPROTO_UDP
ai_addrlen How many bytes the ai_addr structure occupies. IPv4 and IPv6 structures have different sizes. sizeof(sockaddr_in) for IPv4, sizeof(sockaddr_in6) for IPv6
ai_addr Pointer to the actual socket address. Pass this directly to connect(), bind(), etc. Points to sockaddr_in (IPv4) or sockaddr_in6 (IPv6)
ai_canonname Official/canonical name of the host. Only filled if AI_CANONNAME flag used. Only present in first node. “www.example.com” or NULL
ai_next Pointer to the next addrinfo node in the list. NULL marks the end of the list. pointer or NULL
ai_flags Used only in the hints argument (input), not in result structures. AI_PASSIVE, AI_CANONNAME, etc.

Connecting ai_family, ai_socktype, ai_protocol to socket()

The three fields ai_family, ai_socktype, and ai_protocol give you exactly the three arguments needed for the socket() system call:

/* These values come directly from an addrinfo node */
int sockfd = socket(ai_ptr->ai_family,
                    ai_ptr->ai_socktype,
                    ai_ptr->ai_protocol);

This is intentional design โ€” making it easy to create the correct socket type.

3. The Result Linked List โ€” Why Multiple Results?

getaddrinfo() returns a linked list of addrinfo structures, not just one. Why? Because a single hostname+service combination can map to multiple valid addresses.

Reasons for Multiple Results

Reason Example
Host has multiple network interfaces A server with both a WiFi and an Ethernet interface may have two different IP addresses. Both are returned.
Host has both IPv4 and IPv6 addresses A modern server may be reachable via 192.168.1.5 (AF_INET) and 2001:db8::1 (AF_INET6). Both entries appear.
Service available on both TCP and UDP If hints.ai_socktype is 0 (any type), and the service runs on both TCP and UDP, you get entries for both SOCK_STREAM and SOCK_DGRAM.
DNS round-robin A hostname may resolve to multiple IP addresses for load balancing. All are returned.

Linked List Memory Layout

Node 1
ai_family: AF_INET
ai_socktype: SOCK_STREAM
ai_addr โ†’ 93.184.216.34:80
ai_canonname: “www.example.com” (only here)
ai_next โ†’
โ†’
Node 2
ai_family: AF_INET6
ai_socktype: SOCK_STREAM
ai_addr โ†’ [2606:2800::]:80
ai_canonname: NULL
ai_next โ†’
โ†’
Node 3
ai_family: AF_INET
ai_socktype: SOCK_DGRAM
ai_addr โ†’ 93.184.216.34:80
ai_canonname: NULL
ai_next = NULL

result pointer โ†’ Node 1 โ†’ Node 2 โ†’ Node 3 โ†’ NULL

4. Complete Working Code Example

This example shows how to use getaddrinfo() to connect to a server โ€” the standard pattern used in real programs:

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

int main(int argc, char *argv[]) {
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    int sockfd, err;

    if (argc != 3) {
        fprintf(stderr, "Usage: %s <hostname> <service>\n", argv[0]);
        fprintf(stderr, "Example: %s www.example.com http\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    /* Step 1: Set up hints โ€” we want a TCP socket, any address family */
    memset(&hints, 0, sizeof(hints));  /* Zero out all fields first */
    hints.ai_family   = AF_UNSPEC;     /* Accept IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM;   /* TCP socket */

    /* Step 2: Call getaddrinfo() */
    err = getaddrinfo(argv[1], argv[2], &hints, &result);
    if (err != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
        exit(EXIT_FAILURE);
    }

    /* Step 3: Try each address in the list until one succeeds */
    for (rp = result; rp != NULL; rp = rp->ai_next) {
        /* Print which address we are trying */
        char addr_str[INET6_ADDRSTRLEN];
        void *addr_ptr;

        if (rp->ai_family == AF_INET) {
            struct sockaddr_in *ipv4 = (struct sockaddr_in *)rp->ai_addr;
            addr_ptr = &(ipv4->sin_addr);
        } else {
            struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)rp->ai_addr;
            addr_ptr = &(ipv6->sin6_addr);
        }
        inet_ntop(rp->ai_family, addr_ptr, addr_str, sizeof(addr_str));
        printf("Trying address: %s (family: %s)\n",
               addr_str,
               rp->ai_family == AF_INET ? "IPv4" : "IPv6");

        /* Create socket using fields directly from addrinfo */
        sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sockfd == -1) {
            perror("socket");
            continue;  /* try next address */
        }

        /* Try to connect */
        if (connect(sockfd, rp->ai_addr, rp->ai_addrlen) != -1) {
            printf("Connected successfully!\n");
            break;  /* success โ€” stop trying */
        }

        perror("connect");
        close(sockfd);  /* this address failed, try next */
    }

    /* Step 4: Always free the result list */
    freeaddrinfo(result);

    if (rp == NULL) {
        fprintf(stderr, "Could not connect to any address\n");
        exit(EXIT_FAILURE);
    }

    /* ... use sockfd for communication ... */
    close(sockfd);
    return 0;
}

Key Points from the Code

Pattern Why It Matters
memset(&hints, 0, sizeof(hints)) Zeroing hints is mandatory. Uninitialized fields can cause undefined behavior.
AF_UNSPEC in hints Tells getaddrinfo() to return both IPv4 and IPv6 results. This makes code protocol-independent.
Loop through result list with rp = rp->ai_next Standard pattern โ€” try each address until connect() succeeds. Gives automatic fallback.
socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol) Using fields directly from addrinfo is the correct, portable way to create a socket.
freeaddrinfo(result) getaddrinfo() dynamically allocates memory. You MUST free it with freeaddrinfo() to avoid memory leaks.

5. getaddrinfo() and DNS

When you pass a hostname (not a numeric IP) to getaddrinfo(), it may need to contact a DNS server to resolve the name to an IP address. This is a network operation and can take time โ€” it is a blocking call.

Input Type DNS Query Made? Example
Hostname (“www.example.com”) Yes โ€” DNS lookup required May take milliseconds to seconds
Numeric IP string (“93.184.216.34”) No โ€” parsed directly Instant, no network needed
Numeric IP with AI_NUMERICHOST flag No โ€” forced numeric interpretation Safe in performance-sensitive code
๐Ÿ’ก Tip: In production servers, avoid calling getaddrinfo() repeatedly in a tight loop for the same hostname โ€” the DNS lookup cost adds up. Cache the result if you need to connect multiple times.

6. Memory Management โ€” freeaddrinfo()

getaddrinfo() dynamically allocates memory for all the addrinfo structures and the sockaddr structures they point to. You are responsible for releasing this memory.

#include <netdb.h>

void freeaddrinfo(struct addrinfo *result);

Call freeaddrinfo(result) when you are done with the list. It frees the entire linked list in one call โ€” you do not need to loop and free each node manually.

๐Ÿšซ Common Mistake: Forgetting to call freeaddrinfo() causes a memory leak. This is especially bad in long-running server programs that call getaddrinfo() repeatedly.
/* Correct pattern */
struct addrinfo *result;
getaddrinfo("host", "service", &hints, &result);

/* ... use result ... */

freeaddrinfo(result);   /* Release ALL nodes in the list */
result = NULL;          /* Good practice: avoid dangling pointer */

๐ŸŽฏ Interview Questions โ€” getaddrinfo() Basics

Q1. What does getaddrinfo() return and why is it a linked list?

Answer: getaddrinfo() returns a pointer to a linked list of struct addrinfo nodes via its result output parameter. It returns a list (not a single value) because a hostname + service combination can resolve to multiple addresses: for example, a host may have both IPv4 and IPv6 addresses, or may have multiple IP addresses from DNS round-robin, or a service may be available on both TCP and UDP.

Q2. What three fields of addrinfo map directly to the three arguments of socket()?

Answer: ai_family maps to the domain argument, ai_socktype maps to the type argument, and ai_protocol maps to the protocol argument of socket(). This is by design โ€” the addrinfo structure gives you exactly what you need to create the right socket.

Q3. What happens if you forget to call freeaddrinfo()?

Answer: You get a memory leak. getaddrinfo() dynamically allocates all the addrinfo nodes and their sockaddr structures on the heap. If freeaddrinfo() is not called, this memory is never returned to the OS. In long-running servers that repeatedly call getaddrinfo(), this can eventually exhaust all available memory.

Q4. Why should you use gai_strerror() instead of perror() for getaddrinfo() errors?

Answer: getaddrinfo() does not set errno โ€” it returns its own error codes (like EAI_NONAME, EAI_AGAIN, EAI_FAIL). These error codes have their own set of error messages that are completely separate from the standard errno messages. gai_strerror() converts these error codes to human-readable strings. Using perror() would read errno which may be set to something unrelated, giving a misleading error message.

Q5. Why must you call memset(&hints, 0, …) before setting hints fields?

Answer: The hints structure is allocated on the stack (as a local variable) and its fields that you don’t explicitly set may contain garbage values from whatever was previously on the stack. getaddrinfo() reads all fields of hints, not just the ones you set. Garbage values in unused fields can cause it to return wrong results or fail. zeroing with memset() ensures all unused fields are 0 (which means “don’t care / use default”).

Q6. What is the standard loop pattern for using getaddrinfo() results to connect?

Answer: The standard pattern is to iterate through the linked list, try to create a socket and connect for each node, and break on the first success:

for (rp = result; rp != NULL; rp = rp->ai_next) {
    sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
    if (sockfd == -1) continue;
    if (connect(sockfd, rp->ai_addr, rp->ai_addrlen) != -1) break;
    close(sockfd);
}
freeaddrinfo(result);
if (rp == NULL) { /* all addresses failed */ }

Q7. What is ai_addrlen and why is it needed?

Answer: ai_addrlen gives the size in bytes of the socket address structure pointed to by ai_addr. Since getaddrinfo() can return both IPv4 (struct sockaddr_in, 16 bytes) and IPv6 (struct sockaddr_in6, 28 bytes) addresses, and functions like connect() need to know the size of the address structure, ai_addrlen provides this information. You pass rp->ai_addr and rp->ai_addrlen directly to connect().

Continue Learning

Next: getaddrinfo() hints argument โ€” all AI_* flags explained with examples

โ†’ Part 3: hints & AI_* Flags โ† Part 1: /etc/services

Leave a Reply

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