Lesson 10 / 18
IPv6
The notation rules of the 128-bit address, address types, autoconfiguration, header differences, and how the two versions interoperate.
Contents
The previous two lessons built methods for coping with the scarcity of the 32-bit address space: splitting private addresses carefully, giving each section exactly what it needs. These methods manage the scarcity; they do not remove it.
The solution aimed at the scarcity itself is expanding the address space. This lesson’s question is what decisions come bundled with quadrupling the address width — because address width sits at the neck of the hourglass introduced in the previous topic, and touching it affects everything.
128 Bits
An IPv6 address is 128 bits. The total number of addresses is , roughly . For comparison: IPv4’s addresses are one th of IPv6’s space.
Choosing such an extravagant width was not meant to maximize the address count. The goal is to ease hierarchical distribution: when addresses are abundant, every level can be given generous blocks, blocks can be kept contiguous, and prefixes can be summarized. In IPv4, the effort to conserve addresses had led to fragmented routing tables.
The visible consequence of this is that IPv6 subnets are always /64. Regardless of
how many hosts a subnet has — even if it is only two devices — it is given a 64-bit host
portion. Viewed with IPv4 habits, this is wasteful; in IPv6’s design, it is the
precondition for autoconfiguration.
Notation Rules
The address is written as eight hexadecimal groups separated by colons. There are three shortening rules:
- Leading zeros in each group can be dropped:
0db8→db8,0000→0. - Consecutive zero groups can be dropped once with
::. ::can be used at most once in an address; using it twice would leave it ambiguous how many groups each gap stands for.
import ipaddress for text in ("2001:0db8:0010:0003:0000:0000:0000:0001", "fe80:0000:0000:0000:0200:5eff:fe00:5301", "ff02:0000:0000:0000:0000:0000:0000:0001", "0000:0000:0000:0000:0000:0000:0000:0001", "2001:0db8:0000:0000:0001:0000:0000:0001"): a = ipaddress.ip_address(text) print(f"{text} -> {a.compressed}")
2001:0db8:0010:0003:0000:0000:0000:0001 -> 2001:db8:10:3::1 fe80:0000:0000:0000:0200:5eff:fe00:5301 -> fe80::200:5eff:fe00:5301 ff02:0000:0000:0000:0000:0000:0000:0001 -> ff02::1 0000:0000:0000:0000:0000:0000:0000:0001 -> ::1 2001:0db8:0000:0000:0001:0000:0000:0001 -> 2001:db8::1:0:0:1
The last line shows the third rule: the address has two separate runs of zeros, and only
the longer one is compressed. The shorter one must be written as 0:0. If the two
runs are equal in length, the left one is compressed.
When written together with a port number, the address is enclosed in square brackets:
[2001:db8:10:3::1]:443. Otherwise it cannot be told whether the final colon belongs to
the address or the port.
Address Types
IPv6 has no broadcast address. Broadcast’s function is met by multicast instead; this gives an “ask those who care” behavior in place of “ask everyone,” and it reduces unnecessary processing.
| Prefix | Type | Scope |
|---|---|---|
2000::/3 |
Global unicast address | Global Internet |
fe80::/10 |
Link-local | Same link only |
fc00::/7 |
Unique local address | Within the organization |
ff00::/8 |
Multicast | Embedded in prefix |
::1/128 |
Loopback | The machine itself |
::/128 |
Unspecified address | “I have no address” in the source field |
2001:db8::/32 |
Documentation | Not routed |
A link-local address is present on every IPv6 interface and is mandatory; it exists even without a global address. Router advertisements and neighbor discovery run over these addresses. Because the address is only meaningful within a single link, the same link-local address can repeat on different interfaces; this is why the interface must be specified when it is used.
A unique local address is the counterpart of IPv4’s private-use blocks: it is used within the organization and is not routed on the public Internet.
In multicast addresses, the prefix also carries the scope. ff02::1 denotes all nodes
within link scope, ff02::2 all routers within link scope.
The solicited-node multicast address is the mechanism that solves ARP’s broadcast
problem: the target address’s last 24 bits are appended to the ff02::1:ff00:0/104
prefix. The neighbor discovery query is sent to this address, and only the small number
of interfaces whose last 24 bits match process the message. This prevents every device
in the broadcast domain from having to process every query.
Autoconfiguration
IPv6 interfaces can configure an address without a server. Stateless address autoconfiguration (SLAAC) is four steps:
- The interface generates itself a link-local address (
fe80::prefix + interface identifier). - It performs duplicate address detection: it sends a neighbor request to the solicited-node group of the address it generated. If a reply arrives, the address is in use.
- It sends a router solicitation to
ff02::2. The router replies with a router advertisement containing the/64prefix used on the link. - The interface combines the advertised prefix with its own interface identifier to build its global address.
One way to generate the interface identifier is the EUI-64 conversion: ff:fe is
inserted into the middle of the 48-bit MAC address, and the universal/local bit is
flipped.
import ipaddress def eui64(mac: str) -> str: b = [int(x, 16) for x in mac.split(":")] b[0] ^= 0x02 # flip the universal/local bit raw = bytes(b[:3] + [0xFF, 0xFE] + b[3:]) return ":".join(raw[i:i + 2].hex() for i in range(0, 8, 2)) interface_id = eui64("00:00:5e:00:53:01") print("interface identifier:", interface_id) print("link-local address :", ipaddress.ip_address("fe80::" + interface_id)) print("global address :", ipaddress.ip_address("2001:db8:10:3:" + interface_id))
interface identifier: 0200:5eff:fe00:5301 link-local address : fe80::200:5eff:fe00:5301 global address : 2001:db8:10:3:200:5eff:fe00:5301
The example network’s client had MAC address 00:00:5e:00:53:01; the first byte’s
second bit was flipped to make it 02, and ff:fe was inserted in the middle. The
reason for the /64 subnet requirement is visible here: the interface identifier is 64
bits.
The EUI-64 conversion has a privacy problem: because the address embeds the hardware
address, the machine can be tracked even as it moves from network to network. For this
reason, end hosts mostly generate the interface identifier randomly and rotate it
periodically. Servers, by contrast, use fixed and readable identifiers — manually
assigned addresses like 2001:db8:10:4::20 are common.
SLAAC does not carry name server information. That information is delivered either through an option in the router advertisement or through the stateless leasing protocol defined for IPv6.
Header Differences
The IPv6 header is a fixed 40 bytes; it is larger than IPv4’s 20-byte header, but it has fewer fields and no variable length.
| Change | Rationale |
|---|---|
| Header checksum removed | The link layer and transport layer already check; recomputing at every hop is redundant |
| Fragmentation at routers removed | The cost of fragmentation is pushed to the endpoints; an intermediate node drops the packet and reports it |
| Header length fixed | The processing pipeline is simplified |
| Options moved to extension headers | They are appended as a chain, not bloating the main header |
| Minimum MTU raised to 1280 bytes | Reduces the need for path MTU discovery |
Removing the checksum is a direct application of the end-to-end principle: the check performed at the intermediate node was not necessary for correctness — it only added cost.
The Example Network’s IPv6 Plan
The organization has received a 2001:db8:10::/48 allocation from its provider. This
allocation contains 65,536 /64 subnets — far more than enough for the organization’s
five networks.
import ipaddress allocation = ipaddress.ip_network("2001:db8:10::/48") plan = { "Guest wireless": "2001:db8:10:1::/64", "Lab": "2001:db8:10:2::/64", "Administration": "2001:db8:10:3::/64", "Server room": "2001:db8:10:4::/64", "Link": "2001:db8:10:ffff::/64", } print("allocation:", allocation, "— /64 subnets inside:", 2 ** (64 - allocation.prefixlen)) for name, cidr in plan.items(): net = ipaddress.ip_network(cidr) print(f"{name:18s} {str(net):24s} inside allocation={net.subnet_of(allocation)}")
allocation: 2001:db8:10::/48 — /64 subnets inside: 65536 Guest wireless 2001:db8:10:1::/64 inside allocation=True Lab 2001:db8:10:2::/64 inside allocation=True Administration 2001:db8:10:3::/64 inside allocation=True Server room 2001:db8:10:4::/64 inside allocation=True Link 2001:db8:10:ffff::/64 inside allocation=True
The contrast between the two plans is instructive. In the IPv4 plan, every bit was
counted, spare addresses were noted one by one, and growth headroom was a constraint. In
the IPv6 plan, host count never entered the calculation; the only decision was which
subnet number to assign to which section. The subnet numbers were chosen for
readability — giving the point-to-point link ffff is a stylistic choice, starting from
the end of the allocation.
Interoperability of the Two Versions
IPv4 and IPv6 do not talk to each other directly: different address widths, different headers, different neighbor discovery. There are three approaches to interoperability.
Dual stack: Endpoints and routers run both protocols. The result of name resolution decides which version to use for a given destination. This is the simplest method; its cost is that everything is configured and monitored twice.
Tunneling: An IPv6 packet is carried as the payload of an IPv4 packet. It is used when the network in between does not know IPv6. The tunnel header lowers the MTU; this is why the fragmentation and path MTU problems covered in the previous topic are common in tunnels.
Translation: An intermediate node converts IPv6 addresses to IPv4 addresses. It breaks end-to-end transparency and inherits the address-translation problems covered in the next lesson.
If an endpoint has both versions, which one it picks depends on a preference rule; the general tendency is to try IPv6 first and fall back to IPv4 on failure. This behavior can increase latency when there is a silent failure along the IPv6 path.
Summary
- An IPv6 address is 128 bits; the point of the width is less about raising the address count and more about easing hierarchical distribution and prefix summarization.
- Subnets are always
/64; the 64-bit interface identifier is the precondition for autoconfiguration. - In notation, leading zeros are dropped, and the longest run of zeros is compressed
once with
::. - There is no broadcast address; its function is met by multicast and the solicited-node group.
- Stateless autoconfiguration consists of generating a link-local address, duplicate address detection, router advertisement, and prefix combination.
- The header checksum and router fragmentation have been removed; the two versions interoperate through dual stack, tunneling, or translation.
Next Step
This lesson used the phrase “the router drops the packet and reports it” several times. The reporting itself is also a protocol, and it does more than announce errors: reachability testing, path discovery, and MTU notification all run over it too. The next lesson covers these control messages, how the time-to-live field turns into traceroute, and why blocking these messages leads to silent failures.
To keep your progress and take notes, Log in
My notes
Log in to take notes.