getnameinfo() โ€” Reverse Address Resolution

 

getnameinfo() โ€” Reverse Address Resolution
Part 4 โ€” Address-to-Name Conversion + Complete Interview Q&A
๐Ÿ“š Chapter 59 โ€“ TLPI
๐Ÿ”„ Reverse Lookup
๐ŸŽฏ Interview Prep

What Does getnameinfo() Do?

getnameinfo() is the reverse of getaddrinfo(). While getaddrinfo() converts a hostname + service name into a binary socket address structure, getnameinfo() converts a binary socket address structure back into human-readable hostname and service name strings.

It is the modern, thread-safe, IPv4/IPv6-compatible replacement for the obsolete gethostbyaddr() and getservbyport() functions. It is also defined in POSIX.1g and documented in RFC 3493.

Key Terms

getnameinfo() Reverse DNS NI_NUMERICHOST NI_NUMERICSERV NI_NOFQDN NI_NAMEREQD NI_DGRAM sockaddr gai_strerror() NI_MAXHOST NI_MAXSERV

1. getnameinfo() โ€” Function Signature
#include <sys/socket.h>
#include <netdb.h>

int getnameinfo(const struct sockaddr *addr,   /* Input: socket address */
                socklen_t             addrlen, /* Input: size of addr */
                char                 *host,    /* Output: hostname buffer */
                socklen_t             hostlen, /* Input: size of host buffer */
                char                 *service, /* Output: service name buffer */
                socklen_t             servlen, /* Input: size of service buffer */
                int                   flags);  /* Input: NI_* flags */

/* Returns 0 on success, or nonzero error code on failure */

Parameters Explained

Parameter Direction What it is
addr INPUT Pointer to a sockaddr structure (sockaddr_in for IPv4 or sockaddr_in6 for IPv6). This is the binary address to convert.
addrlen INPUT Size of the addr structure in bytes. Use sizeof(struct sockaddr_in) for IPv4 or sizeof(struct sockaddr_in6) for IPv6.
host OUTPUT Buffer where the hostname string will be written. Allocate NI_MAXHOST bytes. Pass NULL if you don’t want the hostname.
hostlen INPUT Size of the host buffer. Typically NI_MAXHOST (defined in <netdb.h>, usually 1025).
service OUTPUT Buffer where the service name string will be written. Allocate NI_MAXSERV bytes. Pass NULL if you don’t want the service name.
servlen INPUT Size of the service buffer. Typically NI_MAXSERV (defined in <netdb.h>, usually 32).
flags INPUT Bitwise OR of NI_* flag constants to control output format. Use 0 for defaults.

getaddrinfo() vs getnameinfo() โ€” Side by Side

getaddrinfo()
“www.example.com”
+ “http”
โ†“
93.184.216.34 : 80
(binary sockaddr)
โ‡Œ
getnameinfo()
93.184.216.34 : 80
(binary sockaddr)
โ†“
“www.example.com”
+ “http”

2. NI_* Flags โ€” Controlling Output Format

The flags argument is formed by ORing together NI_* constants. These control whether you get back names or numeric strings, and how the hostname is formatted.

๐Ÿšฉ NI_NUMERICHOST โ€” Return Numeric IP, Not Hostname

By default, getnameinfo() tries to convert the IP address to a hostname via reverse DNS lookup. If you set NI_NUMERICHOST, it skips DNS and returns the numeric IP address string instead.

When to use it: When you want to display or log the IP address rather than the hostname. Also useful when reverse DNS is unavailable or when you need a fast, non-blocking call.

/* With NI_NUMERICHOST: host buffer gets "93.184.216.34" instead of "www.example.com" */
getnameinfo(addr, addrlen, host, NI_MAXHOST, service, NI_MAXSERV, NI_NUMERICHOST);
printf("Client IP: %s\n", host);  /* Prints: "93.184.216.34" */

๐Ÿšฉ NI_NUMERICSERV โ€” Return Port Number, Not Service Name

By default, getnameinfo() tries to look up the port number in /etc/services to return a service name (like “http”). NI_NUMERICSERV skips this lookup and returns the port number as a decimal string instead.

/* With NI_NUMERICSERV: service buffer gets "80" instead of "http" */
getnameinfo(addr, addrlen, host, NI_MAXHOST, service, NI_MAXSERV, NI_NUMERICSERV);
printf("Port: %s\n", service);  /* Prints: "80" */

๐Ÿšฉ NI_NOFQDN โ€” Return Only Short Hostname, Not FQDN

FQDN = Fully Qualified Domain Name. For example: “mail.example.com” is an FQDN. “mail” is just the short (local) hostname. NI_NOFQDN tells getnameinfo() to return only the short hostname if the address belongs to the local domain.

/* Without NI_NOFQDN: "mail.internal.corp.com" */
/* With NI_NOFQDN:    "mail" (if in the same local domain) */
getnameinfo(addr, addrlen, host, NI_MAXHOST, NULL, 0, NI_NOFQDN);
printf("Host: %s\n", host);

๐Ÿšฉ NI_NAMEREQD โ€” Fail If Name Cannot Be Resolved

By default, if getnameinfo() cannot resolve the IP address to a hostname (no reverse DNS entry), it falls back to returning the numeric IP string. Setting NI_NAMEREQD changes this โ€” if a hostname cannot be found, the function fails and returns an error (EAI_NONAME) rather than falling back to a numeric address.

When to use it: In security-sensitive applications where a numeric IP in the host field would be misleading, or when you strictly require a hostname.

int err = getnameinfo(addr, addrlen, host, NI_MAXHOST,
                      service, NI_MAXSERV, NI_NAMEREQD);

if (err == EAI_NONAME) {
    printf("No hostname found for this IP address\n");
} else if (err != 0) {
    fprintf(stderr, "getnameinfo: %s\n", gai_strerror(err));
}

๐Ÿšฉ NI_DGRAM โ€” Look Up Service as UDP

By default, getnameinfo() looks up the port number in /etc/services using the TCP protocol. Setting NI_DGRAM switches the service lookup to use UDP instead. This matters for the rare case where the same port number means different services on TCP vs UDP (for example, port 514: TCP=rsh, UDP=syslog).

/* Port 514, no flag: service name returned = "shell" (rsh, TCP 514) */
getnameinfo(addr, addrlen, NULL, 0, service, NI_MAXSERV, 0);

/* Port 514, NI_DGRAM: service name returned = "syslog" (UDP 514) */
getnameinfo(addr, addrlen, NULL, 0, service, NI_MAXSERV, NI_DGRAM);

NI_* Flags Summary Table

Flag Effect Default without flag
NI_NUMERICHOST Return numeric IP string (skip reverse DNS) Attempt reverse DNS lookup; fall back to numeric if not found
NI_NUMERICSERV Return port number as decimal string Look up port in /etc/services and return service name
NI_NOFQDN Return short hostname only (not FQDN) if local domain Return fully qualified domain name
NI_NAMEREQD Fail (EAI_NONAME) if hostname cannot be resolved Fall back to numeric address string if name not found
NI_DGRAM Look up service as UDP instead of TCP Look up service as TCP

3. Complete Code Example โ€” Using getnameinfo()

Example 1: Print Client Info After accept()

The most common real-world use of getnameinfo() is in a server โ€” after accepting a connection, you want to log the connecting client’s IP address and port.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

/* Utility: print the IP and port of a sockaddr */
void print_client_info(const struct sockaddr *addr, socklen_t addrlen) {
    char host[NI_MAXHOST];
    char service[NI_MAXSERV];
    int err;

    /* NI_NUMERICHOST: give IP address, not hostname (fast, no DNS)
       NI_NUMERICSERV: give port number, not service name */
    err = getnameinfo(addr, addrlen,
                      host,    NI_MAXHOST,
                      service, NI_MAXSERV,
                      NI_NUMERICHOST | NI_NUMERICSERV);
    if (err != 0) {
        fprintf(stderr, "getnameinfo: %s\n", gai_strerror(err));
        return;
    }

    printf("Client connected from IP: %s  Port: %s\n", host, service);
}

int main() {
    struct addrinfo hints, *res;
    int listenfd, connfd;
    struct sockaddr_storage client_addr;  /* Large enough for IPv4 or IPv6 */
    socklen_t client_addrlen;

    /* Set up listening socket (simplified) */
    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags    = AI_PASSIVE;

    if (getaddrinfo(NULL, "9000", &hints, &res) != 0) {
        perror("getaddrinfo"); exit(1);
    }

    listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    bind(listenfd, res->ai_addr, res->ai_addrlen);
    freeaddrinfo(res);
    listen(listenfd, 5);

    printf("Listening on port 9000...\n");

    /* Accept one connection */
    client_addrlen = sizeof(client_addr);
    connfd = accept(listenfd,
                    (struct sockaddr *)&client_addr,
                    &client_addrlen);
    if (connfd == -1) { perror("accept"); exit(1); }

    /* Use getnameinfo() to display client info */
    print_client_info((struct sockaddr *)&client_addr, client_addrlen);

    close(connfd);
    close(listenfd);
    return 0;
}
๐Ÿ’ก Note: struct sockaddr_storage is a generic structure large enough to hold either an IPv4 or IPv6 socket address. It is the correct type to use for the address buffer in accept() when you don’t know which protocol will connect.

Example 2: Both Reverse Lookup and Numeric Fallback

#include <stdio.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

void resolve_ipv4(const char *ip_str) {
    struct sockaddr_in addr;
    char host[NI_MAXHOST];
    char service[NI_MAXSERV];
    int err;

    /* Build an IPv4 sockaddr from a dotted-decimal string */
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(80);
    inet_pton(AF_INET, ip_str, &addr.sin_addr);

    /* Try to get the hostname (reverse DNS) */
    err = getnameinfo((struct sockaddr *)&addr, sizeof(addr),
                      host, NI_MAXHOST,
                      service, NI_MAXSERV,
                      0);   /* No flags: try hostname, try service name */
    if (err == 0) {
        printf("IP %s -> hostname: %s, service: %s\n", ip_str, host, service);
    } else {
        fprintf(stderr, "getnameinfo failed: %s\n", gai_strerror(err));
    }

    /* Now get numeric values explicitly */
    err = getnameinfo((struct sockaddr *)&addr, sizeof(addr),
                      host, NI_MAXHOST,
                      service, NI_MAXSERV,
                      NI_NUMERICHOST | NI_NUMERICSERV);
    if (err == 0) {
        printf("Numeric: IP=%s Port=%s\n", host, service);
    }
}

int main() {
    resolve_ipv4("8.8.8.8");   /* Google DNS */
    return 0;
}

Example output:

IP 8.8.8.8 -> hostname: dns.google, service: http
Numeric: IP=8.8.8.8 Port=80

4. Important Constants for Buffer Sizing
Constant Defined in Typical value Purpose
NI_MAXHOST <netdb.h> 1025 Maximum buffer size needed for the hostname string returned by getnameinfo()
NI_MAXSERV <netdb.h> 32 Maximum buffer size needed for the service name string returned by getnameinfo()
INET_ADDRSTRLEN <arpa/inet.h> 16 Buffer size for an IPv4 address string (“255.255.255.255\0”)
INET6_ADDRSTRLEN <arpa/inet.h> 46 Buffer size for an IPv6 address string (full expanded form)
/* Correct buffer declarations for getnameinfo() */
char host[NI_MAXHOST];     /* 1025 bytes โ€” safe for any hostname */
char service[NI_MAXSERV];  /* 32 bytes โ€” safe for any service name */

getnameinfo(addr, addrlen, host, sizeof(host), service, sizeof(service), flags);

5. Error Handling for getnameinfo()

Like getaddrinfo(), getnameinfo() does NOT set errno. It returns its own error code. Use gai_strerror() to convert the error code to a string.

Error Code Meaning
EAI_AGAIN Temporary DNS failure โ€” try again later.
EAI_BADFLAGS Invalid flags in the flags argument.
EAI_FAIL Permanent DNS failure.
EAI_FAMILY Address family not supported.
EAI_MEMORY Out of memory.
EAI_NONAME No name found for the given address, and NI_NAMEREQD was set โ€” OR the host or service is NULL.
EAI_OVERFLOW The provided host or service buffer is too small.
char host[NI_MAXHOST], service[NI_MAXSERV];
int err;

err = getnameinfo(addr, addrlen, host, sizeof(host),
                  service, sizeof(service), 0);

switch (err) {
    case 0:
        printf("Host: %s  Service: %s\n", host, service);
        break;
    case EAI_NONAME:
        printf("No name found for this address\n");
        break;
    case EAI_AGAIN:
        printf("Temporary failure โ€” try again\n");
        break;
    default:
        fprintf(stderr, "getnameinfo error: %s\n", gai_strerror(err));
        break;
}

๐ŸŽฏ Comprehensive Interview Q&A โ€” Full Chapter 59 Coverage

Section A: getnameinfo() Questions

Q1. What is getnameinfo() and what does it do?

Answer: getnameinfo() converts a binary socket address structure (either IPv4 sockaddr_in or IPv6 sockaddr_in6) into human-readable strings โ€” a hostname and a service name. It is the modern replacement for the obsolete gethostbyaddr() and getservbyport() functions. It is protocol-independent (works for both IPv4 and IPv6) and thread-safe (reentrant).

Q2. What is the difference between NI_NUMERICHOST and NI_NAMEREQD?

Answer: NI_NUMERICHOST always returns the numeric IP address string without attempting a reverse DNS lookup. NI_NAMEREQD means “I require a hostname โ€” if you cannot find one, return an error (EAI_NONAME) rather than falling back to a numeric address”. They serve opposite goals: NI_NUMERICHOST actively avoids name resolution; NI_NAMEREQD makes name resolution mandatory.

Q3. Why would you use NI_DGRAM flag?

Answer: By default, getnameinfo() looks up the port number using the TCP protocol mapping in /etc/services. NI_DGRAM switches this lookup to use UDP. This matters for the rare ports where TCP and UDP have different service names โ€” for example, port 514 is “shell” (rsh) on TCP but “syslog” on UDP. If you know the socket is UDP-based, you should set NI_DGRAM to get the correct service name.

Q4. What is struct sockaddr_storage and when should you use it?

Answer: sockaddr_storage is a generic socket address structure that is large enough to hold any type of socket address โ€” either IPv4 (sockaddr_in, 16 bytes) or IPv6 (sockaddr_in6, 28 bytes). You should use it as the buffer type for accept() when writing protocol-independent code, because you don’t know at compile time whether the connecting client will use IPv4 or IPv6. After the address is filled in, you cast it to (struct sockaddr *) before passing to getnameinfo() or other functions.

Section B: Combined API Comparison Questions

Q5. What is the relationship between getaddrinfo() and getnameinfo()?

Answer: They are inverse functions. getaddrinfo() converts a hostname and service name (text) into socket address structures (binary). getnameinfo() converts a socket address structure (binary) back into hostname and service name (text). Together they form a complete, protocol-independent name resolution API that replaces four older functions: gethostbyname(), gethostbyaddr(), getservbyname(), and getservbyport().

Q6. Both getaddrinfo() and getnameinfo() may perform DNS lookups. What are the implications?

Answer: DNS lookups involve network communication and can take an unpredictable amount of time (milliseconds to several seconds). Both functions are blocking calls โ€” your program’s thread cannot do anything else while the DNS query is in progress. In performance-sensitive code (like a high-throughput server), you should: (1) cache results rather than calling these functions repeatedly for the same address, (2) use AI_NUMERICHOST / NI_NUMERICHOST flags when you don’t need name resolution, (3) consider doing name resolution in a separate thread.

Q7. What error-reporting functions should you use for getaddrinfo() and getnameinfo() errors?

Answer: Use gai_strerror(errorCode) for both functions. Neither getaddrinfo() nor getnameinfo() sets errno โ€” they return their own error codes (EAI_* constants). Using perror() would be wrong because it reads errno, which may be stale or set to something completely unrelated.

Section C: Conceptual and Design Questions

Q8. Walk me through writing a TCP server that is protocol-independent (works for both IPv4 and IPv6).

Answer: The steps are:

/* 1. Set up hints for a server socket */
struct addrinfo hints, *result;
memset(&hints, 0, sizeof(hints));
hints.ai_family   = AF_UNSPEC;    /* Accept IPv4 or IPv6 clients */
hints.ai_socktype = SOCK_STREAM;  /* TCP */
hints.ai_flags    = AI_PASSIVE;   /* Server: bind to wildcard address */

/* 2. Resolve the port */
getaddrinfo(NULL, "port_number", &hints, &result);

/* 3. Create socket, set SO_REUSEADDR, bind, listen */
int sfd = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
int opt = 1;
setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bind(sfd, result->ai_addr, result->ai_addrlen);
freeaddrinfo(result);
listen(sfd, BACKLOG);

/* 4. Accept connections โ€” use sockaddr_storage for client addr */
struct sockaddr_storage client_addr;
socklen_t client_len = sizeof(client_addr);
int connfd = accept(sfd, (struct sockaddr *)&client_addr, &client_len);

/* 5. Log client info with getnameinfo() */
char host[NI_MAXHOST], serv[NI_MAXSERV];
getnameinfo((struct sockaddr *)&client_addr, client_len,
            host, NI_MAXHOST, serv, NI_MAXSERV,
            NI_NUMERICHOST | NI_NUMERICSERV);
printf("Client: %s:%s\n", host, serv);

Q9. What is the difference between the wildcard address and the loopback address in the context of server sockets?

Answer: The wildcard address (0.0.0.0 for IPv4, :: for IPv6) means “accept connections arriving on any network interface.” A server bound to the wildcard address can receive connections from any client โ€” local or remote. The loopback address (127.0.0.1 for IPv4, ::1 for IPv6) means “only accept connections from the same machine.” A server bound to loopback cannot be reached from other machines on the network. getaddrinfo() returns the wildcard address when AI_PASSIVE is set with host = NULL, and the loopback address when AI_PASSIVE is NOT set with host = NULL.

Q10. Why is it important to loop through all getaddrinfo() results when trying to connect?

Answer: getaddrinfo() may return multiple addresses โ€” for example, an IPv6 address followed by an IPv4 address. The first address in the list may not be reachable (maybe the server has an IPv6 address but the network path between client and server has no IPv6 support). By looping and trying each address until one succeeds, your program automatically uses the best available address and falls back gracefully to alternatives. Trying only the first result could cause failures that would succeed if you tried a later result.

Q11. What header files are needed to use getaddrinfo() and getnameinfo()?

Answer: You need:

#include <sys/socket.h>   /* sockaddr, socket() */
#include <netdb.h>         /* getaddrinfo(), getnameinfo(), struct addrinfo, NI_* constants, gai_strerror() */
/* Also commonly needed: */
#include <arpa/inet.h>     /* inet_pton(), inet_ntop(), htons(), ntohs() */
#include <string.h>        /* memset() */

Q12. What does it mean to say getaddrinfo() is “reentrant”? Why does this matter?

Answer: A reentrant (thread-safe) function does not use shared global or static memory for its results. getaddrinfo() allocates its result structures dynamically on the heap (using malloc) and returns the pointer to the caller, who owns the memory and must free it with freeaddrinfo(). This means two threads can call getaddrinfo() simultaneously without interfering with each other’s results. The older gethostbyname() stored its result in a single static buffer inside the library โ€” if two threads called it at the same time, one thread would corrupt the other’s result. In multi-threaded servers (which is the norm today), this makes getaddrinfo() essential.

Q13. How does the AI_ADDRCONFIG flag interact with AI_V4MAPPED?

Answer: They are often used together: AI_ADDRCONFIG | AI_V4MAPPED. AI_ADDRCONFIG filters results to only return address types the local system actually has configured. AI_V4MAPPED provides a fallback: if AF_INET6 is requested and no native IPv6 addresses are found, return IPv4 addresses in IPv4-mapped format (::ffff:x.x.x.x). The combined effect on an IPv6-capable dual-stack system: return native IPv6 addresses. On an IPv4-only system: return IPv4 addresses in mapped format. This combination allows a single codebase to handle both network configurations gracefully.

๐Ÿ“‹ Quick Reference โ€” Complete API Summary
Function Direction Returns Thread Safe? DNS Lookup?
getaddrinfo() name โ†’ address Linked list of addrinfo โœ… Yes Yes (for hostnames)
getnameinfo() address โ†’ name Strings in caller buffers โœ… Yes Yes (for reverse lookup)
gethostbyname() name โ†’ address Pointer to static struct โŒ No Yes (IPv4 only)
gethostbyaddr() address โ†’ name Pointer to static struct โŒ No Yes (IPv4 only)
getservbyname() name โ†’ port Pointer to static struct โŒ No No (reads /etc/services)
getservbyport() port โ†’ name Pointer to static struct โŒ No No (reads /etc/services)
AI_* Flags (getaddrinfo hints.ai_flags)
Flag Purpose Typical Use
AI_PASSIVE Wildcard address for servers Server bind() setup
AI_CANONNAME Get official hostname Logging/debugging
AI_NUMERICHOST Host must be numeric IP Skip DNS, known IP
AI_NUMERICSERV Service must be numeric port Skip /etc/services
AI_ADDRCONFIG Only configured address types Avoid unreachable IPv6
AI_V4MAPPED IPv4-mapped fallback for IPv6 queries IPv6-only code reaching IPv4
AI_ALL With AI_V4MAPPED: return both IPv6 and mapped IPv4 Dual-protocol queries
NI_* Flags (getnameinfo flags argument)
Flag Effect on host Effect on service
NI_NUMERICHOST Return numeric IP string No effect
NI_NUMERICSERV No effect Return port number string
NI_NOFQDN Short hostname only (local domain) No effect
NI_NAMEREQD Fail if no hostname found No effect
NI_DGRAM No effect Lookup as UDP service

Chapter 59 Series Complete!

You have covered: /etc/services, getaddrinfo(), hints & AI_* flags, and getnameinfo()

โ† Back to Part 1: /etc/services โ† Part 3: AI_* Flags embeddedpathashala.com

Leave a Reply

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