Sockets: Internet Domains Host and Service Name Conversion Functions

 

Chapter 59 โ€“ Sockets: Internet Domains
Part 3 of 3 ย |ย  Host and Service Name Conversion Functions
๐Ÿ“ก Topic
Name Resolution
๐Ÿ”ง APIs
inet_pton ยท getaddrinfo
๐Ÿ“š Source
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.

Key Terms in This Section

hostname service name presentation format dotted-decimal hex-string (IPv6) inet_pton() inet_ntop() getaddrinfo() getnameinfo() inet_aton() inet_ntoa() gethostbyname() getservbyname() DNS /etc/services

1. The Three Formats for Host Addresses

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()

2. The Modern API โ€” inet_pton() and 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.

3. getaddrinfo() โ€” The Do-Everything Modern Function

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

4. getnameinfo() โ€” The Reverse: Binary Address to Name

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

5. Service Names and Port Numbers

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.

6. The Obsolete API โ€” What You’ll See in Old Code

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.

7. Modern vs Obsolete API โ€” Side-by-Side Comparison
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

8. Error Handling for getaddrinfo() and getnameinfo()

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

9. Putting It All Together โ€” Minimal TCP Client

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

๐ŸŽฏ Interview Questions โ€” Name Resolution and Conversion

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.

๐Ÿ“‹ Chapter 59 Summary โ€” All Three Parts
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()

Chapter 59 Complete!

You have covered readLine(), socket address structures, and name resolution for Internet domain sockets.

โ† Part 1 โ† Part 2 EmbeddedPathashala.com

Leave a Reply

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