Networking in C · beginner · ~15 min

IPv6 mechanics — addresses and dual-stack

Bind, connect, and parse IPv6 alongside IPv4 cleanly.

Overview

IPv6 has 128-bit addresses and a slightly different sockaddr. The Berkeley API is otherwise identical. inet_pton(AF_INET6, ...) parses both forms.

Why it matters

Modern internet is dual-stack. Any server that listens on IPv4-only is missing half its addressable clients. Any parser that breaks on IPv6 is a CVE waiting.

Core concepts

Address types. Loopback ::1; link-local fe80::/10; documentation 2001:db8::/32; ULA fc00::/7; multicast ff00::/8.

Scope IDs. fe80::1%eth0 — the %eth0 is the scope (interface name or index) needed for link-local addresses.

Dual-stack. A single AF_INET6 socket can accept v4-mapped (::ffff:1.2.3.4) connections unless you set IPV6_V6ONLY=1.

Pentester mindset. IPv6 introduces new private/internal ranges (ULA, link-local) that allow-lists must consider. Don't only block 10.0.0.0/8 — also block fc00::/7 and fe80::/10.

Defensive coding habit. When binding a server, listen on a specific address (not ::), or set IPV6_V6ONLY explicitly so you know whether v4 traffic is allowed.

Syntax notes

struct sockaddr_in6 {
    sa_family_t      sin6_family;
    in_port_t        sin6_port;
    uint32_t         sin6_flowinfo;
    struct in6_addr  sin6_addr;
    uint32_t         sin6_scope_id;
};

Lesson

IPv6 addresses are 128 bits, written as eight 16-bit hex groups joined by colons. The struct sockaddr_in6 mirrors sockaddr_in; inet_pton/inet_ntop handle both families uniformly.

Code examples

struct sockaddr_in6 a6 = { .sin6_family = AF_INET6, .sin6_port = htons(8080) };
inet_pton(AF_INET6, "::1", &a6.sin6_addr);
int s = socket(AF_INET6, SOCK_STREAM, 0);
int v6only = 1; setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof v6only);
bind(s, (struct sockaddr *)&a6, sizeof a6);

Line by line

struct sockaddr_in6 a = { .sin6_family = AF_INET6 };
a.sin6_port = htons(8080);
inet_pton(AF_INET6, "::1", &a.sin6_addr);    /* parse loopback */
int s = socket(AF_INET6, SOCK_STREAM, 0);
int yes = 1;
setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof yes);
bind(s, (struct sockaddr *)&a, sizeof a);

Common mistakes

  • Hardcoding AF_INET. Use AF_UNSPEC in getaddrinfo and handle both.
  • Forgetting IPV6_V6ONLY when you want to listen on both v4 and v6 separately.

Debugging tips

ip -6 addr lists IPv6 addresses. ping6 ::1 verifies loopback. nc -6 ::1 8080 connects from the shell.

Memory safety

sin6_addr is 16 bytes; use inet_pton/inet_ntop (not inet_aton).

Real-world uses

Every modern web server, every cloud provider's load balancer, every operating system since 2010.

Practice tasks

  1. Bind to ::1:8080 and accept a connection from nc -6. 2. Parse a string into a discriminated v4/v6 union. 3. Reject any IPv6 result in ULA + link-local + loopback.

Summary

IPv6 = 128-bit addresses, separate sockaddr, same API. Dual-stack via AF_UNSPEC + V6ONLY toggle. Don't break the parser on it.

Practice with these exercises