Networking in C · intermediate · ~20 min
Resolve a hostname to a sockaddr safely and defensively.
getaddrinfo looks up a hostname and returns one or more sockaddrs. It handles IPv4, IPv6, and service-name-to-port translation in one call.
Hand-rolling DNS is a security trap. getaddrinfo is the only sane choice for any code that takes a hostname from configuration or input.
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.
#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);
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).
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);
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);
getent ahosts example.com (Linux) shows what the resolver would return. dig +short is shorter.
The addrinfo * list is allocated by glibc; freeaddrinfo releases it. Forgetting is a small but cumulative leak.
curl, wget, every HTTP client library, every TLS terminator. The standard resolver entry point.
getaddrinfo replaces gethostbyname. Walk the result list, free it, and check the addresses before you connect.