Name Resolution
inet_pton ยท getaddrinfo
TLPI ยง59.5
The Problem: Humans Like Names, Computers Like Numbers
The kernel works entirely with binary IP addresses and port numbers. Humans remember names like www.google.com and service names like "http". A group of library functions exists to convert between these two worlds.
There are two generations of these APIs: the modern API (IPv4 + IPv6 aware, recommended) and the obsolete API (IPv4-only, still common in old code). This chapter covers both so you can read legacy code and write new code correctly.
A host’s IP address can exist in three forms in a program. You need conversion functions to move between them:
| Format | IPv4 example | IPv6 example | Where it lives |
|---|---|---|---|
| Binary | 0xC0A8010A |
16 raw bytes | Inside sockaddr_in / sockaddr_in6 |
| Presentation string | "192.168.1.10" |
"2001:db8::1" |
Char array in your program, config files |
| Hostname | "myserver.local" |
"myserver.local" |
DNS, /etc/hosts |
| Hostname www.example.com |
โ | Binary in_addr / in6_addr |
โ | Presentation “192.168.1.1” |
| getaddrinfo() getnameinfo() |
inet_pton() inet_ntop() |
These two functions convert between presentation strings and binary addresses. They work for both IPv4 and IPv6.
The names follow a pattern: p = presentation, n = network (binary).
| Function | Direction | Input | Output |
|---|---|---|---|
inet_pton() |
presentation โ binary | string like "192.168.1.1" |
fills in_addr or in6_addr |
inet_ntop() |
binary โ presentation | in_addr or in6_addr |
string like "192.168.1.1" |
#include <arpa/inet.h>
/* inet_pton: presentation string โ binary address */
int inet_pton(int af, const char *src, void *dst);
/* Returns 1 on success, 0 if src is invalid, -1 on error */
/* inet_ntop: binary address โ presentation string */
const char *inet_ntop(int af, const void *src,
char *dst, socklen_t size);
/* Returns dst (the string pointer) on success, NULL on error */
Example โ IPv4:
#include <arpa/inet.h>
#include <stdio.h>
#include <netinet/in.h>
int main(void)
{
struct in_addr addr4;
char buf[INET_ADDRSTRLEN]; /* 16 bytes is enough for IPv4 dotted-decimal */
/* String to binary */
if (inet_pton(AF_INET, "192.168.1.10", &addr4) != 1) {
fprintf(stderr, "Invalid IPv4 address\n");
return 1;
}
/* Binary back to string */
if (inet_ntop(AF_INET, &addr4, buf, sizeof(buf)) == NULL) {
perror("inet_ntop");
return 1;
}
printf("Parsed and printed: %s\n", buf);
return 0;
}
Example โ IPv6:
#include <arpa/inet.h>
#include <stdio.h>
#include <netinet/in.h>
int main(void)
{
struct in6_addr addr6;
char buf[INET6_ADDRSTRLEN]; /* 46 bytes is enough for IPv6 hex-string */
/* String to binary */
if (inet_pton(AF_INET6, "2001:db8::1", &addr6) != 1) {
fprintf(stderr, "Invalid IPv6 address\n");
return 1;
}
/* Binary back to string */
if (inet_ntop(AF_INET6, &addr6, buf, sizeof(buf)) == NULL) {
perror("inet_ntop");
return 1;
}
printf("IPv6: %s\n", buf);
return 0;
}
The constants INET_ADDRSTRLEN (16) and INET6_ADDRSTRLEN (46) are defined in <arpa/inet.h> and give the exact buffer size needed for the longest possible presentation string of each type.
getaddrinfo() translates a hostname (or presentation-format address) plus a service name (or port number string) into one or more sockaddr structures you can directly use with connect() or bind(). It handles both IPv4 and IPv6 transparently.
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(
const char *node, /* hostname or IP string, e.g. "www.example.com" */
const char *service, /* service name or port string, e.g. "http" or "80" */
const struct addrinfo *hints, /* filters (can be NULL) */
struct addrinfo **res /* OUTPUT: linked list of results */
);
void freeaddrinfo(struct addrinfo *res); /* always call this when done */
/* The addrinfo structure returned: */
struct addrinfo {
int ai_flags; /* AI_PASSIVE, AI_CANONNAME, etc. */
int ai_family; /* AF_INET, AF_INET6, or AF_UNSPEC */
int ai_socktype; /* SOCK_STREAM or SOCK_DGRAM */
int ai_protocol; /* IPPROTO_TCP or IPPROTO_UDP */
size_t ai_addrlen; /* size of ai_addr */
struct sockaddr *ai_addr; /* pointer to the socket address */
char *ai_canonname; /* canonical hostname */
struct addrinfo *ai_next; /* next in linked list */
};
Client example โ connect to www.example.com on port 80:
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
int connect_to_host(const char *host, const char *port)
{
struct addrinfo hints, *result, *rp;
int sockfd;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; /* accept IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM; /* TCP */
int s = getaddrinfo(host, port, &hints, &result);
if (s != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
return -1;
}
/* Try each result until one works */
for (rp = result; rp != NULL; rp = rp->ai_next) {
sockfd = socket(rp->ai_family,
rp->ai_socktype,
rp->ai_protocol);
if (sockfd == -1)
continue; /* try next */
if (connect(sockfd, rp->ai_addr, rp->ai_addrlen) == 0)
break; /* success */
close(sockfd);
}
freeaddrinfo(result); /* MUST free after use */
if (rp == NULL) {
fprintf(stderr, "Could not connect to %s:%s\n", host, port);
return -1;
}
return sockfd; /* connected socket */
}
Server example โ bind to all interfaces, accept IPv4 and IPv6:
int create_server_socket(const char *port)
{
struct addrinfo hints, *result, *rp;
int sockfd;
int optval = 1;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; /* IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; /* wildcard โ bind to all interfaces */
/* node = NULL with AI_PASSIVE means INADDR_ANY / in6addr_any */
if (getaddrinfo(NULL, port, &hints, &result) != 0) {
perror("getaddrinfo");
return -1;
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sockfd == -1) continue;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
if (bind(sockfd, rp->ai_addr, rp->ai_addrlen) == 0)
break;
close(sockfd);
}
freeaddrinfo(result);
if (rp == NULL) return -1;
listen(sockfd, 50);
return sockfd;
}
getnameinfo() is the inverse of getaddrinfo(). It takes a binary socket address and returns a hostname string and a service name string.
#include <netdb.h>
int getnameinfo(
const struct sockaddr *addr, /* input: socket address */
socklen_t addrlen,
char *host, /* output buffer for hostname */
socklen_t hostlen,
char *serv, /* output buffer for service name */
socklen_t servlen,
int flags /* NI_NUMERICHOST | NI_NUMERICSERV | etc. */
);
/* Returns 0 on success, nonzero error code on failure */
Common flags:
| Flag | Effect |
|---|---|
NI_NUMERICHOST |
Return numeric IP string instead of hostname (avoids DNS lookup) |
NI_NUMERICSERV |
Return port number string instead of service name |
NI_NOFQDN |
Return only the hostname part (not fully-qualified) |
NI_DGRAM |
Look up as UDP service instead of TCP |
#include <netdb.h>
#include <stdio.h>
#include <sys/socket.h>
void print_address(struct sockaddr *addr, socklen_t len)
{
char host[NI_MAXHOST];
char serv[NI_MAXSERV];
int s = getnameinfo(addr, len,
host, sizeof(host),
serv, sizeof(serv),
NI_NUMERICHOST | NI_NUMERICSERV);
if (s == 0)
printf("Address: %s Port: %s\n", host, serv);
else
fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s));
}
Ports also have two forms: a numeric port (like 80) and a symbolic service name (like "http"). The mapping is defined in /etc/services on Linux.
/* From /etc/services (typical entries): */
http 80/tcp
https 443/tcp
ssh 22/tcp
ftp 21/tcp
smtp 25/tcp
dns 53/udp
getaddrinfo() automatically handles service name resolution โ you can pass "http" as the service argument and it resolves to port 80. You can also pass "80" directly as a decimal string.
These functions are IPv4-only and not thread-safe in most implementations. Do not use them in new code. You need to recognize them when reading legacy codebases.
| Old function | What it did | Modern replacement | Why it’s broken |
|---|---|---|---|
inet_aton() |
dotted-decimal string โ in_addr |
inet_pton() |
IPv4 only |
inet_ntoa() |
in_addr โ dotted-decimal string |
inet_ntop() |
IPv4 only; returns pointer to static buffer (not thread-safe) |
gethostbyname() |
hostname โ IP address | getaddrinfo() |
IPv4 only; not thread-safe; uses internal static struct |
gethostbyaddr() |
IP address โ hostname | getnameinfo() |
IPv4 only; not thread-safe |
getservbyname() |
service name โ port | getaddrinfo() |
Not thread-safe; uses static struct |
getservbyport() |
port โ service name | getnameinfo() |
Not thread-safe; uses static struct |
Why inet_ntoa() is not thread-safe (example):
/* inet_ntoa() returns a pointer to a static internal buffer.
If two threads call it simultaneously, they corrupt each other's result. */
char *p1 = inet_ntoa(addr1); /* Thread 1 */
char *p2 = inet_ntoa(addr2); /* Thread 2 โ overwrites the same static buffer! */
/* Both p1 and p2 now point to the same memory. p1 has been corrupted. */
In contrast, inet_ntop() writes into a caller-supplied buffer, making it thread-safe.
| Task | Obsolete (avoid) | Modern (use this) |
|---|---|---|
| IPv4 string โ binary | inet_aton() |
inet_pton(AF_INET, ...) |
| IPv4 binary โ string | inet_ntoa() |
inet_ntop(AF_INET, ...) |
| IPv6 string โ binary | No equivalent | inet_pton(AF_INET6, ...) |
| IPv6 binary โ string | No equivalent | inet_ntop(AF_INET6, ...) |
| Hostname โ socket address | gethostbyname() |
getaddrinfo() |
| Socket address โ hostname | gethostbyaddr() |
getnameinfo() |
| Service name โ port | getservbyname() |
getaddrinfo() |
| Port โ service name | getservbyport() |
getnameinfo() |
| Thread-safe? | No | Yes |
| IPv6 support? | No | Yes |
Unlike most Unix system calls, getaddrinfo() and getnameinfo() do not use errno. They return a nonzero error code directly, and you translate it to a string with gai_strerror().
#include <netdb.h>
#include <stdio.h>
int ret = getaddrinfo("nonexistent.host", "80", NULL, &result);
if (ret != 0) {
/* Do NOT use perror() here โ errno is not set */
fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(ret));
}
| Error constant | Meaning |
|---|---|
EAI_NONAME |
Host or service name not found |
EAI_SERVICE |
Service not supported for given socket type |
EAI_SOCKTYPE |
Socket type not supported |
EAI_FAMILY |
Address family not supported |
EAI_AGAIN |
Temporary DNS failure โ try again later |
EAI_SYSTEM |
System error โ check errno |
This example uses every concept from Parts 1โ3: socket address structures, getaddrinfo(), connect(), and readLine().
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include "read_line.h"
#define BUF 1024
int main(int argc, char *argv[])
{
/* argv[1] = host, argv[2] = port, argv[3] = message to send */
if (argc != 4) {
fprintf(stderr, "Usage: %s host port message\n", argv[0]);
return 1;
}
/* --- Step 1: resolve host+port into socket addresses --- */
struct addrinfo hints, *res, *rp;
int sockfd;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
int s = getaddrinfo(argv[1], argv[2], &hints, &res);
if (s != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
return 1;
}
/* --- Step 2: try each result until connect succeeds --- */
for (rp = res; 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) == 0) break;
close(sockfd);
}
freeaddrinfo(res);
if (rp == NULL) { fprintf(stderr, "Could not connect\n"); return 1; }
/* --- Step 3: send message (with newline for line-based protocol) --- */
char msg[BUF];
snprintf(msg, sizeof(msg), "%s\n", argv[3]);
write(sockfd, msg, strlen(msg));
/* --- Step 4: read response line by line using readLine() --- */
char reply[BUF];
ssize_t n;
while ((n = readLine(sockfd, reply, BUF)) > 0) {
printf("Server: %s", reply);
if (reply[n-1] != '\n')
printf("\n[line truncated]\n");
}
close(sockfd);
return 0;
}
Q1. What is the difference between inet_aton() and inet_pton()?
inet_aton() is IPv4-only. inet_pton() accepts an af argument (AF_INET or AF_INET6) and works for both. Always prefer inet_pton().
Q2. Why is inet_ntoa() not thread-safe?
It stores the result in a static internal buffer and returns a pointer to it. If two threads call it concurrently, both will write to and read from the same buffer, causing data races. inet_ntop() takes a caller-supplied buffer, so it is thread-safe.
Q3. What does AI_PASSIVE do in the hints struct passed to getaddrinfo()?
When node is NULL and AI_PASSIVE is set, the returned address structures contain the wildcard address (INADDR_ANY for IPv4, in6addr_any for IPv6). This is what you want for a server that should accept connections on all available network interfaces.
Q4. Why does getaddrinfo() return a linked list instead of a single result?
A hostname can have multiple A records (IPv4) and AAAA records (IPv6). The kernel may also support multiple socket types. The linked list lets you try each option in order (e.g., prefer IPv6, fall back to IPv4) and handle multi-homed hosts.
Q5. What happens if you forget to call freeaddrinfo()?
Memory allocated by getaddrinfo() leaks. The addrinfo linked list and all the sockaddr structures it points to are heap allocations. Always call freeaddrinfo(result) when done, even on error paths.
Q6. How does getaddrinfo() handle both service names and port numbers?
The service argument can be either a symbolic name like "http" or a decimal port string like "80". Internally it consults /etc/services (or the system’s name service) to resolve symbolic names, then stores the result as a binary port in the returned sockaddr.
Q7. What is the correct buffer size to pass to inet_ntop() for IPv4 and IPv6?
Use the constants from <arpa/inet.h>: INET_ADDRSTRLEN (16 bytes) for IPv4 and INET6_ADDRSTRLEN (46 bytes) for IPv6.
Q8. How do you use getaddrinfo() and getnameinfo() to implement a simple hostname lookup tool (like nslookup)?
#include <netdb.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
void lookup(const char *hostname)
{
struct addrinfo hints, *res, *rp;
char addr_str[INET6_ADDRSTRLEN];
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if (getaddrinfo(hostname, NULL, &hints, &res) != 0) {
fprintf(stderr, "lookup failed\n");
return;
}
for (rp = res; rp != NULL; rp = rp->ai_next) {
void *ptr;
if (rp->ai_family == AF_INET)
ptr = &((struct sockaddr_in *)rp->ai_addr)->sin_addr;
else
ptr = &((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr;
inet_ntop(rp->ai_family, ptr, addr_str, sizeof(addr_str));
printf("%s -> %s (%s)\n", hostname, addr_str,
rp->ai_family == AF_INET ? "IPv4" : "IPv6");
}
freeaddrinfo(res);
}
Q9. What does gai_strerror() do and when must you use it?
It converts the nonzero return value from getaddrinfo() or getnameinfo() into a human-readable error string. Unlike most POSIX functions, these two do not use errno on failure, so perror() and strerror(errno) would give wrong or misleading output. Always use gai_strerror(ret) for these functions.
Q10. How can you find out at runtime what address family a connected socket is using?
Call getsockname() (for your own address) or getpeername() (for the remote address) with a sockaddr_storage buffer, then check the ss_family field. If it is AF_INET cast to sockaddr_in; if AF_INET6 cast to sockaddr_in6.
| Part | Topic | Key functions / structs |
|---|---|---|
| 1 | readLine() utility | readLine(), read(), EINTR handling |
| 2 | Internet socket addresses | sockaddr_in, sockaddr_in6, sockaddr_storage, htons() |
| 3 | Host and service conversion | inet_pton(), inet_ntop(), getaddrinfo(), getnameinfo(), gai_strerror() |
You have covered readLine(), socket address structures, and name resolution for Internet domain sockets.
