Sockets: Internet Domains Overview, Address Families & getaddrinfo()

 

Chapter 59 โ€“ Sockets: Internet Domains
Part 1 of 3 ย |ย  Overview, Address Families & getaddrinfo()
๐Ÿ“ก Topic
Internet Sockets
๐Ÿ“˜ Book
TLPI Ch. 59
๐ŸŽฏ Level
Intermediate

What Are Internet Domain Sockets?

In the previous chapters we looked at UNIX domain sockets which let processes on the same machine talk to each other. Internet domain sockets go one big step further โ€” they let processes talk to each other across a network, even if they are on completely different machines on the other side of the world.

Linux supports two internet address families:

  • AF_INET โ€“ IPv4 (32-bit addresses like 192.168.1.1)
  • AF_INET6 โ€“ IPv6 (128-bit addresses like 2001:db8::1)

The key challenge with internet sockets compared to UNIX domain sockets is that you have to deal with host addresses and port numbers, and both IPv4 and IPv6 use different structures to represent them. The modern way to handle this complexity is to use getaddrinfo().

Key Terms in This File

AF_INET AF_INET6 AF_UNSPEC getaddrinfo() freeaddrinfo() addrinfo struct AI_PASSIVE AI_NUMERICSERV NI_MAXHOST NI_MAXSERV getnameinfo() sockaddr_storage

1. Why getaddrinfo()? The Old Problem

In the old days, programmers used separate functions for IPv4 and IPv6 โ€” like gethostbyname() for IPv4 only. This made code that only worked with one address family and broke when IPv6 came along.

getaddrinfo() was introduced to solve this. It is the single modern function that handles both IPv4 and IPv6 transparently. You give it a hostname and a service name/port, and it gives you back a linked list of socket address structures ready to use. Your code just walks the list and tries each one until something works.

How getaddrinfo() works โ€” high level:

You provide getaddrinfo() does You get back
hostname (“localhost”)
service/port (“50000”)
DNS lookup + fills in
all address structures
Linked list of
addrinfo structs
NULL (for server,
wildcard address)
Sets INADDR_ANY or
IN6ADDR_ANY_INIT
Structs for binding
to all interfaces

2. The struct addrinfo โ€“ Your Blueprint

Before calling getaddrinfo(), you fill in a hints variable of type struct addrinfo to tell it what kind of socket you want. Here is what the struct looks like:

#include <netdb.h>

struct addrinfo {
    int              ai_flags;      /* Input flags (AI_PASSIVE, AI_NUMERICSERV ...) */
    int              ai_family;     /* AF_INET, AF_INET6, or AF_UNSPEC             */
    int              ai_socktype;   /* SOCK_STREAM or SOCK_DGRAM                   */
    int              ai_protocol;   /* Protocol (usually 0 = auto)                 */
    socklen_t        ai_addrlen;    /* Length of socket address in ai_addr         */
    struct sockaddr *ai_addr;       /* Pointer to socket address structure         */
    char            *ai_canonname; /* Canonical hostname (if AI_CANONNAME set)    */
    struct addrinfo *ai_next;       /* Pointer to next node in linked list         */
};

The important fields for our purposes are:

Field What you set in hints Meaning
ai_family AF_UNSPEC Accept both IPv4 and IPv6 results
ai_socktype SOCK_STREAM TCP (reliable, connection-oriented)
ai_flags AI_PASSIVE For server: use wildcard address
ai_flags AI_NUMERICSERV Port number is a number not a name

Fields you do not use in hints should be set to NULL or 0 using memset() before you start filling fields. This avoids garbage values.

3. Calling getaddrinfo() โ€” The Function Signature
#include <netdb.h>

int getaddrinfo(
    const char *node,           /* hostname or NULL for wildcard (server) */
    const char *service,        /* port number as string, e.g. "50000"    */
    const struct addrinfo *hints, /* what kind of socket we want          */
    struct addrinfo **result    /* OUTPUT: linked list of results         */
);

/* Returns 0 on success, nonzero error code on failure */
/* Use gai_strerror(errcode) to get a readable error string */

When the function succeeds, result points to a linked list of addrinfo structures. You walk through them one by one trying to create and connect/bind a socket. Once you are done with the list you must free it using freeaddrinfo(result).

What the result linked list looks like:

result โ†’ addrinfo[0] (IPv4) โ†’ addrinfo[1] (IPv6) โ†’ NULL
ai_family AF_INET ai_family AF_INET6
ai_addr sockaddr_in* ai_addr sockaddr_in6*
ai_addrlen sizeof(sockaddr_in) ai_addrlen sizeof(sockaddr_in6)
ai_next โ†’ addrinfo[1] ai_next NULL

4. Minimal Working Example โ€“ Using getaddrinfo()

This example shows how to set up hints and call getaddrinfo() correctly, which is the same pattern used in both the server and client programs in this chapter.

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

#define PORT "50000"

int main(void)
{
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    int sfd;

    /* Step 1: Zero out hints โ€” very important! */
    memset(&hints, 0, sizeof(struct addrinfo));

    /* Step 2: Fill in what we want */
    hints.ai_family   = AF_UNSPEC;       /* IPv4 or IPv6, we don't mind      */
    hints.ai_socktype = SOCK_STREAM;     /* TCP socket                        */
    hints.ai_flags    = AI_NUMERICSERV;  /* Port is a number, not a name     */

    /* Step 3: Call getaddrinfo โ€” NULL node means connect to localhost */
    int err = getaddrinfo("localhost", PORT, &hints, &result);
    if (err != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
        return 1;
    }

    /* Step 4: Walk the list, try each address */
    for (rp = result; rp != NULL; rp = rp->ai_next) {
        sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sfd == -1)
            continue;   /* socket() failed, try next */

        if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
            break;      /* connect() succeeded! */

        close(sfd);     /* connect failed, close and try next */
    }

    if (rp == NULL) {
        fprintf(stderr, "Could not connect to any address\n");
        freeaddrinfo(result);
        return 1;
    }

    printf("Connected! Socket fd = %d\n", sfd);

    /* Step 5: Always free the list when done */
    freeaddrinfo(result);

    close(sfd);
    return 0;
}

Key point: The pattern โ€” zero hints, fill fields, call getaddrinfo, loop and try each result, freeaddrinfo โ€” is the exact same pattern used in the real server and client code. Understand this loop and the rest is easy.

5. getnameinfo() โ€“ Turning an Address Back into Text

getnameinfo() is the reverse of getaddrinfo(). You give it a socket address structure and it gives you back a readable hostname and port string. This is used in the server to display which client just connected.

#include <netdb.h>

int getnameinfo(
    const struct sockaddr *addr, /* socket address to look up          */
    socklen_t addrlen,           /* length of the address              */
    char *host,                  /* output buffer for hostname         */
    socklen_t hostlen,           /* size of host buffer (NI_MAXHOST)   */
    char *serv,                  /* output buffer for service/port     */
    socklen_t servlen,           /* size of serv buffer (NI_MAXSERV)   */
    int flags                    /* 0 for default behavior             */
);
/* Returns 0 on success, nonzero error code on failure */

Use the constants NI_MAXHOST and NI_MAXSERV from <netdb.h> as buffer sizes โ€” they are guaranteed to be large enough for any hostname or service name.

/* Example: print host and port of a connected client */
char host[NI_MAXHOST];
char service[NI_MAXSERV];
struct sockaddr_storage claddr;  /* Large enough for IPv4 or IPv6 */
socklen_t addrlen = sizeof(claddr);

/* accept() fills claddr */
int cfd = accept(lfd, (struct sockaddr *) &claddr, &addrlen);

if (getnameinfo((struct sockaddr *) &claddr, addrlen,
                host, NI_MAXHOST,
                service, NI_MAXSERV, 0) == 0) {
    printf("Connection from host=%s port=%s\n", host, service);
} else {
    printf("Connection from unknown client\n");
}

6. struct sockaddr_storage โ€“ The Universal Address Buffer

When the server accepts a connection, it needs a buffer to store the client’s address. The problem: it doesn’t know if the client is IPv4 or IPv6. struct sockaddr_in is only big enough for IPv4. struct sockaddr_in6 is only big enough for IPv6.

The solution: struct sockaddr_storage. It is guaranteed to be large enough to hold any socket address (IPv4, IPv6, or anything else). Always use it when you don’t know which address family you’ll receive.

Struct Holds When to use
struct sockaddr_in IPv4 address only Only if you know it’s IPv4
struct sockaddr_in6 IPv6 address only Only if you know it’s IPv6
struct sockaddr_storage Either IPv4 or IPv6 General purpose โ€” use this!
/* Typical server: store client address without knowing family */
struct sockaddr_storage claddr;
socklen_t addrlen = sizeof(struct sockaddr_storage);

int cfd = accept(lfd, (struct sockaddr *) &claddr, &addrlen);
/* Now cast to sockaddr* when needed for getnameinfo() etc. */

๐ŸŽฏ Interview Questions โ€“ Part 1

Q1. What is the difference between AF_INET and AF_UNSPEC in getaddrinfo hints?

AF_INET restricts results to IPv4 only. AF_UNSPEC allows the function to return both IPv4 and IPv6 results. Using AF_UNSPEC makes your code protocol-independent and future-proof.

Q2. Why must you call freeaddrinfo() after getaddrinfo()?

getaddrinfo() dynamically allocates memory for the linked list of addrinfo structures. If you don’t call freeaddrinfo(), that memory leaks. It’s the same concept as malloc()/free().

Q3. What does AI_PASSIVE flag do?

When set in hints, AI_PASSIVE tells getaddrinfo() that the returned address will be used for bind() on a server socket. With a NULL node and AI_PASSIVE, it returns the wildcard address (0.0.0.0 for IPv4, :: for IPv6), which means the server listens on all network interfaces.

Q4. What is struct sockaddr_storage and why is it used?

It is a generic socket address structure large enough to hold any address family’s address (IPv4 or IPv6). It is used when you don’t know at compile time which address family you’ll receive โ€” for example when accepting connections on a dual-stack server.

Q5. What does getnameinfo() do and when would you use it?

It converts a socket address structure back into a human-readable hostname and port string. It is used in servers to log which client just connected โ€” you pass it the address returned by accept() and it gives you the client’s hostname and port number as strings.

Q6. What is the difference between AI_NUMERICSERV and not using it?

Without AI_NUMERICSERV, the service parameter to getaddrinfo() can be a service name like “http” and the function does a lookup in /etc/services. With AI_NUMERICSERV, the service parameter must be a numeric port string like “80” and no lookup is done โ€” faster and avoids ambiguity.

๐Ÿ“š Chapter 59 Navigation
Part 1: Internet Sockets Overview & getaddrinfo() ย |ย  Part 2: Server Program Walkthrough ย |ย  Part 3: Client Program & Ephemeral Ports

Next: Server Program โ†’ EmbeddedPathashala Home

Leave a Reply

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