Name Resolution
Intermediate
TLPI Ch. 59
Why Do We Need Name Resolution?
You never want to hardcode an IP address like 142.250.182.46 into your program. Instead, you type google.com and let the system figure out the IP. This is called hostname resolution.
Similarly, instead of hardcoding port 80 you say “http”, and the system looks it up. This is called service name resolution.
Linux provides two generations of APIs for this. The old (obsolete) generation was simple but broken in subtle ways. The new (modern) generation β getaddrinfo() and getnameinfo() β handles both IPv4 and IPv6, is thread-safe, and is what all new code should use.
Key Terms
| Two Axes of Name Resolution in Socket Programming | ||||
| Hostname e.g. “google.com” |
β· | IP Address e.g. 142.250.0.1 |
Handled by: getaddrinfo() (obsolete: gethostbyname) |
DNS + /etc/hosts |
| Service Name e.g. “http” |
β· | Port Number e.g. 80 |
Handled by: getaddrinfo() (obsolete: getservbyname) |
/etc/services |
These four functions are widely used in old code. You will find them in textbooks, legacy systems, and open-source projects written before 2000. SUSv3 marked them obsolete; SUSv4 removed them entirely. You need to understand them to read old code β but write all new code using getaddrinfo().
| Function | Direction | Problem |
|---|---|---|
gethostbyname() |
hostname β IP address(es) | IPv4 only, not thread-safe (static buffer) |
gethostbyaddr() |
IP address β hostname | IPv4 only, not thread-safe |
getservbyname() |
service name β port number | Not thread-safe (static struct) |
getservbyport() |
port number β service name | Not thread-safe (static struct) |
Why Are They Not Thread-Safe?
Each of these functions returns a pointer to a static internal structure (struct hostent * or struct servent *). Two threads calling the same function will share that static memory, causing one to overwrite the other’s result. There is no way to fix this without changing the API β which is why getaddrinfo() was designed differently (it allocates new memory and you free it yourself).
What Old Code Looks Like (for reference only)
/* OLD CODE - DO NOT USE IN NEW PROGRAMS */
#include <netdb.h>
struct hostent *he;
he = gethostbyname("www.example.com");
if (he == NULL) {
herror("gethostbyname"); /* special error function for DNS */
return -1;
}
/* he->h_addr_list[0] is the first IP address as binary */
/* he->h_addrtype is AF_INET or AF_INET6 */
/* he->h_name is the official hostname */
/* PROBLEM: he points to static internal memory - not thread-safe */
getaddrinfo() replaces both gethostbyname() and getservbyname() in a single call. It handles IPv4 and IPv6 transparently. It allocates memory for results (so it is thread-safe) and you must call freeaddrinfo() when done.
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *host, /* hostname or IP string, or NULL */
const char *service, /* service name or port number string */
const struct addrinfo *hints, /* filter criteria (can be NULL) */
struct addrinfo **result); /* OUTPUT: linked list of results */
/* Returns 0 on success, nonzero error code on failure */
/* Use gai_strerror(ret) to get error string */
void freeaddrinfo(struct addrinfo *result); /* always call this when done */
The addrinfo Structure
struct addrinfo {
int ai_flags; /* Hints: 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; /* 0 = any, or IPPROTO_TCP/UDP */
socklen_t ai_addrlen; /* Length of ai_addr */
struct sockaddr *ai_addr; /* Binary socket address */
char *ai_canonname; /* Canonical hostname (if requested) */
struct addrinfo *ai_next; /* Next result in linked list */
};
getaddrinfo() returns a linked list β a hostname may have multiple IPs:
| result[0] AF_INET 192.0.2.1:80 |
β | result[1] AF_INET6 [2001:db8::1]:80 |
β | result[2] AF_INET 192.0.2.2:80 |
β | NULL |
Your code should try each entry in order until a connection succeeds. This is the correct way to write a dual-stack (IPv4+IPv6) client.
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
int main(void)
{
struct addrinfo hints;
struct addrinfo *res, *rp;
int sfd, ret;
/* Step 1: Fill in hints to filter results */
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; /* Accept IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM; /* TCP */
/* Step 2: Resolve hostname + service */
ret = getaddrinfo("www.example.com", "80", &hints, &res);
if (ret != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret));
return 1;
}
/* Step 3: Try each result until connection succeeds */
for (rp = res; rp != NULL; rp = rp->ai_next) {
sfd = socket(rp->ai_family,
rp->ai_socktype,
rp->ai_protocol);
if (sfd == -1)
continue; /* Try next */
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
break; /* Success! */
close(sfd);
}
if (rp == NULL) {
fprintf(stderr, "Could not connect to any address\n");
freeaddrinfo(res);
return 1;
}
printf("Connected successfully! fd = %d\n", sfd);
/* Step 4: MUST free the linked list */
freeaddrinfo(res);
/* ... use sfd for send/recv ... */
close(sfd);
return 0;
}
On the server side, you want to bind to all available interfaces. Pass NULL as the host and set the AI_PASSIVE flag. This causes the returned address to be the wildcard address (INADDR_ANY for IPv4 or in6addr_any for IPv6).
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
int main(void)
{
struct addrinfo hints, *res;
int sfd, ret;
int opt = 1;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET6; /* IPv6 (or AF_UNSPEC for dual-stack) */
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; /* Wildcard address - for bind() */
/* host = NULL means INADDR_ANY / in6addr_any */
ret = getaddrinfo(NULL, "8080", &hints, &res);
if (ret != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret));
return 1;
}
sfd = socket(res->ai_family,
res->ai_socktype,
res->ai_protocol);
if (sfd == -1) { perror("socket"); return 1; }
setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (bind(sfd, res->ai_addr, res->ai_addrlen) == -1) {
perror("bind");
return 1;
}
freeaddrinfo(res); /* Done with addrinfo, free before listen */
listen(sfd, 5);
printf("Server listening on port 8080 ...\n");
/* accept() loop would go here */
close(sfd);
return 0;
}
getnameinfo() is the reverse of getaddrinfo(). Given a binary socket address, it returns the hostname and service name. It is also the modern replacement for gethostbyaddr() and getservbyport().
#include <sys/socket.h>
#include <netdb.h>
int getnameinfo(const struct sockaddr *addr, /* binary socket address */
socklen_t addrlen, /* sizeof the sockaddr */
char *host, /* OUTPUT: hostname buffer */
socklen_t hostlen, /* size of host buffer */
char *service, /* OUTPUT: service buffer */
socklen_t servicelen, /* size of service buffer */
int flags); /* NI_NUMERICHOST etc. */
/* Returns 0 on success, nonzero error code on failure */
/* Use gai_strerror() to print error */
Key Flags for getnameinfo()
| Flag | Effect |
|---|---|
NI_NUMERICHOST |
Return IP string instead of doing DNS lookup (like inet_ntop) |
NI_NUMERICSERV |
Return port number string instead of service name |
NI_NOFQDN |
Return only the hostname part, not fully qualified domain name |
NI_DGRAM |
Service is UDP-based (affects portβname mapping) |
Buffer Sizes
#include <netdb.h>
#define NI_MAXHOST 1025 /* Max hostname length */
#define NI_MAXSERV 32 /* Max service name length */
Example β Get hostname and port from a connected socket
#include <stdio.h>
#include <sys/socket.h>
#include <netdb.h>
void print_peer_info(int sockfd)
{
struct sockaddr_storage addr; /* Works for both IPv4 and IPv6 */
socklen_t addrlen = sizeof(addr);
char host[NI_MAXHOST];
char service[NI_MAXSERV];
int ret;
/* Get peer's address */
if (getpeername(sockfd, (struct sockaddr *)&addr, &addrlen) == -1) {
perror("getpeername");
return;
}
/* Convert binary address to name */
ret = getnameinfo((struct sockaddr *)&addr, addrlen,
host, sizeof(host),
service, sizeof(service),
NI_NUMERICHOST | NI_NUMERICSERV); /* No DNS, just numbers */
if (ret != 0) {
fprintf(stderr, "getnameinfo: %s\n", gai_strerror(ret));
return;
}
printf("Peer: %s port %s\n", host, service);
}
When you call getaddrinfo("www.example.com", "http", ...), the system does the following:
| 1 | Service name β port Looks up “http” in /etc/services β finds port 80/tcp |
/etc/services maps symbolic names to port numbers. Example:http 80/tcpssh 22/tcp |
|
| 2 | Hostname β IP (local) Checks /etc/hosts for a static entry first |
/etc/hosts example:127.0.0.1 localhost |
|
| 3 | Hostname β IP (DNS) If no local entry, sends a DNS query to the nameserver |
DNS is a distributed database. It is essential because no single machine could hold all internet hostnames. | |
The order of lookup (local file first, then DNS) is controlled by /etc/nsswitch.conf. This is why editing /etc/hosts can override DNS for testing purposes.
Checking /etc/services on Your System
# View some entries in /etc/services
$ grep -E "^(http|ssh|ftp|smtp)" /etc/services
http 80/tcp www
https 443/tcp
ssh 22/tcp
ftp 21/tcp
smtp 25/tcp mail
| Capability | Old (Obsolete) | New (Use This) |
|---|---|---|
| Hostname β IP | gethostbyname() |
getaddrinfo() |
| IP β Hostname | gethostbyaddr() |
getnameinfo() |
| Service name β Port | getservbyname() |
getaddrinfo() |
| Port β Service name | getservbyport() |
getnameinfo() |
| IPv6 support | β No | β Yes |
| Thread-safe | β No (static buffer) | β Yes (heap allocation) |
| Memory management | Static (auto) | Heap β must call freeaddrinfo() |
| Error reporting | h_errno / herror() |
Return code + gai_strerror() |
| POSIX status | Removed in SUSv4 | Current standard |
A: Three reasons: (1) It only handles IPv4, not IPv6. (2) It returns a pointer to a static internal structure, making it not thread-safe. (3) SUSv3 marked it obsolete and SUSv4 removed it entirely. getaddrinfo() replaces it and addresses all three problems.
A: getaddrinfo() allocates a linked list of struct addrinfo nodes on the heap and sets the result pointer to the head. Since it uses heap allocation (not a static buffer), the caller must call freeaddrinfo(result) when done to avoid memory leaks.
A: Setting hints.ai_family = AF_UNSPEC tells getaddrinfo() to return both IPv4 and IPv6 addresses. This lets you write dual-stack programs that work on either IP version without version-specific code.
A: When writing a server, you pass AI_PASSIVE in hints.ai_flags and pass NULL as the hostname. The returned socket address will use the wildcard address (INADDR_ANY for IPv4, in6addr_any for IPv6), meaning the socket will accept connections on all available network interfaces.
A: getaddrinfo() and getnameinfo() return their own set of error codes (not errno values). gai_strerror(ret) converts these codes to a human-readable string. You cannot use strerror() because the error codes are different from errno values.
A: /etc/services is a local database that maps symbolic service names to port numbers and protocol types. When you call getaddrinfo(host, "http", ...), the system looks up “http” in /etc/services to get port 80. You can also pass a port number string like “80” directly to bypass this lookup.
A: getaddrinfo() returns a linked list of results (via ai_next). A well-written client loops through the list calling socket() and connect() for each entry. If one fails, it tries the next. This is the correct pattern for robust, dual-stack clients.
A: NI_NUMERICHOST tells getnameinfo() to return the IP address as a numeric string (like “192.168.1.1”) instead of performing a reverse DNS lookup. Use it when you need fast, non-blocking address printing β for example in logs or diagnostics β and don’t want the latency of a DNS query.
Next: IPv6 UDP Datagram Socket Example
See a real working IPv6 server and client using AF_INET6 datagram sockets.
