Networking for beginners. Part 13: IPv6: addresses and the header

All parts of the series “Networking for beginners”

In part five we ended with the 4.3 billion IPv4 addresses running out in 2011, and in part six with NAT, which patches over that shortage. The answer, conceived back in the 1990s and now final, is IPv6, RFC 8200. It is not "IPv4 with longer addresses": along with the addresses, the header was redesigned, broadcast was removed, routers were forbidden to fragment, autoconfiguration was built in, and NAT was thrown out as an idea. We start this four-part block with addresses and the header; the protocols that bring IPv6 to life — Neighbor Discovery, SLAAC and DHCPv6 — come next.

The IPv4 series was long for a reason: almost everything in it will be useful here, and the differences will stand out by contrast.


The address: 128 bits

There are 2¹²⁸ addresses — about 3.4 × 10³⁸. There is no point comparing that number with the number of devices; the point is that there are enough addresses not to economize: give every local network 2⁶⁴ addresses, give every subscriber thousands of such networks, and there will still be plenty left.

An address is written as eight groups of 16 bits in hexadecimal, separated by colons:

2001:0db8:1234:5678:0000:0000:0000:0001

Two shortening rules:

  1. Leading zeros in a group are dropped: 0db8 → db8, 0000 → 0.
  2. One longest run of all-zero groups is replaced with :: — only one, otherwise it would be ambiguous.
2001:0db8:1234:5678:0000:0000:0000:0001
2001:db8:1234:5678:0:0:0:1          after rule 1
2001:db8:1234:5678::1               after rule 2

Letters are lowercase (RFC 5952), and :: goes where there are more zeros. In a URL the address is put in square brackets to tell it apart from the port: https://[2001:db8::1]:443/. A prefix is written as in CIDR: 2001:db8:1234:5678::/64.

2001:db8::/32 is reserved for examples — the counterpart of the documentation range 192.0.2.0/24; that is what we will use.

What a global address is made of

 ┌──────────────────────────┬──────────────┬──────────────────────────────────┐
 │ provider prefix          │ subnet       │ interface identifier             │
 │ 48 bits                  │ 16 bits      │ 64 bits                          │
 │ 2001:db8:1234            │ 5678         │ ::1                              │
 └──────────────────────────┴──────────────┴──────────────────────────────────┘
                                 /64 — one local network

The rule that makes IPv6 simple: every local network is a /64. There is no "/26 for servers, /28 for printers": the mask arithmetic from part five is not needed here. The lower 64 bits are the interface identifier, and that size was chosen so a host can generate it on its own (from its MAC address or at random) without asking anyone — SLAAC is built on this, part 15. The upper 64 are the network prefix: the provider gives a subscriber a /48 block (65 thousand networks) or a /56 (256 networks), and the subscriber — or rather their router — hands out a /64 to each of its networks. A home network with a /56 from the provider is the norm, not a luxury.

How the subscriber's router learns its block — prefix delegation via DHCPv6 — is covered in part 16.


Address types

IPv4 had three kinds: unicast, broadcast, multicast. IPv6 has unicast, multicast and anycast — no broadcast. Within unicast there are several kinds with different scopes.

Prefix What Scope
2000::/3 global unicast — ordinary routable addresses, assigned by providers the whole internet
fe80::/10 link-local — always present on every interface, self-generated, never crosses a router one segment
fc00::/7 (in practice fd00::/8) ULA, unique local — the counterpart of private 10.0.0.0/8, but with 40 random bits after fd so two networks do not collide when merged within an organization
::1/128 loopback, like 127.0.0.1 host
::/128 unspecified, like 0.0.0.0: "no address" —
::ffff:0:0/96 IPv4-mapped: ::ffff:192.0.2.1 — how an IPv4 address looks inside an IPv6 socket, so one program can serve both versions within the host
ff00::/8 multicast by the second byte
2001:db8::/32 documentation —

Link-local is the most unfamiliar part for people coming from IPv4. An interface has this address before any configuration, from the moment it comes up, and it is needed for IPv6 itself to work: hosts use it to find the router and each other (part 14), and it is the router's link-local address, not its global one, that usually serves as the default gateway:

$ ip -6 route
default via fe80::da47:32ff:fe0a:b0c dev eth0 proto ra metric 1024

Since fe80::/10 is the same in every segment, the address alone does not say which interface to use, so a zone is attached to it: fe80::1%eth0. ping fe80::1 without a zone will not work on a machine with two interfaces.

Multicast replaced broadcast. The address ff02::1 means "all nodes on the segment" — that is the former broadcast; ff02::2 is all routers. The second byte sets the scope: ff02 is the segment, ff05 the site, ff0e the whole internet. The group ff02::1:ff00:0/104 is solicited-node, covered in the next part: it is the trick that lets neighbor lookup avoid bothering everyone. On Ethernet, IPv6 multicast addresses map to MAC 33:33:xx:xx:xx:xx with the four lower bytes of the address, as we saw in part two, and the network card filters out other groups in hardware: a host not subscribed to a group will not even see the frame.

Anycast is an ordinary unicast address assigned to several nodes; routing delivers the packet to the nearest one. The root DNS servers work this way, and it is done in IPv4 too, but in IPv6 it is officially sanctioned.

An interface always has several addresses

$ ip -6 addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP
    inet6 2001:db8:1234:5678:a1b2:c3d4:e5f6:789a/64 scope global temporary dynamic
       valid_lft 86153sec preferred_lft 14153sec
    inet6 2001:db8:1234:5678:3e52:82ff:fe1a:2b3c/64 scope global dynamic mngtmpaddr
       valid_lft 86153sec preferred_lft 86153sec
    inet6 fd12:3456:789a:1::10/64 scope global
       valid_lft forever preferred_lft forever
    inet6 fe80::3e52:82ff:fe1a:2b3c/64 scope link
       valid_lft forever preferred_lft forever

Four addresses on one interface, and that is normal:

  • scope link — link-local, it came first;
  • scope global … mngtmpaddr — global, built from the network prefix and the interface identifier. Here 3e52:82ff:fe1a:2b3c is derived from the MAC 3c:52:82:1a:2b:3c using the EUI-64 scheme: ff:fe is inserted in the middle, and the U/L bit from part two is flipped in the first byte (3c → 3e). It is stable, and the machine can be found by it;
  • scope global temporary — a temporary address with a random identifier: the stack uses it for outgoing connections and changes it once a day so websites cannot track the machine by the unchanging half of the address. Its preferred_lft is shorter: when it expires, the address stays to receive replies, but new connections will use a new one;
  • fd12:… — a ULA assigned for internal needs, for example so a printer is reachable at an address that does not depend on the provider.

Which one goes in as the source? The rules of RFC 6724: prefer an address of the same scope as the destination (link-local for link-local), with the longest common prefix, and a temporary one for outgoing traffic. Mistakes in this choice — when a ULA address goes out to the internet as the source — are one of the reasons for "IPv6 seems to work, but doesn't".


The header

Forty bytes, exactly, always:

 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)                      │
│                                                               │
├───────────────────────────────────────────────────────────────┤
│                                                               │
│                   Destination Address (128)                   │
│                                                               │
└───────────────────────────────────────────────────────────────┘

Let's compare it field by field with IPv4 from part six:

In IPv4 In IPv6
Version = 4 Version = 6
IHL, Options gone: the header is fixed, extensions are a chain of separate headers
DSCP, ECN Traffic Class, the same thing
— Flow Label: a flow label that lets routers keep packets of one connection on one path when load balancing, without looking at ports
Total Length Payload Length — the length of everything after the 40 bytes
Identification, Flags, Fragment Offset gone: fragmentation moved into an extension
TTL Hop Limit — an honest name for the same hop counter
Protocol Next Header — the same "what's inside" field, but now it can also point to an extension
Header Checksum gone
32-bit addresses 128-bit

Three decisions stand behind this table.

There is no checksum. Ethernet checks the frame, TCP and UDP check their segments together with a pseudo-header that includes the addresses. Checking the IP header a third time is extra work for every router, which on top of that had to recompute the checksum every time Hop Limit changed. The price: the UDP checksum, optional in IPv4, is mandatory in IPv6.

The header has a fixed length. A router does not need to read IHL to find where the data starts; it is always 40 bytes. Everything optional is moved into extension headers, which form a chain between the IPv6 header and the transport, each pointing to the next with its Next Header field:

┌──────────┬──────────────┬──────────────┬──────────┬─────────┐
│ IPv6     │ Hop-by-Hop   │ Fragment     │ TCP      │ data    │
│ NH = 0   │ NH = 44      │ NH = 6       │          │         │
└──────────┴──────────────┴──────────────┴──────────┴─────────┘
Next Header Extension Processed by
0 Hop-by-Hop Options every router on the path — the only one like that
43 Routing the nodes listed in it; hardly used
44 Fragment the receiver
50, 51 ESP, AH — an encrypted and a signed IPsec packet (part 19) the receiver
60 Destination Options the receiver
59 No Next Header nothing follows
6, 17, 58 TCP, UDP, ICMPv6 transport, the end of the chain

A router looks only at the first 40 bytes and, if there is no Hop-by-Hop, does not look any further at all. A firewall that needs ports has to walk the chain down to the transport, and, just as with IPv4 options, it often simply drops long chains of extensions.

Only senders fragment. A router whose packet does not fit the MTU of the next link never splits it in IPv6: it drops the packet and sends the sender an ICMPv6 Packet Too Big with the MTU size. The sender, if it really has to, splits the packet itself by adding a Fragment header, and only the receiver reassembles it. To make life simpler, the minimum MTU of any IPv6 link is 1280 bytes: a packet of that size will get through anywhere, no questions asked. The consequence for the firewalls from part eight is stricter here: Path MTU Discovery is not an option but the only mechanism, and filtering ICMPv6 Packet Too Big is guaranteed to break IPv6.


What's missing: broadcast and NAT

There is no broadcast because multicast does the same thing more neatly: a packet to ff02::1 reaches every node on the segment, while a packet to a solicited-node group reaches only those who may need it. ARP, with its broadcast requests that woke every machine on the segment, is replaced by Neighbor Discovery over multicast; details in the next part.

There is no NAT because there is no reason for it: there are enough addresses, and every device gets a global address. This brings back the end-to-end connectivity the internet was designed with: any node can accept a connection from any other, without port forwarding, STUN or relays from part nine. This raises two questions:

  • "So my fridge is reachable from the internet?" Reachable by address — yes; reachable in fact — only if the firewall on the router lets it through. NAT was never protection, it was a side effect; protection is a stateful firewall that lets in only replies to outgoing connections, and home IPv6 routers have it enabled by default with exactly that rule. The difference is that, if you want, the rule can be relaxed for a single device without inventing port forwards.
  • "What about privacy?" That is what the temporary addresses from the section above are for: outgoing connections come from a random identifier that changes daily.

Translation does exist in IPv6, but for something else: NAT64 together with DNS64 lets an IPv6-only network reach servers that only have IPv4. DNS64 synthesizes an AAAA record from the A record by embedding the IPv4 address into the 64:ff9b::/96 prefix, and NAT64 at the edge unpacks such packets into IPv4. This is how most carriers' mobile networks work: the phone lives in pure IPv6, and old websites are reached through NAT64. There is also NPTv6 — one-to-one prefix replacement without ports, for switching providers without renumbering — but it is rare too.


How IPv6 and IPv4 live together

The transition did not happen overnight, and most networks today are dual stack: a host has both addresses, a DNS name has both A and AAAA. The browser asks for both records, tries IPv6 first and, if the connection is not established within 250 ms, tries IPv4 in parallel — the algorithm is called Happy Eyeballs, and thanks to it broken IPv6 is almost invisible to the user, which is both good and bad: it stays broken for years.

Where the provider does not offer IPv6, you can get it through a tunnel: IPv6 packets are wrapped in IPv4 (Protocol 41) up to a tunnel broker that has IPv6. It is the same trick as VXLAN and IPsec from the block on tunnels: a protocol inside a protocol.


Hands-on

Addresses and their scopes:

$ ip -6 addr

Routes. Note the link-local gateway marked proto ra — it came not from configuration but from a router advertisement (part 14) — and the multicast route:

$ ip -6 route
2001:db8:1234:5678::/64 dev eth0 proto ra metric 1024 expires 86153sec
fe80::/64 dev eth0 proto kernel metric 1024
default via fe80::da47:32ff:fe0a:b0c dev eth0 proto ra metric 1024 expires 1753sec
ff00::/8 dev eth0 metric 256

The clearest demonstration that broadcast is gone is pinging all nodes on the segment via multicast:

$ ping -c 2 ff02::1%eth0
PING ff02::1%eth0(ff02::1%eth0) 56 data bytes
64 bytes from fe80::3e52:82ff:fe1a:2b3c%eth0: icmp_seq=1 ttl=64 time=0.05 ms
64 bytes from fe80::da47:32ff:fe0a:b0c%eth0: icmp_seq=1 ttl=64 time=0.41 ms (DUP!)
64 bytes from fe80::1a2b:3cff:fe4d:5e6f%eth0: icmp_seq=1 ttl=64 time=0.88 ms (DUP!)

Everyone on the segment answers with their link-local address; DUP! appears because ping expected one reply per request. This way you see all your neighbors in a second, even though IPv6 may be "not configured" on them: link-local is always there.

To check which source address the stack will pick and which way the packet will go:

$ ip -6 route get 2001:db8:9999::1
2001:db8:9999::1 from :: via fe80::da47:32ff:fe0a:b0c dev eth0 src 2001:db8:1234:5678:a1b2:c3d4:e5f6:789a metric 1024

src is the temporary address: the stack preferred it for an outgoing connection. Capture with tcpdump -i eth0 -n ip6, and in Wireshark the IPv6 header expands into the same eight fields, with the Next Header chain below it.

On Windows, ipconfig shows "IPv6 Address", "Temporary IPv6 Address" and "Link-local IPv6 Address" — the same three kinds — and netsh interface ipv6 show route shows the routes.


Key takeaways

  • 128 bits, eight hex groups, zeros are shortened, :: only once. In a URL — in brackets. Examples come from 2001:db8::/32.
  • A global address: 48 bits from the provider, 16 bits of subnet, 64 bits of interface. Every network is a /64; a subscriber gets a /48 or a /56.
  • An interface has several addresses: link-local fe80:: always, a stable global one, a temporary one for outgoing traffic, sometimes a ULA from fd00::/8. The gateway is the router's link-local address.
  • Broadcast is replaced by multicast: ff02::1 is all nodes, MAC 33:33:…, the card filters out other groups.
  • The header is 40 bytes with no checksum and no fragmentation fields; everything optional is in a chain of extension headers linked through Next Header. Hop Limit instead of TTL.
  • Routers do not fragment, the minimum MTU is 1280, and Packet Too Big must get through.
  • NAT is not needed and does not exist; protection is a stateful firewall; privacy is temporary addresses; NAT64/DNS64 is for getting from an IPv6-only network to the IPv4 world.

Next — Part 14: ICMPv6 and Neighbor Discovery: the protocol that does the job of ICMP, ARP and a bit more in IPv6. How a host finds a neighbor through solicited-node multicast, how it finds a router, what a Router Advertisement is, how address uniqueness is checked, and why you must not block ICMPv6 in any form.

Only registered users can leave a comment.

Sign in or register to leave a comment.