Networking in C · intermediate · ~20 min

DNS resolution with getaddrinfo

Resolve a hostname to a sockaddr safely and defensively.

Overview

getaddrinfo looks up a hostname and returns one or more sockaddrs. It handles IPv4, IPv6, and service-name-to-port translation in one call.

Why it matters

Hand-rolling DNS is a security trap. getaddrinfo is the only sane choice for any code that takes a hostname from configuration or input.

Core concepts

hints. Mostly ai_family = AF_UNSPEC (let kernel pick v4 or v6), ai_socktype = SOCK_STREAM. Set AI_NUMERICHOST to refuse DNS and only accept IP literals.

Result list. Walk ai_next; on each, try socket + connect. The first that succeeds wins.

Error reporting. getaddrinfo returns a code, not -1. Use gai_strerror(rc) to format.

Pentester mindset. SSRF (Server-Side Request Forgery) exploits resolvers: attacker passes localhost.attacker.com that resolves to 127.0.0.1. Defence: after resolution, reject results in RFC1918, link-local, loopback, multicast ranges. This is what getaddrinfo-allowlist exercises.

Defensive coding habit. Always freeaddrinfo. Always set an explicit timeout on the subsequent connect. Never trust the hostname alone — inspect the resolved address.

Syntax notes

#include <netdb.h>
int  getaddrinfo(const char *node, const char *service,
                 const struct addrinfo *hints, struct addrinfo **res);
void freeaddrinfo(struct addrinfo *res);
const char *gai_strerror(int errcode);

Lesson

getaddrinfo(3) is the modern, IPv4+IPv6-clean replacement for the old gethostbyname. It returns a linked list of addrinfo results which you walk until one succeeds. Defensive code refuses any result that resolves to internal address ranges (SSRF defence).

Code examples

struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM };
struct addrinfo *res;
int rc = getaddrinfo("example.com", "443", &hints, &res);
if (rc) { fprintf(stderr, "%s\n", gai_strerror(rc)); return -1; }
for (struct addrinfo *p = res; p; p = p->ai_next) { /* try connect */ }
freeaddrinfo(res);

Line by line

struct addrinfo hints = {0};
hints.ai_family   = AF_UNSPEC;        /* v4 or v6 — kernel picks */
hints.ai_socktype = SOCK_STREAM;
struct addrinfo *res = NULL;
int rc = getaddrinfo(host, port, &hints, &res);
if (rc) { fprintf(stderr, "%s\n", gai_strerror(rc)); return -1; }
for (struct addrinfo *p = res; p; p = p->ai_next) {
    /* try p->ai_addr / p->ai_addrlen with socket+connect */
}
freeaddrinfo(res);

Common mistakes

  • Forgetting freeaddrinfo (leak).
  • Treating rc with perror — wrong; use gai_strerror.

Debugging tips

getent ahosts example.com (Linux) shows what the resolver would return. dig +short is shorter.

Memory safety

The addrinfo * list is allocated by glibc; freeaddrinfo releases it. Forgetting is a small but cumulative leak.

Real-world uses

curl, wget, every HTTP client library, every TLS terminator. The standard resolver entry point.

Practice tasks

  1. Resolve 'localhost' and 'example.com', print every returned address. 2. Refuse to connect if the resolved address is in 127/8 or 10/8. 3. Add a 3-second deadline via SIGALRM.

Summary

getaddrinfo replaces gethostbyname. Walk the result list, free it, and check the addresses before you connect.

Practice with these exercises