Sign in
← Networking: basics
12 min · medium · Lesson 11 of 13

IPv6 and SLAAC

How IPv6 differs from IPv4 beyond address length, what link-local means, and how a host configures its own address.

What sets IPv6 apart from IPv4 besides the address length, what link-local is, and how a host configures its own address.

Why this matters

IPv6 is already enabled almost everywhere: in clouds, with mobile carriers, in Docker and Kubernetes with dual-stack. A service that answers over IPv4 and stays silent over IPv6 is simply down for part of its users. And when a server "has no address", you need to understand where it was supposed to come from in the first place. This lesson covers how an IPv6 address is built, how a host gets one without a DHCP server, and what to check when autoconfiguration didn't work.

Address length and the network model

An IPv4 address takes 32 bits, that's about 4.3 billion addresses, and they ran out. That's where private networks and NAT from the previous lesson come from. An IPv6 address takes 128 bits: enough to give every device a public one.

IPv6 is written as eight groups of hex digits, 16 bits each. Leading zeros in a group can be dropped, and one run of consecutive zero groups is replaced with :::

2001:0db8:0000:0000:0000:0000:0000:0001
2001:db8::1              # same thing, :: collapses the zeros once

:: never appears twice in one address: otherwise there's no way to tell how many zeros go in each spot. The prefix 2001:db8::/32 is reserved for documentation, which is why it's in every example in the course.

The change in model matters more than the length. In IPv6 a host usually has a global address, and NAT isn't needed. Reachability is now decided by the firewall, not a translation table. Anyone used to "we're invisible behind NAT" now has to configure filtering explicitly.

Every interface with IPv6 enabled has a link-local address from the fe80::/10 prefix. The host assembles it on its own from the interface identifier, with no router and no DHCP. It works only within its own network segment; routers never forward it further.

An IPv6 address splits into two 64-bit halves: the network prefix and the interface identifier. Here's the output from a container with MAC address 02:42:0a:d4:1f:02:

ip -6 addr show eth0
    inet6 2001:db8:12:1:42:aff:fed4:1f02/64 scope global dynamic mngtmpaddr
       valid_lft 86399sec preferred_lft 14399sec
    inet6 fe80::42:aff:fed4:1f02/64 scope link
       valid_lft forever preferred_lft forever

Here the identifier was assembled the old way, via EUI-64. The MAC is stretched to 64 bits by inserting ff:fe in the middle, and the second bit of the first byte, the U/L (local/global) bit, is flipped: 02 becomes 00. The result is 0042:0aff:fed4:1f02, written without leading zeros as 42:aff:fed4:1f02. The MAC can be read straight out of such an address, which is why desktops moved away from this scheme. Where a network is managed by NetworkManager, stable-privacy (RFC 7217) usually runs instead: the identifier is a hash of the prefix, the interface name, and a machine secret, with no ff:fe marker in it. The Linux kernel without NetworkManager uses EUI-64 by default.

Link-local addresses carry all of IPv6's internal machinery. Neighbor and router discovery (NDP, replacing ARP) travels over them. ip -6 neigh shows the neighbor table, the equivalent of the ARP table:

fe80::42:aff:fed4:1f03 dev eth0 lladdr 02:42:0a:d4:1f:03 router REACHABLE

A link-local address looks the same on any interface, so when you address one you specify the interface with %: ping fe80::42:aff:fed4:1f03%eth0.

SLAAC: an address without a DHCP server

SLAAC (stateless address autoconfiguration) lets a host configure a global address on its own. The router periodically sends out a Router Advertisement (RA) with the network prefix. The host takes the prefix, adds its own interface identifier, and gets a ready address. There's no server keeping track of what was handed out, hence stateless.

The host also gets its default gateway from the RA. It becomes the router itself, addressed by its own link-local address.

For a prefix to turn into an address, three conditions have to line up:

SLAAC and DHCPv6 don't rule each other out. Flags in the RA tell the host what to do. The M (managed) flag means "get your address from DHCPv6". The O (other) flag means "assemble the address yourself, but get DNS servers and other settings from DHCPv6". The A flag on the prefix allows SLAAC. Both mechanisms work together fine on the same network.

You can see the RA's content right on the host. The radvdump utility from the radvd package listens for RA and prints them out as configuration:

	prefix 2001:db8:12:1::/64
	{
		AdvValidLifetime 86400;
		AdvPreferredLifetime 14400;
		AdvOnLink on;
		AdvAutonomous on;
		AdvRouterAddr off;
	}; # End of prefix definition

AdvAutonomous here is the A flag.

What ip output looks like

The result of SLAAC shows up in plain ip, no extra utility needed. The default route with its source marked:

ip -6 route show
2001:db8:12:1::/64 dev eth0 proto kernel metric 256 expires 86398sec pref medium
fe80::/64 dev eth0 proto kernel metric 256 pref medium
default via fe80::42:aff:fed4:1f03 dev eth0 proto ra metric 1024 expires 13sec hoplimit 64 pref medium

proto ra means the route came from a Router Advertisement, not from manual configuration. The gateway is given as the router's link-local address. The default route's expires comes from the router's lifetime in the RA: in this lab environment that's 15 seconds, refreshed with every new RA.

What you see depends on who processes the RA. Here it's the kernel, and the route to the prefix is marked proto kernel. On a machine running NetworkManager it sets the routes itself, and the picture is different: proto ra on both routes, metric 100 instead of 1024.

We've already seen an address with the same history above: scope global dynamic and finite valid_lft/preferred_lft. dynamic and a finite lifetime mean autoconfiguration. An address set by hand, or one Docker assigned when creating the container, looks different: no dynamic, and valid_lft forever.

Dual-stack

The move to IPv6 happens through both stacks running at once. A host has both an IPv4 and an IPv6 address, and the application picks whichever is available. A service, though, listens on separate sockets for each stack, and turning on IPv6 in the network doesn't automatically mean the service starts answering on it.

That gives a practical diagnostic rule: check both stacks. That's what ping and ping -6, curl -4 and curl -6 are for. A service can be alive on one protocol and down on the other. In a URL, an IPv6 address goes in square brackets:

curl -6 -g 'http://[2001:db8:12:2::20]/'

The -g flag turns off curl's handling of [] as a glob pattern; without it, it may interpret the brackets in the address on its own. The single quotes are there for the shell: on macOS, zsh without them treats the brackets as a filename glob and prints no matches found.

Common mistakes

What you'll need for the tasks

Next lesson: DHCP, how a host gets an address on an IPv4 network.

Done reading?

Mark the lesson as read to move your track progress forward.

Go through it with an AI agent

The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.

Get access for $49

Tell the agent: use MCP izi, give me lesson networking-basics-12-ipv6-slaac