IPv6 Basics
Why IPv6 exists, how its addresses are written and shortened, the main address types, and how hosts configure themselves.
Why IPv6
Internet Protocol version 6 (IPv6) is the successor of Internet Protocol version 4 (IPv4), standardized in Request for Comments (RFC) 8200. Its main reason for existing is size. IPv4 has 32-bit addresses: about 4.3 billion in total, far fewer than the number of phones, laptops, servers and sensors that want one. The Internet Assigned Numbers Authority (IANA) handed out its last free IPv4 blocks to the regional registries in 2011, and most of the registries have since run out as well.
Network Address Translation (NAT) kept IPv4 going: a whole office or home shares one public address, and the router rewrites addresses and ports on the way out (see NAT and PAT). NAT works, but it is a stopgap. It breaks end-to-end reachability, complicates protocols that carry addresses inside their data, and needs state on every translating device. IPv6 fixes the shortage itself: 128-bit addresses give 2128 (about 3.4 × 1038) addresses, enough to give every device a globally unique one.
Along the way IPv6 also simplified some things: a fixed 40-byte header, no fragmentation by routers (only the sender fragments), no broadcast, and built-in automatic address configuration.
Writing IPv6 addresses
The 128 bits are written as eight groups of 16 bits each. Each group is four hexadecimal digits (see hexadecimal), and the groups are separated by colons. A group is often called a hextet. The full form of an address looks like this:
2001:0db8:0000:0000:0000:ff00:0042:8329
The two shortening rules
Nobody types that. Two rules make addresses shorter:
-
Drop leading zeros in each group.
0db8becomesdb8,0042becomes42,0000becomes0. Trailing zeros stay:ff00remainsff00. -
Replace one run of consecutive all-zero groups with
::. This can be done only once per address, because with two::nobody could tell how many groups each one hides.
Applied to the example,
2001:0db8:0000:0000:0000:ff00:0042:8329 becomes
2001:db8::ff00:42:8329. To expand an address, count the
groups that are present (here five) and fill the
:: with as many zero groups as are missing (here
three).
The canonical form (RFC 5952)
Several short forms can describe the same address, which makes searching logs and comparing configurations harder. RFC 5952 defines one recommended text form:
- Leading zeros are always removed.
-
::replaces the longest run of zero groups; if two runs are equally long, the leftmost one. -
::is not used for a single zero group; that group is written as0. - Hexadecimal letters are written in lowercase.
Python's ipaddress module produces exactly this form
with .compressed, and the full form with
.exploded:
import ipaddress
for text in ["2001:0db8:0000:0000:0000:ff00:0042:8329",
"2001:db8:0:0:1:0:0:1",
"2001:db8:0:1:1:1:1:1",
"2001:DB8:0:0:8:800:200C:417A",
"0:0:0:0:0:0:0:1"]:
address = ipaddress.ip_address(text)
print(f"{address.compressed:24} {address.exploded}")
2001:db8::ff00:42:8329 2001:0db8:0000:0000:0000:ff00:0042:8329 2001:db8::1:0:0:1 2001:0db8:0000:0000:0001:0000:0000:0001 2001:db8:0:1:1:1:1:1 2001:0db8:0000:0001:0001:0001:0001:0001 2001:db8::8:800:200c:417a 2001:0db8:0000:0000:0008:0800:200c:417a ::1 0000:0000:0000:0000:0000:0000:0000:0001
Line 2 shows the tie rule: two runs of two zero groups, and the
leftmost one becomes ::. Line 3 has only a single zero
group, so no :: at all. Line 4 comes out in lowercase.
Prefix length and the /64 convention
IPv6 has no dotted subnet masks; it uses only the prefix length, as
in 2001:db8:1:10::/64. A typical global address is
divided into three parts:
A local area network (LAN) subnet is a /64 in practice. That is not a limit of the protocol but a convention that other standards rely on: Stateless Address Autoconfiguration (SLAAC; see below) needs a 64-bit interface ID. It looks wasteful — one subnet holds 264 addresses — but the address space is so large that this does not matter. With a /48, a site still has 65,536 /64 subnets to hand out.
import ipaddress
site = ipaddress.ip_network("2001:db8:1::/48")
lan = ipaddress.ip_network("2001:db8:1:10::/64")
print("addresses in one /64:", lan.num_addresses)
print("/64 subnets in a /48:", 2 ** (64 - site.prefixlen))
print("LAN inside the site? ", lan.subnet_of(site))
addresses in one /64: 18446744073709551616 /64 subnets in a /48: 65536 LAN inside the site? True
Point-to-point links between routers often use a /127 (RFC 6164), the IPv6 counterpart of the IPv4 /31 described in Subnetting.
Address types
An IPv6 interface normally has several addresses at once, each with a different scope. The first bits of an address tell you its type:
| Type | Prefix | Starts with | Reach |
|---|---|---|---|
| Global unicast | 2000::/3 |
2 or 3 | Routable on the internet |
| Unique local |
fc00::/7 (in practice fd00::/8)
|
fd | Inside an organization |
| Link-local | fe80::/10 |
fe80 | One link only, never routed |
| Multicast | ff00::/8 |
ff | A group of interfaces |
| Loopback | ::1/128 |
— | The host itself |
| Unspecified | ::/128 |
— | "No address yet" |
Global unicast
A global unicast address (GUA) is a public, globally unique IPv6 address from 2000::/3, the range that the regional registries currently assign. It is the IPv6 equivalent of a public IPv4 address: routable on the internet, without NAT. The prefix 2001:db8::/32, used in all examples here, lies inside this range but is reserved for documentation (RFC 3849) and never routed.
Unique local
A
unique local address
(ULA, RFC 4193) comes from fc00::/7 and is meant for use inside a
site or between cooperating sites, never on the internet — roughly
what RFC 1918 private addresses are in IPv4. Only the fd00::/8 half
is in use: the organization picks a random 40-bit Global ID after
fd, which makes a clash between two companies that
later merge very unlikely. fd12:3456:789a::/48 is such
a prefix.
Link-local
A link-local address from
fe80::/10 is valid only on one link (one layer 2 segment, such as
one virtual LAN, or VLAN); routers never forward packets that have
one as source or destination. Every IPv6-enabled interface creates
one automatically, even when no other address is configured, and
IPv6 relies on it: Neighbor Discovery and router advertisements use
it, and the default gateway that hosts learn is the router's
link-local address. Because the same fe80:: prefix exists on every
link, a link-local address is only unambiguous together with the
interface, written with a zone index such as
fe80::1%eth0.
Multicast, and no broadcast
IPv6 has no broadcast address. Everything IPv4 did with broadcasts
is done with multicast (ff00::/8), which reaches only interfaces
that joined a group. Well-known groups include
ff02::1 (all nodes on the link) and
ff02::2 (all routers on the link). Each address also
has a solicited-node multicast group (ff02::1:ff
plus the last 24 bits of the address); Neighbor Discovery uses it so
an address lookup disturbs almost no other hosts.
A small classifier that tests prefixes, as a router would:
import ipaddress
TYPES = [
("unspecified", "::/128"),
("loopback", "::1/128"),
("multicast", "ff00::/8"),
("link-local", "fe80::/10"),
("unique local", "fc00::/7"),
("global unicast", "2000::/3"),
]
def address_type(text):
address = ipaddress.ip_address(text)
for name, prefix in TYPES:
if address in ipaddress.ip_network(prefix):
return name
return "other"
for text in ["2001:db8:1:10::20", "fd12:3456:789a:1::20", "fe80::1",
"ff02::1", "::1", "::"]:
print(f"{text:22} {address_type(text)}")
2001:db8:1:10::20 global unicast fd12:3456:789a:1::20 unique local fe80::1 link-local ff02::1 multicast ::1 loopback :: unspecified
SLAAC and EUI-64
Stateless Address Autoconfiguration (SLAAC) (RFC 4862) lets a host build its own global address without a server that keeps track of it — hence "stateless":
- The host creates its link-local address and checks that nobody else uses it (Duplicate Address Detection, DAD).
- It sends a Router Solicitation (RS) to ff02::2, all routers.
- A router answers with a Router Advertisement (RA) that contains the /64 prefix of the link, and itself as the default gateway. Routers also send RAs unsolicited at intervals.
- The host appends a 64-bit interface ID to the prefix, runs DAD again, and starts using the address.
Building an interface ID with EUI-64
Modified EUI-64 (Extended Unique
Identifier, 64-bit) is one way to make the interface ID: it
stretches the 48-bit media access control (MAC) address to 64 bits.
Take the MAC address 00-00-5E-00-53-01 (from the
documentation range):
-
Split it in the middle:
00-00-5E|00-53-01. -
Insert
FF-FEbetween the halves:00-00-5E-FF-FE-00-53-01. -
Flip the 7th bit from the left of the first byte (the
universal/local bit):
00= 0000 0000 becomes 0000 0100 =02. -
Write it as hextets:
0200:5eff:fe00:5301, and put the prefix in front:2001:db8:1:10:200:5eff:fe00:5301. The link-local address uses the same ID:fe80::200:5eff:fe00:5301.
import ipaddress
def eui64_interface_id(mac):
octets = bytearray.fromhex(mac.replace("-", ""))
eui = octets[:3] + bytearray.fromhex("fffe") + octets[3:]
eui[0] ^= 0b00000010 # flip the universal/local bit
return int.from_bytes(eui, "big")
interface_id = eui64_interface_id("00-00-5E-00-53-01")
prefix = ipaddress.ip_network("2001:db8:1:10::/64")
print("interface ID:", f"{interface_id:016x}")
print("global: ", prefix.network_address + interface_id)
print("link-local: ", ipaddress.ip_address("fe80::") + interface_id)
interface ID: 02005efffe005301 global: 2001:db8:1:10:200:5eff:fe00:5301 link-local: fe80::200:5eff:fe00:5301
EUI-64 exposes the hardware address in every packet, which lets websites follow a laptop from network to network. Current Windows, macOS, Linux distributions, iOS and Android therefore generate random or per-network interface IDs for their global addresses (RFC 7217 and RFC 8981). Cisco routers still use EUI-64 for their link-local addresses by default, and you can ask for it explicitly on a global address, so it remains exam material.
DHCPv6, stateful and stateless
The router advertisement also tells hosts whether to use Dynamic Host Configuration Protocol version 6 (DHCPv6), through two flags:
- Stateless DHCPv6 (O flag, "other configuration"): the host forms its address with SLAAC and asks a DHCPv6 server only for extra settings such as Domain Name System (DNS) servers.
- Stateful DHCPv6 (M flag, "managed"): a DHCPv6 server assigns the address and keeps track of it, as DHCP does in IPv4 (see DHCP).
In both cases the default gateway still comes from the router advertisement; DHCPv6 has no option for it.
Neighbor Discovery
IPv4 uses the Address Resolution Protocol (ARP) to find the MAC address that belongs to an IP address on the local link. IPv6 has no ARP. Its job is done by the Neighbor Discovery Protocol (NDP, RFC 4861), which runs over ICMPv6 (the Internet Control Message Protocol for IPv6) and uses multicast instead of broadcast: a Neighbor Solicitation goes to the target's solicited-node group, and the target replies with a Neighbor Advertisement. The router solicitations and advertisements of SLAAC, and Duplicate Address Detection, are part of the same protocol. The comparison with ARP is on ARP and ICMP.
Dual stack and transition
IPv4 and IPv6 are not compatible on the wire: an IPv6-only host cannot talk directly to an IPv4-only host. The internet is therefore moving over gradually, and the most common approach is dual stack: hosts, routers and services run IPv4 and IPv6 side by side, each interface with addresses of both versions. When a name resolves to both an IPv4 and an IPv6 address, clients generally prefer IPv6 and fall back to IPv4 quickly if it does not answer ("Happy Eyeballs").
Two other families of techniques exist for where dual stack is not possible:
- Tunneling carries IPv6 packets inside IPv4 packets across a part of the network that only speaks IPv4 (for example 6in4 tunnels).
- Translation rewrites packets between the two versions, such as NAT64 together with DNS64, which lets IPv6-only clients (common on mobile networks) reach IPv4-only servers.
IPv6 on Cisco IOS
A Cisco router forwards IPv6 only after you enable it globally. A minimal configuration of one LAN interface:
R1(config)# ipv6 unicast-routing
R1(config)# interface GigabitEthernet0/0
R1(config-if)# ipv6 address 2001:db8:1:10::1/64
R1(config-if)# no shutdown
R1(config-if)# end
-
ipv6 unicast-routing— turns on IPv6 routing, so the router forwards IPv6 packets and sends router advertisements. Without it the router behaves like an IPv6 host. Exam-relevant. -
ipv6 address 2001:db8:1:10::1/64— a static global address with its prefix length. The link-local address is created automatically. The variantipv6 address 2001:db8:1:10::/64 eui-64builds the interface ID from the MAC address instead. Exam-relevant. no shutdown— enables the interface.
R1# show ipv6 interface brief
GigabitEthernet0/0 [up/up]
FE80::200:5EFF:FE00:5310
2001:DB8:1:10::1
GigabitEthernet0/1 [administratively down/down]
unassigned
Each interface shows its status (line and protocol) and then its addresses: the automatic EUI-64 link-local address and the configured global one. Gi0/1 has no IPv6 address and is shut down.
On a Linux host, the same information comes from
ip -6 addr:
ip -6 addr show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
inet6 2001:db8:1:10:200:5eff:fe00:5301/64 scope global dynamic
valid_lft 2591961sec preferred_lft 604761sec
inet6 fe80::200:5eff:fe00:5301/64 scope link
valid_lft forever preferred_lft forever
scope global dynamic marks an address learned through
SLAAC, with the lifetimes from the router advertisement;
scope link is the link-local address. This host uses
EUI-64. Many distributions instead generate a stable, per-network
interface ID (RFC 7217) and may add temporary random addresses (RFC
8981).