Skip to content
academia.sh

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 21282^{128}, roughly 3.4×10383.4 \times 10^{38}. For comparison: IPv4’s 2322^{32} addresses are one 2962^{96}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:

  1. Leading zeros in each group can be dropped: 0db8db8, 00000.
  2. Consecutive zero groups can be dropped once with ::.
  3. :: 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:

  1. The interface generates itself a link-local address (fe80:: prefix + interface identifier).
  2. 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.
  3. It sends a router solicitation to ff02::2. The router replies with a router advertisement containing the /64 prefix used on the link.
  4. 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.

Start typing to search.

↑↓ Esc navigate · open · close