Internet Sockets
TLPI Ch. 59
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().
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 ofaddrinfo structs |
| NULL (for server, wildcard address) |
Sets INADDR_ANY or IN6ADDR_ANY_INIT |
Structs for binding to all interfaces |
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.
#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 | |
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.
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");
}
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. */
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.
