Sockets: Internet Domains Resolving Host Names with gethostbyname()

 

Chapter 59 – Sockets: Internet Domains
Part 1: Resolving Host Names with gethostbyname()
59.13.2
Section
DNS
Lookup
hostent
Structure

Chapter 59 Series

This series covers Internet domain sockets from TLPI Chapter 59. Each part focuses on one concept so you can learn step by step.

Part 1: gethostbyname() Part 2: getservbyname() Part 3: UNIX vs Internet Sockets Part 4: Chapter Summary

Key Terms in This Part
gethostbyname struct hostent h_errno hstrerror herror DNS resolution canonical name h_aliases h_addr_list inet_ntop

What is Host Name Resolution?

When you type www.google.com in a browser, your computer needs to find the actual IP address (like 142.250.183.4) behind that name. This process is called host name resolution.

In C network programming, the traditional function to do this is gethostbyname(). It takes a hostname string and returns a structure packed with all the information about that host — its real (canonical) name, any aliases, and its IP addresses.

Note: gethostbyname() is old and considered obsolete today. The modern replacement is getaddrinfo(). But you will see gethostbyname() in older codebases, so it is important to understand it.

How DNS Resolution Works (Big Picture)

When your program calls gethostbyname("www.example.com"), here is what happens behind the scenes:

Your Program
calls gethostbyname()
C Library
checks /etc/hosts first
DNS Server
queries if not cached
Returns IP
struct hostent*

The C library first checks /etc/hosts for a local mapping, then checks /etc/resolv.conf to find the DNS server, and finally sends a UDP query to the DNS server to resolve the name.

Function Signature

Include <netdb.h> to use these functions:

#include <netdb.h>

struct hostent *gethostbyname(const char *name);

/* Error reporting helpers */
void herror(const char *str);
const char *hstrerror(int err);

/* On success: returns pointer to a statically allocated hostent structure
   On failure: returns NULL and sets h_errno (not errno) */

Important: Unlike most C functions that set errno on failure, gethostbyname() sets a separate global variable called h_errno. You must use hstrerror(h_errno) or herror() to print the error — not strerror(errno).

The struct hostent Structure

When gethostbyname() succeeds it returns a pointer to this structure:

struct hostent {
    char  *h_name;       /* Official (canonical) name of the host     */
    char **h_aliases;    /* NULL-terminated array of alias name        */
    int    h_addrtype;   /* Address type: AF_INET or AF_INET6          */
    int    h_length;     /* Length of each address in bytes            */
    char **h_addr_list;  /* NULL-terminated array of IP addresses      */
                         /* (in network byte order)                    */
};

/* Convenience macro (for compatibility with old code) */
#define h_addr h_addr_list[0]   /* First address in the list */

struct hostent (example for www.jambit.com)
h_name
“jamjam1.jambit.com”  (canonical/official name)
h_aliases[0]
“www.jambit.com”  (the alias you searched for)
h_aliases[1]
NULL  (end of alias list)
h_addrtype
AF_INET  (IPv4)
h_length
4  (4 bytes for IPv4)
h_addr_list[0]
“62.245.207.90”  (first IP address)
h_addr_list[1]
NULL  (end of address list)

Key point: Both h_aliases and h_addr_list are arrays of pointers that end with a NULL pointer. You loop through them until you hit NULL.

Complete Working Program

This program takes hostnames as command-line arguments and prints all information returned by gethostbyname():

/* t_gethostbyname.c
 * Usage: ./t_gethostbyname www.google.com www.example.com
 *
 * Compile: gcc -o t_gethostbyname t_gethostbyname.c
 */

#define _BSD_SOURCE   /* Needed to expose hstrerror() in <netdb.h> */

#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>        /* gethostbyname, struct hostent, h_errno */
#include <netinet/in.h>   /* struct in_addr, struct in6_addr        */
#include <arpa/inet.h>    /* inet_ntop()                             */

int main(int argc, char *argv[])
{
    struct hostent *h;
    char **pp;
    char str[INET6_ADDRSTRLEN];   /* Big enough for IPv6 address string */

    if (argc < 2) {
        fprintf(stderr, "Usage: %s hostname...\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    /* Loop through each hostname given on command line */
    for (argv++; *argv != NULL; argv++) {

        h = gethostbyname(*argv);

        if (h == NULL) {
            /* h_errno is set, NOT errno — use hstrerror() */
            fprintf(stderr, "gethostbyname() failed for '%s': %s\n",
                    *argv, hstrerror(h_errno));
            continue;   /* Try next hostname */
        }

        /* Print canonical name (official DNS name) */
        printf("Canonical name: %s\n", h->h_name);

        /* Print all aliases (e.g. www.example.com -> example.com) */
        printf("  alias(es): ");
        for (pp = h->h_aliases; *pp != NULL; pp++)
            printf(" %s", *pp);
        printf("\n");

        /* Print address family */
        printf("  address type: %s\n",
               (h->h_addrtype == AF_INET)  ? "AF_INET"  :
               (h->h_addrtype == AF_INET6) ? "AF_INET6" : "???");

        /* Print all IP addresses */
        if (h->h_addrtype == AF_INET || h->h_addrtype == AF_INET6) {
            printf("  address(es): ");
            for (pp = h->h_addr_list; *pp != NULL; pp++) {
                /* inet_ntop converts binary IP to printable string */
                printf(" %s",
                       inet_ntop(h->h_addrtype, *pp,
                                 str, INET6_ADDRSTRLEN));
            }
            printf("\n");
        }
    }

    exit(EXIT_SUCCESS);
}

Sample output:

$ ./t_gethostbyname www.jambit.com
Canonical name: jamjam1.jambit.com
  alias(es):  www.jambit.com
  address type: AF_INET
  address(es):  62.245.207.90

Key Points to Remember
1. Static buffer — not thread-safe
gethostbyname() returns a pointer to a statically allocated structure. Each call overwrites the previous result. Never call it from two threads simultaneously. Use getaddrinfo() in multi-threaded programs.
2. h_errno, not errno
On failure, gethostbyname() sets h_errno. Common values: HOST_NOT_FOUND, TRY_AGAIN, NO_RECOVERY, NO_ADDRESS. Use hstrerror(h_errno) to get a string.
3. IP addresses are in network byte order
The addresses in h_addr_list are raw binary in network byte order. Use inet_ntop() to convert to a readable string. Do not try to print them directly.
4. Obsolete — prefer getaddrinfo()
gethostbyname() does not support IPv6 properly in a single call. The modern API is getaddrinfo(), which is reentrant, protocol-independent, and handles both IPv4 and IPv6.

Mini Example: Get First IP of a Host

A focused example showing how to get just the first IP address:

#define _BSD_SOURCE
#include <stdio.h>
#include <netdb.h>
#include <arpa/inet.h>

int main(void)
{
    const char *hostname = "www.example.com";
    struct hostent *h;
    char buf[INET_ADDRSTRLEN];

    h = gethostbyname(hostname);
    if (h == NULL) {
        herror("gethostbyname");    /* herror() prints to stderr automatically */
        return 1;
    }

    /* h_addr is a macro for h_addr_list[0] */
    printf("First IP of %s: %s\n",
           hostname,
           inet_ntop(AF_INET, h->h_addr, buf, sizeof(buf)));

    return 0;
}
/* Expected output (actual IP may differ):
   First IP of www.example.com: 93.184.216.34
*/

h_errno Error Codes

These are set in h_errno when gethostbyname() returns NULL:

h_errno Value Meaning
HOST_NOT_FOUND The hostname does not exist in DNS
TRY_AGAIN Temporary DNS failure — try again later
NO_RECOVERY Permanent DNS server error
NO_ADDRESS Host exists in DNS but has no IP address record

Interview Questions & Answers
Q1: What does gethostbyname() return on failure and how do you get the error message?

It returns NULL and sets the global variable h_errno. To get the error string, call hstrerror(h_errno) or call herror("message") which prints to stderr automatically.

Q2: Why is gethostbyname() not safe to use in multi-threaded programs?

It returns a pointer to a statically allocated struct hostent that is shared across all threads. A second thread calling gethostbyname() overwrites the same buffer, corrupting the result in the first thread. Use getaddrinfo() instead, which allocates new memory per call and is thread-safe (reentrant).

Q3: What is the difference between h_name and h_aliases in struct hostent?

h_name is the official canonical DNS name of the host (e.g., jamjam1.jambit.com). h_aliases is a NULL-terminated array of alternative names (aliases) for the same host (e.g., www.jambit.com). When you query by an alias, the canonical name may differ from what you searched for.

Q4: How do you convert the IP address from struct hostent into a printable string?

The addresses in h_addr_list are raw binary in network byte order. Use inet_ntop(h->h_addrtype, *pp, str, sizeof(str)). Pass h_addrtype (either AF_INET or AF_INET6) so inet_ntop knows the format.

Q5: What is the modern replacement for gethostbyname() and why is it preferred?

getaddrinfo() is the modern replacement. It is preferred because it is thread-safe (allocates memory per call), works for both IPv4 and IPv6 in a single call, is protocol-independent, and also integrates service (port) name resolution in the same call.

Q6: What does the macro h_addr expand to?

It expands to h_addr_list[0], which is the first IP address in the list. It was provided as a backward-compatible convenience macro since older code often assumed a host had only one address.

Next: Service Name Resolution

Learn how getservbyname() and getservbyport() map service names like “http” or “ftp” to port numbers.

Part 2: getservbyname() →

Leave a Reply

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