Sockets: Internet Domains Host & Service Name Resolution: Obsolete vs Modern APIs

 

Chapter 59 – Sockets: Internet Domains
Part 2 of 3 Β |Β  Host & Service Name Resolution: Obsolete vs Modern APIs
πŸ“‘ Topic
Name Resolution
🎯 Level
Intermediate
πŸ“˜ Source
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

gethostbyname getservbyname gethostbyaddr getservbyport getaddrinfo getnameinfo freeaddrinfo addrinfo DNS /etc/services hostent servent

1. The Big Picture – What Needs to Be Resolved?

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

2. The Obsolete Functions (Know Them, But Don’t Use Them)

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 */

3. getaddrinfo() – The Modern Successor

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.

4. Coding Example – TCP Client Using getaddrinfo()
#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;
}

5. Server Side – Using AI_PASSIVE with getaddrinfo()

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;
}

6. getnameinfo() – The Reverse: Binary β†’ Name

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);
}

7. How Does Name Resolution Work Internally?

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/tcp
ssh 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

8. Old vs New API Comparison Table
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

9. Interview Questions & Answers
Q1. Why is gethostbyname() considered obsolete?

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.

Q2. What does getaddrinfo() return, and why must you call freeaddrinfo()?

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.

Q3. What is AF_UNSPEC and when do you use it in getaddrinfo()?

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.

Q4. What is the AI_PASSIVE flag used for?

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.

Q5. What does gai_strerror() do? Why not use strerror()?

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.

Q6. What is the role of /etc/services in socket programming?

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.

Q7. How does a client program handle the case where a hostname resolves to multiple IP addresses?

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.

Q8. What is NI_NUMERICHOST and when would you use it in getnameinfo()?

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.

Part 3 β†’ embeddedpathashala.com

Leave a Reply

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