IPv6 fundamentals
IPv6 from first principles: address structure, SLAAC, Neighbor Discovery, extension headers, PMTUD, and the operational realities of dual stack.
The lazy version of IPv6 — "IPv4 with longer addresses" — is wrong in a way that produces operational pain. The protocol's authors had two decades to look at IPv4's working set and the bugs that piled up around it, and they redesigned the parts that aged worst. Address space is the headline change, but the model for host configuration, neighbor resolution, fragmentation, and broadcast all changed too, and not understanding those changes is what makes IPv6 networks misbehave in subtle ways. This module is the foundational pass: enough to read an IPv6 address fluently, capture and interpret Neighbor Discovery traffic, and have a defensible opinion about the dual-stack tradeoffs you'll meet in practice.
Prerequisites
- Module 1.2 — Ethernet and MAC addressing. The contrast with ARP is going to do a lot of work here.
- Module 1.3 — IPv4 addressing and subnetting deep dive. You need fluency with binary prefix math because the same arithmetic shows up in IPv6, just at 128 bits.
Learning objectives
By the end of this module you should be able to:
- Explain the structural changes that make IPv6 different from IPv4 — not just "more addresses" — and predict how those changes affect router fast paths, host configuration, and neighbor resolution.
- Parse any IPv6 address into prefix, subnet, and interface identifier, and classify it by scope: link-local, unique-local, global unicast, multicast, anycast, or special-purpose.
- Describe SLAAC, Neighbor Discovery, and link-local addressing as one coherent host-configuration story rather than three unrelated mechanisms.
- Diagnose the most common operational IPv6 misunderstandings, especially the reflexive "just disable it" pattern that we've all seen in production.
- Use
ip -6 addr,ip -6 route, andtcpdumpICMPv6 captures to see what your host is actually doing on an IPv6-enabled segment.
Why IPv6 exists, beyond "more addresses"
The address-exhaustion story is real but boring. The IANA free pool of IPv4 prefixes ran out in 2011, regional registries finished allocating between 2011 and 2024 depending on region, and the secondary market for IPv4 addresses now routinely transacts at $50+ per address. None of that, on its own, motivates the protocol-redesign choices in IPv6. If "more addresses" had been the only goal, IPv4 with a 128-bit address field — call it IPv4 Long — would have been simpler.
What IPv6 actually changed:
Cleaner forwarding-path semantics. IPv6 routers don't fragment in-flight packets. The fixed-size 40-byte base header doesn't have the variable-length options field that made IPv4 routing harder than it needed to be. Header processing fits in fewer ASIC stages.
Real autoconfiguration. A host on an IPv6 segment can come up with a working global address in milliseconds, with no DHCP server in the loop. SLAAC handed the address-assignment problem to the host itself, parameterized by a router advertisement. The DHCP world is still there as DHCPv6 if you want it, but it's no longer the default.
Multicast instead of broadcast. Broadcasts touch every host on the segment, even the ones that don't care. Multicast replaces them with group-membership semantics: only hosts that subscribed to a group receive its traffic. Most LAN housekeeping (Neighbor Discovery, router discovery, multicast DNS) was rebuilt on multicast, dramatically reducing per-host noise on a busy segment.
Restored end-to-end addressability. RFC 1918 and the resulting NAT empire fragmented the IPv4 internet into a quilt of overlapping private spaces. IPv6 has enough address space that every interface anywhere can have a unique global address. NAT becomes optional. End-to-end protocols that broke under IPv4 NAT — IPsec, multicast applications, peer-to-peer protocols — work cleanly when both ends are on IPv6.
These four matter at least as much as the address count. Whether they matter to you depends on what you're building, but the architecture is doing more than padding the address field.
Reading an IPv6 address without panic
An IPv6 address is 128 bits, written as eight groups of four hexadecimal digits separated by colons:
2001:0db8:85a3:0000:0000:8a2e:0370:7334
Two shorthand rules let you compress this:
- Leading zeros in a group are optional.
0db8becomesdb8,0000becomes0,8a2estays8a2e. - One run of all-zero groups can be replaced with
::. Only one run, only once per address (otherwise the expansion is ambiguous).
So the address above compresses to 2001:db8:85a3::8a2e:370:7334. Both forms are equivalent; standards-compliant parsers handle both. You'll see the long form in protocol traces and the short form in user-facing tooling.
The categories of addresses you'll meet in practice:
| Prefix | Purpose | Notes |
|---|---|---|
2000::/3 | Global unicast | Routable on the public IPv6 internet |
fe80::/10 | Link-local | Auto-configured on every IPv6 interface; not routable beyond the link |
fc00::/7 | Unique local addresses (ULA) | RFC 4193 — the IPv6 equivalent of RFC 1918, used for private networks |
ff00::/8 | Multicast | Group-based delivery |
::1/128 | Loopback | The 127.0.0.1 of IPv6; one address, not a whole /8 |
::/128 | Unspecified | Used during address acquisition |
2001:db8::/32 | Documentation | RFC 3849 — never routed, safe in examples |
The convention for documentation is to use 2001:db8:: prefixes, much like the IPv4 ranges 192.0.2.0/24 and friends. We'll use 2001:db8:: throughout.
A note on link-local: every IPv6 interface gets a link-local address automatically as soon as the interface comes up, before any DHCP or SLAAC has run. The address starts with fe80:: and the bottom 64 bits are derived from the interface MAC or a random secret. Link-local is the bootstrapping address — it's how Neighbor Discovery talks to routers and peers before global addresses are even configured. It's also why ping6 fe80::1%eth0 works: the %eth0 ("zone identifier") is required because the same fe80:: address can exist on multiple interfaces.
Prefixes, interface identifiers, and the conventional /64
An IPv6 address has two parts by convention: a 64-bit routing prefix (network identifier) and a 64-bit interface identifier (host portion). This isn't enforced by the protocol — you can run any prefix length you like — but the entire ecosystem assumes /64 for end-segment subnets. SLAAC requires it. Multicast scoping mostly assumes it. ISPs hand out /56 or /48 blocks to customers expecting them to be carved into /64 subnets at deployment.
The 64 bits of host space is, intuitively, absurd. A single /64 contains 18 quintillion addresses — far more than every IPv4 host in history combined. The reason is structural rather than capacity-driven: a /64 interface identifier is large enough that hosts can pick their own with negligible collision probability without coordinating with anyone. The address space is wasteful by design so that autoconfiguration is robust.
Three ways an interface identifier gets formed:
EUI-64. The original method: stretch the 48-bit MAC address to 64 bits by inserting fffe in the middle and flipping the universal/local bit. This produces a deterministic per-MAC address. Pros: easy to compute, debug, and maintain across reboots. Cons: leaks the MAC, which leaks the vendor and is a long-term tracking identifier. Modern stacks default to other methods.
Privacy addresses (RFC 4941, now RFC 8981). Generate a random 64-bit interface identifier and rotate it on a schedule (typically daily). Hosts may have multiple addresses simultaneously: a stable one for accepting incoming connections, a temporary one for outgoing. This is what most consumer OSes default to.
Stable secret-based (RFC 7217). Derive a random-looking but stable interface identifier from a per-host secret and the network prefix. Same address persists for the same host on the same network, but different across networks. Servers tend to use this; it gives you stability for DNS while not leaking identity to networks the host has never visited.
The choice matters for what other systems can fingerprint. EUI-64 makes a host trivially trackable across networks. Privacy addresses defeat naive cross-network tracking. Stable-secret is the middle ground for systems that need predictable inbound addressing.
The IPv6 base header
The IPv6 base header is exactly 40 bytes:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| Traffic Class | Flow Label |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Length | Next Header | Hop Limit |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| Source Address |
+ (128 bits) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| Destination Address |
+ (128 bits) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The fields:
- Version (4 bits). Always
6. The first nibble of an IPv6 packet is0110. - Traffic Class (8 bits). The IPv6 equivalent of the IPv4 TOS / DSCP field. Used for QoS classification in transit.
- Flow Label (20 bits). Hint to routers that all packets with the same source, destination, and Flow Label belong to one flow and should follow the same path. Underused in practice, but useful for ECMP load-balancing decisions.
- Payload Length (16 bits). Length of everything after the base header, in bytes. Maximum 65,535. Jumbograms beyond this require an extension header.
- Next Header (8 bits). Identifies what comes after this header. Values include
6(TCP),17(UDP),58(ICMPv6), or one of the extension-header values that chains another IPv6 header in front of the payload. - Hop Limit (8 bits). The IPv6 equivalent of TTL. Decremented by every router; when it hits zero, the packet is dropped and an ICMPv6 message returns to the source.
- Source / Destination Address. 128 bits each.
The two consequential changes from IPv4:
Fixed length. No options field. The IPv4 header is 20–60 bytes depending on options; IPv6's base is always 40. Hardware can parse the header in fewer cycles. Options that IPv4 stuffed inline live in extension headers that come after the base; routers that don't care about a particular extension header pass it through untouched.
No header checksum. IPv4 has a checksum over the header; IPv6 doesn't. The reasoning is that the link layer (Ethernet FCS) and the transport layer (TCP/UDP checksum) already cover the bits that matter, and recomputing a header checksum at every router was a measurable expense for no real safety gain.
The Next Header chain is what gives IPv6 its extensibility. A packet might have base header → Hop-by-Hop Options (Next Header = 0) → Fragment header (Next Header = 44) → TCP (Next Header = 6). Each header tells you what's next. Routers process only the headers they care about; the rest pass through.
The operational catch with extension headers — and this is real — is that some firewalls and middleboxes drop packets with extension headers they don't understand. Hop-by-Hop and Routing headers are particularly vulnerable. The pragmatic deployment guidance: don't rely on extension headers other than Fragment for paths that traverse the public internet.
Stateless address autoconfiguration (SLAAC)
When an IPv6 host comes up on a network, here's what happens:
Step 1: derive link-local. The interface forms an fe80::/64 address using its chosen IID method (privacy, stable, or EUI-64). The host now has a working address for control-plane traffic on the local link.
Step 2: duplicate-address detection (DAD). The host sends a Neighbor Solicitation for its own intended address. If anyone replies, the address is in use; the host gives up and logs a conflict. If silence, the host commits to the address.
Step 3: router solicitation. The host sends a Router Solicitation (ICMPv6 type 133) to the all-routers multicast address ff02::2. Any IPv6 router on the link replies with a Router Advertisement (type 134).
Step 4: prefix learning. The Router Advertisement contains a list of on-link prefixes (typically a /64), the link's MTU, the default-router's link-local address, and DNS server addresses (per RFC 8106). Plus flags telling the host whether to use SLAAC, DHCPv6, or both for address acquisition.
Step 5: address formation. The host concatenates the advertised /64 prefix with its IID to form a global address. DAD again. Address commits.
Step 6: routing. The host installs a default route via the router's link-local address.
The whole sequence takes a few hundred milliseconds. No DHCP server, no per-network configuration database, no client/server matchmaking. The router advertises, the host listens, the host configures itself.
Routers send periodic unsolicited Router Advertisements as well (every 4–10 minutes by default), so hosts that miss the initial round still learn the prefix. That's also how prefix changes propagate: the router updates its RA, hosts re-learn, old prefix gets a "deprecated" lifetime and is eventually retired.
DHCPv6 still exists for cases where SLAAC isn't enough — typically when you want centrally-tracked address assignment, additional configuration parameters that don't fit in an RA, or strict address registration for compliance. Most consumer networks use SLAAC; many enterprise networks use DHCPv6; many use both with overlapping responsibilities.
Neighbor Discovery replaces ARP
ARP solved a specific problem on IPv4 LANs: given an IP address, find the MAC. The protocol was a dedicated EtherType (0x0806) outside the IP header, used broadcast for queries, and kept a flat cache.
IPv6 abandoned the dedicated protocol entirely. Neighbor Discovery (NDP, RFC 4861) is built on ICMPv6 and uses multicast instead of broadcast. The five message types you'll see:
- Router Solicitation (RS, type 133): host asks "who are the routers here?"
- Router Advertisement (RA, type 134): routers reply with prefix info, MTU, default router presence.
- Neighbor Solicitation (NS, type 135): equivalent of ARP request; "who has address X?"
- Neighbor Advertisement (NA, type 136): reply to NS.
- Redirect (type 137): router tells a host "for that destination, use a different next hop on this link."
The protocol upgrades from ARP in three meaningful ways:
Multicast scoping. An NS for 2001:db8::5 is sent to the solicited-node multicast address of the target, which is derived from the last 24 bits of the target's address. Only hosts whose address shares those 24 bits in the last group will receive the NS. On a busy LAN, this is dramatically less wasteful than broadcasting to every interface.
Reachability tracking. NDP keeps state per neighbor: whether the entry is REACHABLE, STALE, PROBE, or INCOMPLETE. A neighbor that hasn't been heard from gets re-probed before its entry is used; a neighbor that doesn't respond is moved to FAILED and removed. ARP had no equivalent state machine and would silently happily forward to dead MACs.
ICMPv6 integration. NDP messages share the ICMPv6 number space with ping, error replies, and other IPv6 control-plane traffic. This means filtering rules and middleboxes that handle ICMPv6 sensibly already handle NDP. Filtering rules that block all ICMPv6 break NDP and disable IPv6 entirely — a common mistake on poorly-configured firewalls.
To watch this live, point tcpdump at an IPv6-enabled interface and ping a host on the same segment:
sudo tcpdump -ni en0 'icmp6 and (ip6[40] = 135 or ip6[40] = 136)'
(Filter ICMPv6 type 135 = NS, 136 = NA.)
You'll see the requesting host send NS, the target respond with NA, and the kernel populate a neighbor entry visible in ip -6 neigh.
Path MTU discovery and the no-fragmentation rule
IPv4 routers can fragment in-flight packets if a downstream link has a smaller MTU. This was a useful flexibility in 1981 and is now an anti-pattern: it forces routers to do work that endpoints could do better, and reassembly requires holding state at the receiver. IPv6 explicitly removes router-side fragmentation. If a packet is too big for a downstream link, the router drops it and sends an ICMPv6 "Packet Too Big" message back to the source.
The source is then expected to do Path MTU Discovery (PMTUD, RFC 8201): cache the per-destination path MTU and segment future packets at the application or transport layer to fit. If a "Packet Too Big" arrives later because the path changed, the cache updates.
PMTUD works beautifully when ICMPv6 flows freely. It fails when something on the path drops the "Packet Too Big" messages — frequently a misconfigured firewall. The packet keeps getting dropped, the source never learns why, and the application sees a black hole. This is the most common IPv6 connectivity failure mode in the wild.
The IPv6 minimum MTU is 1280 bytes (RFC 8200, Section 5). Hosts may always send packets up to 1280 without worrying about path MTU. For larger packets, they must do PMTUD or they're gambling.
The operational consequence: don't blanket-block ICMPv6. Block specific types if you must, but allow the types that NDP and PMTUD need: 1, 2, 3, 4, 128, 129, 133, 134, 135, 136. Otherwise you'll have IPv6 connectivity issues that nobody can debug because the diagnostic packets are themselves being dropped.
Operational reality: dual stack, privacy extensions, and "just disable it"
The honest deployment picture for most networks today is dual stack: IPv4 and IPv6 running in parallel on every interface. Hosts get both, applications resolve both A and AAAA records, and the kernel picks one for each connection (Happy Eyeballs, RFC 8305, races them with a small head-start for IPv6). This is fine when both stacks work; when one is misconfigured, it produces some of the most frustrating network bugs in the field.
The dual-stack failure pattern most operators hit: the IPv6 stack is partially configured and silently leaks. Some examples:
- The application configures an IPv4-only VPN tunnel. The OS still has IPv6 connectivity to the local LAN and the public internet via SLAAC. Traffic to dual-stack destinations leaks around the tunnel via the IPv6 path.
- The corporate firewall blocks IPv4 access to internal resources but the IPv6 ACLs are missing or wrong. Intended-blocked traffic flows freely on IPv6.
- DNS servers return both
AandAAAA, but the IPv6 path is broken at one hop. Connections fail intermittently in a way that looks like flaky network when actually it's broken IPv6.
The reflexive engineering response to all of these has been "just disable IPv6." It works as a tactical patch — the symptoms go away. It is also, structurally, an admission that you didn't get IPv6 right. The architecture is generally cleaner than IPv4. The right long-term answer is to fix the dual-stack configuration, not abandon the protocol.
That said: there are legitimate cases for partial or full IPv6 disablement. If you're running a single-tenant VM behind a tunnel that you've explicitly designed as IPv4-only, you can disable IPv6 on that interface and reduce attack surface. If your traffic-anonymity tool only handles IPv4, you may want IPv6 off until the tool catches up. The discipline is to know why you're disabling it and to revisit the decision when the underlying constraint changes.
For a more detailed operational treatment of the leak-prevention angle, see the IPv6 leak prevention deep dive once that module is published.
Hands-on exercise
Exercise 1 — Inspect IPv6 addresses and routes on a live host
# Show all IPv6 addresses on all interfaces.
ip -6 addr
# Show the IPv6 routing table.
ip -6 route
# Show the IPv6 neighbor cache.
ip -6 neigh
Look at ip -6 addr output. For each address, identify the scope:
fe80::...is link-local. Every interface has one.2000::...(or any address with the first three bits001) is global unicast.fc00::...orfd00::...is unique-local. Used for organizational private networks.::1is loopback.
Now look at ip -6 route. The default route should be default via fe80::... dev <iface>. Notice the gateway is a link-local address, not a global one — that's standard. The router-discovery process handed back the router's link-local address, and the kernel uses that as the next-hop for the default route.
Stretch: look at your interface's inet6 lines. Are there multiple global addresses? If yes, one is probably the stable address for inbound and the others are temporary privacy addresses for outbound. You can confirm by looking at the dynamic and noprefixroute flags or by checking ip -6 addr show dynamic if your kernel supports it.
Exercise 2 — Capture Neighbor Discovery
In one terminal, start a capture:
sudo tcpdump -ni en0 'icmp6 and ip6[40] >= 133 and ip6[40] <= 136'
(ip6[40] is the ICMPv6 type byte; types 133–136 are the four NDP messages.)
In another terminal, ping a neighbor on the same LAN — your default gateway works:
ping6 -c 3 $(ip -6 route | awk '/default/ {print $3; exit}')
In the capture, you should see:
- A Neighbor Solicitation from your host to the solicited-node multicast address derived from the gateway's IPv6 address.
- A Neighbor Advertisement from the gateway, unicast to your host.
- Three echo-request and echo-reply pairs.
If your host already has the gateway in its neighbor cache (which ip -6 neigh would show as REACHABLE), the NS/NA exchange may not happen. Force it:
sudo ip -6 neigh flush all
Then re-run the ping. The fresh resolution will produce the NS/NA you want to see.
Common misconceptions
"IPv6 is just IPv4 with longer addresses." It also moved fragmentation off the router fast path, replaced ARP with NDP, replaced broadcast with multicast, defined autoconfiguration as a first-class mechanism, and changed the operational story for dual-stack hosts. Treating the protocol as IPv4 with longer fields makes you wrong about half of those.
"Disabling IPv6 is the proper fix for every leak." It's a tactical workaround. The proper fix is to configure both stacks consistently. Disabling IPv6 buys time to do that correctly; if you stop there, you're leaving the architecture worse than it could be.
"IPv6 has no NAT, so it's insecure." NAT was never security; it was a side effect of address-space scarcity that some people retroactively classified as a firewall feature. IPv6 networks should run actual stateful firewalls — the kind that explicitly drop unsolicited inbound. Most consumer routers do this by default for IPv6, just like they do for IPv4.
"A /64 is wasteful, so smaller subnets are smarter." SLAAC, multicast scoping, privacy address rotation, and several other ecosystem behaviors assume /64. A /96 or /120 for end-user segments is technically valid and operationally awful — you'll fight the ecosystem for years. Keep /64 per segment; you have plenty of address space.
"Routers can fragment oversized IPv6 packets like IPv4 routers did." They cannot. PMTUD is mandatory for packets above 1280 bytes, and PMTUD only works if ICMPv6 "Packet Too Big" messages can return to the source. Blocking ICMPv6 broadly breaks IPv6.
Further reading
- RFC 8200 — Internet Protocol, Version 6 (IPv6) Specification. The base header, extension-header model, and packet-size rules straight from the standard. Consolidates and replaces the older RFC 2460.
- RFC 4291 — IPv6 Addressing Architecture. The cleanest single document on address types, notation, and scope.
- RFC 4861 — Neighbor Discovery for IP version 6. ARP replacement, router discovery, reachability detection, and on-link behavior. Long, but the structure is clean.
- RFC 4862 — IPv6 Stateless Address Autoconfiguration. The SLAAC state machine and address-formation rules.
- RFC 8201 — Path MTU Discovery for IP version 6. The operational consequence of endpoint-only fragmentation, and what to do when ICMPv6 is broken on the path.
The next module — IP forwarding plane — covers what routers actually do once they have either an IPv4 or IPv6 packet in hand, and how routing tables, longest-prefix match, and the FIB combine to move bytes between networks.
// related reading
DNS — name resolution end to end
DNS from first principles: zones, delegation, recursive vs authoritative resolvers, the wire format, caching, DNSSEC, DoH/DoT/DoQ, and where privacy actually leaks.
HTTP/1.1, HTTP/2, HTTP/3 — the evolution
Why HTTP needed three rewrites in twenty years: pipelining's failure, HTTP/2's multiplexing, QUIC's leap to UDP, and the head-of-line blocking that connects all three.
The IP forwarding plane
How a router actually forwards a packet: longest-prefix match, FIB lookup, adjacency resolution, TTL/Hop Limit, fragmentation, ICMP feedback, and the data/control/management plane split.