Computer Networks Module 9 – IPv6
IPv6 addresses and prefixes
How IPv6 addresses are written and shortened, how to tell global, unique local, link-local and multicast addresses apart, and how to plan /64 subnets.
What you will learn
- Write IPv6 addresses in the RFC 5952 canonical form and expand compressed ones
- Identify global unicast, unique local, link-local, multicast and documentation addresses
- Plan /64 LAN subnets from a /48 or /56 delegation
Before you start
What you’ll learn · Before you start
What you will learn
- Write IPv6 addresses in the RFC 5952 canonical form and expand compressed ones
- Identify global unicast, unique local, link-local, multicast and documentation addresses
- Plan /64 LAN subnets from a /48 or /56 delegation
Before you start
On this page
An IPv6 address is a 128-bit number that identifies a network interface. It is written as eight groups of four
hexadecimal digits separated by colons, such as 2001:0db8:0000:0000:0000:0000:0000:0001, and because that is
long, the standard short form drops leading zeros and replaces one run of zero groups with ::, giving
2001:db8::1. The first bits of every address form a prefix that names a network, written after a slash:
2001:db8:abcd:12::/64 is one LAN, and on a LAN the prefix is 64 bits long, leaving the other 64 bits to the host.
Those first bits also tell you what kind of address you are looking at: global unicast for the Internet, unique
local for private networks, link-local for a single link, or multicast for a group. This lesson shows how to write
addresses the canonical way, how to recognise each type, and how to split a site’s /48 or /56 into LANs, with
Python checking each step.
128 bits in eight groups
An IPv4 address has 32 bits, about 4.3 billion values in all, which is why IPv4 networks so often share one public address through NAT. An IPv6 address has 128 bits, about 3.4 × 10³⁸ values: enough for 2⁶⁴ networks (about 1.8 × 10¹⁹) of 2⁶⁴ addresses each.
RFC 4291 writes the 128 bits as eight 16-bit groups, each as one to four hexadecimal digits, separated by colons.
One hex digit is 4 bits (a nibble), so a group runs from 0 to ffff, and a full address has 32 digits. The group
0db8, for example, is the 16 bits 0000 1101 1011 1000.
The canonical short form
Any address can be typed in many ways, and RFC 5952 picks one of them as the form software should print:
- Drop the leading zeros of every group:
0db8becomesdb8, and0000becomes0. - Replace the longest run of two or more zero groups with
::, once. When two runs are equally long, replace the first. - Leave a single zero group as
0;::never stands for just one group. - Write the hex digits a to f in lower case.
Python’s ipaddress module follows these rules, so it can show both forms of each address:
# The short form (RFC 5952) and the full form of eight IPv6 addresses, from Python's ipaddress module.
import ipaddress
ADDRESSES = [
"2001:0db8:0000:0000:0000:0000:0000:0001", # leading zeros and one long run of zero groups
"2001:DB8:0:0:8:800:200C:417A", # upper-case hex digits
"2001:0:0:1:0:0:0:1", # two runs of zeros: the longer one goes
"2001:db8:0:0:1:0:0:1", # two runs of the same length: the first one goes
"2001:db8:0:1:1:1:1:1", # a single zero group stays
"fe80:0000:0000:0000:0204:61ff:fe9d:f156", # a link-local address
"0:0:0:0:0:0:0:1", # the loopback address
"0:0:0:0:0:ffff:c000:0201", # an IPv4-mapped address
]
for text in ADDRESSES:
address = ipaddress.IPv6Address(text)
print(text)
print(" short:", address.compressed)
print(" full: ", address.exploded) Output
2001:0db8:0000:0000:0000:0000:0000:0001 short: 2001:db8::1 full: 2001:0db8:0000:0000:0000:0000:0000:0001 2001:DB8:0:0:8:800:200C:417A short: 2001:db8::8:800:200c:417a full: 2001:0db8:0000:0000:0008:0800:200c:417a 2001:0:0:1:0:0:0:1 short: 2001:0:0:1::1 full: 2001:0000:0000:0001:0000:0000:0000:0001 2001:db8:0:0:1:0:0:1 short: 2001:db8::1:0:0:1 full: 2001:0db8:0000:0000:0001:0000:0000:0001 2001:db8:0:1:1:1:1:1 short: 2001:db8:0:1:1:1:1:1 full: 2001:0db8:0000:0001:0001:0001:0001:0001 fe80:0000:0000:0000:0204:61ff:fe9d:f156 short: fe80::204:61ff:fe9d:f156 full: fe80:0000:0000:0000:0204:61ff:fe9d:f156 0:0:0:0:0:0:0:1 short: ::1 full: 0000:0000:0000:0000:0000:0000:0000:0001 0:0:0:0:0:ffff:c000:0201 short: ::ffff:192.0.2.1 full: 0000:0000:0000:0000:0000:ffff:192.0.2.1
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 canonical_forms.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The third and fourth addresses show the placement rule. In 2001:0:0:1:0:0:0:1 the run of three zero groups wins
over the run of two, while in 2001:db8:0:0:1:0:0:1 both runs have two groups, so the first one becomes ::. The
fifth keeps its lone 0. The last is an IPv4-mapped address, which RFC 5952 recommends writing with the IPv4 part
in dotted decimal, and Python does so in both forms.
Accept every spelling, print one
The canonical form is a rule for output. RFC 5952 still requires software to accept every legitimate way of typing an address, so the same address arrives in logs, configuration files and user input in many spellings:
# Every text below is a valid way to type an address, and software must accept all of them. Only one spelling of
# each address is canonical: the one RFC 5952 tells software to print. This compares each text with it.
import ipaddress
CANDIDATES = [
"2001:db8::1",
"2001:DB8::1",
"2001:0db8::1",
"2001:db8:0::1",
"2001:db8::0:1",
"2001:db8::1:1:1:1:1",
"2001:db8:0:0:1::1",
"fe80::1%en0",
]
for text in CANDIDATES:
canonical = str(ipaddress.IPv6Address(text))
verdict = "canonical" if text == canonical else "print it as " + canonical
print(f"{text:22} {verdict}") Output
2001:db8::1 canonical 2001:DB8::1 print it as 2001:db8::1 2001:0db8::1 print it as 2001:db8::1 2001:db8:0::1 print it as 2001:db8::1 2001:db8::0:1 print it as 2001:db8::1 2001:db8::1:1:1:1:1 print it as 2001:db8:0:1:1:1:1:1 2001:db8:0:0:1::1 print it as 2001:db8::1:0:0:1 fe80::1%en0 canonical
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 canonical_check.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The first five lines are one address, 2001:db8::1, typed five ways, and only one of them is canonical. That is
why comparing addresses as strings is a bug: parse them and compare the values, or store them in canonical form.
The zone after % survives, because it is part of the address as the host uses it (zones are covered below). When
an address needs a port, put it in square brackets, [2001:db8::1]:443, as in a URL such as
https://[2001:db8::1]:8443/. RFC 5952 advises against 2001:db8::1:443, which is itself a valid address, so the
port cannot be told apart from the last group.
Common mistakes
- Using
::twice:2001::1::1could mean2001:0:1:0:0:0:0:1or2001:0:0:0:0:1:0:1, so it is not an address. - Dropping trailing zeros: only leading zeros may go.
db8is0db8, neverdb80. - Writing
::for a single zero group, or upper-case digits, in output that people or programs will compare.
Prefixes: which bits name the network
A prefix is an address followed by a slash and a length, the number of leading bits that name the network. In
2001:db8:abcd:12::/64, the first 64 bits name a network and every address that starts with them belongs to it;
2001:db8:abcd::/48 is a larger block that contains it and 65,535 other /64 networks.
For a global unicast address, RFC 4291 splits those bits into three parts: a global routing prefix that the site
receives, a subnet ID that the site chooses for each LAN, and an interface ID that identifies the host on its LAN.
It also fixes the interface ID at 64 bits for every unicast address except those that start with binary 000,
so a LAN prefix is a /64.
The three parts of a global unicast address on a LAN
Text description of the diagram
The diagram splits the 128-bit address 2001:db8:abcd:12::1 into three parts, side by side.
- The routing prefix, 2001:db8:abcd, the first 48 bits. The site receives it from its ISP, and routers on the Internet use it to send traffic to the site.
- The subnet ID, 0012, the next 16 bits. The site chooses it, one value for each LAN.
- The interface ID, 0:0:0:1, the last 64 bits. Each host on the LAN chooses its own.
Below, a note says that the first 64 bits, 2001:db8:abcd:12::/64, name the LAN, and that a /48 holds 65,536 such LANs while a /56 holds 256.
The /64 is not about the number of hosts. A host using stateless autoconfiguration (SLAAC, the next lesson) picks its own 64-bit interface ID, and RFC 4862 makes it ignore an advertised prefix whose length plus the interface ID’s length is not 128. On a LAN numbered as a /80 or a /120, such hosts never configure an address from that prefix. The huge host part is what lets every device choose an identifier, even a random one, without asking anyone.
Address types: read the first bits
- Global unicast,
2000::/3: reachable across the whole Internet, such as2001:7fd::1, the root DNS server k.root-servers.net. - Unique local (ULA),
fc00::/7, in practicefd00::/8: used inside a site and never routed on the Internet, such asfd3c:8a91:7f20:4::25. - Link-local,
fe80::/10, used asfe80::/64: valid on one link only, and routers never forward it, such asfe80::1%en0. - Multicast,
ff00::/8: a group of interfaces within a scope, such asff02::1, every node on the link. - Loopback,
::1/128: the host itself. - Unspecified,
::/128: used only as a source address, before a host has an address of its own. - Documentation,
2001:db8::/32and3fff::/20: for examples and manuals only, never on a real network, such as2001:db8:abcd:12::1. - IPv4-mapped,
::ffff:0:0/96: an IPv4 address as IPv6 software sees it, never forwarded, such as::ffff:192.0.2.1.
A few types need a sentence more. IPv6 has no broadcast address at all: RFC 4291 replaces broadcast with multicast,
and the fourth hex digit of a multicast address is its scope, 2 for one link, 5 for a site and e for the
whole Internet, so ff02::1 reaches every node on the link and ff02::2 every router on it. A unique local prefix
is fd followed by a 40-bit Global ID that RFC 4193 tells each site to choose at random, so two companies that
merge their networks are very unlikely to collide; fc00::/8 is held back for a future way of assigning them. The
documentation blocks exist so that examples never point at someone’s real network: RFC 3849 reserved
2001:db8::/32, and RFC 9637 added the much larger 3fff::/20 because a /32 is too small to describe large
networks realistically.
The program below classifies addresses by prefix and prints what Python’s is_global and is_private flags say
about the same addresses:
# What kind of address is it? A lookup by prefix (RFC 4291 and the IANA special-purpose registry), next to what
# Python's is_global and is_private flags say about the same addresses.
import ipaddress
# The most specific prefixes come first, because the first match wins.
TYPES = [
("::/128", "unspecified"),
("::1/128", "loopback"),
("::ffff:0:0/96", "IPv4-mapped"),
("64:ff9b::/96", "IPv4/IPv6 translation"),
("2001:db8::/32", "documentation"),
("3fff::/20", "documentation"),
("2000::/3", "global unicast"),
("fc00::/7", "unique local"),
("fe80::/10", "link-local"),
("ff00::/8", "multicast"),
]
NETWORKS = [(ipaddress.IPv6Network(prefix), name) for prefix, name in TYPES]
def kind(address):
for network, name in NETWORKS:
if address in network:
return f"{name} ({network})"
return "other"
SAMPLES = [
"2001:7fd::1", # k.root-servers.net, a real public server
"2001:db8:abcd:12::1",
"3fff:0:5::9",
"fd3c:8a91:7f20:4::25",
"fe80::1%en0",
"ff02::1",
"ff02::1:ff00:25",
"::1",
"::ffff:192.0.2.1", # an IPv4 documentation address, seen through IPv6 software
"::ffff:193.0.14.129", # the IPv4 address of k.root-servers.net, seen the same way
]
print(f"{'address':22} {'type':36} global private")
for text in SAMPLES:
address = ipaddress.IPv6Address(text)
print(f"{text:22} {kind(address):36} {address.is_global!s:7} {address.is_private!s}") Output
address type global private 2001:7fd::1 global unicast (2000::/3) True False 2001:db8:abcd:12::1 documentation (2001:db8::/32) False True 3fff:0:5::9 documentation (3fff::/20) False True fd3c:8a91:7f20:4::25 unique local (fc00::/7) False True fe80::1%en0 link-local (fe80::/10) False True ff02::1 multicast (ff00::/8) True False ff02::1:ff00:25 multicast (ff00::/8) True False ::1 loopback (::1/128) False True ::ffff:192.0.2.1 IPv4-mapped (::ffff:0.0.0.0/96) False True ::ffff:193.0.14.129 IPv4-mapped (::ffff:0.0.0.0/96) True False
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 address_types.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The two flags answer a different question. They follow the “globally reachable” column of the IANA special-purpose
registry, so documentation, unique local, link-local and loopback addresses all come out as private. An IPv4-mapped
address is judged by the IPv4 address inside it: ::ffff:192.0.2.1 holds an IPv4 documentation address and counts
as private, while ::ffff:193.0.14.129, the IPv4 address of k.root-servers.net, counts as global. The registry
does not list multicast at all, so ff02::1 and the solicited-node group ff02::1:ff00:25 come out as global,
although link-scope multicast never leaves the link. When the type matters, test the prefix.
Link-local addresses and zone indexes
Every IPv6 interface has a link-local address, with or without a router, because Neighbor Discovery and address
configuration run on it. A laptop with Wi-Fi and Ethernet therefore sits on two fe80::/64 networks at once, and
fe80::1 may exist on both. The address alone cannot say which link it means, so RFC 4007 adds a zone index after
a % sign: fe80::1%en0 names the neighbour on the interface called en0, and a zone can also be a number such
as %3.
A zone is meaningful only on the host that wrote it. It never travels in packets and never belongs in DNS; another
host has its own interface names. RFC 9844 requires user interfaces that accept non-global addresses to let you
enter a zone, preferably in the % form. It also withdraws RFC 6874, the earlier rule for zones inside URLs,
because browser makers found it impractical, so do not expect a browser to open a link-local address. Python keeps
the zone in scope_id, and two addresses that differ only in their zone are not equal.
Planning subnets from a /48 or /56
An ISP delegates a block to each customer site, and the site numbers its LANs inside it. RFC 6177 says an end site should get at least one /64 and in most cases significantly more. RIPE-690, a best-practice document of the RIPE community, recommends a /48 for every customer, or a /48 for businesses and a /56 for homes, and strongly discourages anything longer than a /56. The number of LANs is 2 to the power of the bits between the delegation and the /64:
| Delegation | Subnet ID bits | /64 LANs |
|---|---|---|
| /48 | 16 | 65,536 |
| /52 | 12 | 4,096 |
| /56 | 8 | 256 |
| /60 | 4 | 16 |
| /64 | 0 | 1 |
Plan the subnet ID in whole hex digits. Each digit is 4 bits, so a level of the plan that owns whole digits stays
readable in every address, adds up to a single shorter prefix that a router or firewall rule can match, and
matches one reverse DNS zone, because ip6.arpa names have one label per hex digit.
# Splitting a site's /48 and a home's /56 into /64 LAN subnets, and a readable plan built on hex digits.
import ipaddress
from itertools import islice
site = ipaddress.IPv6Network("2001:db8:abcd::/48")
home = ipaddress.IPv6Network("2001:db8:abcd:ef00::/56")
for network in (site, home):
count = 2 ** (64 - network.prefixlen)
print(f"{network} holds {count:,} subnets of /64")
# The subnet ID is the 16 bits between the /48 and the /64: the fourth group of the address.
lans = site.subnets(new_prefix=64)
print("first three:", ", ".join(str(n) for n in islice(lans, 3)))
lan_18 = next(islice(site.subnets(new_prefix=64), 18, None))
print("subnet ID 18 (0x12):", lan_18)
# A plan on hex digits: building (1 digit), floor (1 digit), VLAN (2 digits).
def lan(building, floor, vlan):
subnet_id = f"{building:x}{floor:x}{vlan:02x}"
return ipaddress.IPv6Network(f"2001:db8:abcd:{subnet_id}::/64")
print("building 2, floor 3, VLAN 10:", lan(2, 3, 10))
print("everything in building 2: ", lan(2, 0, 0).supernet(new_prefix=52))
# Reverse DNS delegates at hex-digit (nibble) boundaries: a /48 is 12 digits.
digits = site.network_address.exploded.replace(":", "")[:12]
print("reverse zone of the /48:", ".".join(reversed(digits)) + ".ip6.arpa") Output
2001:db8:abcd::/48 holds 65,536 subnets of /64 2001:db8:abcd:ef00::/56 holds 256 subnets of /64 first three: 2001:db8:abcd::/64, 2001:db8:abcd:1::/64, 2001:db8:abcd:2::/64 subnet ID 18 (0x12): 2001:db8:abcd:12::/64 building 2, floor 3, VLAN 10: 2001:db8:abcd:230a::/64 everything in building 2: 2001:db8:abcd:2000::/52 reverse zone of the /48: d.c.b.a.8.b.d.0.1.0.0.2.ip6.arpa
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 plan_subnets.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Subnet ID 18 is 0x12, so it becomes the fourth group, 2001:db8:abcd:12::/64. The plan gives the building, the
floor and the VLAN one, one and two hex digits, so 2001:db8:abcd:230a::/64 reads as building 2, floor 3, VLAN 10,
and the whole of building 2 is the single prefix 2001:db8:abcd:2000::/52. The reverse zone of the /48 is its 12
hex digits in reverse order. Subnet IDs count from 0; the subnet calculator numbers subnets from 1, so ID 0x12 is
its subnet 19.
IPv6 use in India
APNIC Labs measures how many users in each country can connect over IPv6. Its figures put India at about three in four users, against about three in five in the United States, so an Indian visitor to a service that has IPv6 addresses is more likely than not to arrive over IPv6. Test a service for Indian users over IPv6 first.
Exercise: shorten, expand and number subnets
Exercise · Medium · Python
Shorten, expand and number IPv6 subnets
Write three functions in ipv6tools.py, working on the eight 16-bit groups yourself rather than calling the ipaddress module (use it afterwards, to check your answers).
expand_ipv6(text) returns the full form: eight groups of four lower-case hex digits. The input may use :: once, leave out leading zeros and use upper case. Raise ValueError for text that is not an IPv6 address: :: twice, more than eight groups, or a group with more than four hex digits.
compress_ipv6(text) returns the canonical form of RFC 5952: lower case, no leading zeros, and :: in place of the longest run of two or more zero groups (the first run when two are equally long). A single zero group stays 0.
nth_subnet(delegation, new_prefix, index) takes a prefix such as "2001:db8:abcd::/48" and returns subnet number index (counting from 0) of length new_prefix, in canonical form with its length. Raise ValueError when new_prefix is shorter than the delegation or longer than 128, or when index is negative or too large.
expand_ipv6("2001:db8::1")is"2001:0db8:0000:0000:0000:0000:0000:0001"compress_ipv6("2001:0:0:1:0:0:0:1")is"2001:0:0:1::1"compress_ipv6("2001:db8:0:0:1:0:0:1")is"2001:db8::1:0:0:1"nth_subnet("2001:db8:abcd::/48", 64, 18)is"2001:db8:abcd:12::/64"
The eight sample tests cover the rules one by one, inputs that are already compressed, the error cases, and a subnet number above four billion, which only arithmetic on the address bits finds in time.
Starter code · ipv6tools.py
def expand_ipv6(text):
"""Return the full form: eight groups of four lower-case hex digits. Raise ValueError for invalid text."""
# Replace this line with your code.
return text
def compress_ipv6(text):
"""Return the canonical RFC 5952 form of an IPv6 address."""
# Replace this line with your code.
return text.lower()
def nth_subnet(delegation, new_prefix, index):
"""Return subnet number index (from 0) of length new_prefix inside delegation, as "address/length"."""
# Replace this line with your code.
return delegation The sample tests · test_ipv6tools.py
from ipv6tools import compress_ipv6, expand_ipv6, nth_subnet
def raises_value_error(function, *args):
try:
function(*args)
except ValueError:
return True
return False
def test_expand():
"""writes all eight groups with four digits each"""
assert expand_ipv6("2001:db8::1") == "2001:0db8:0000:0000:0000:0000:0000:0001"
assert expand_ipv6("::") == "0000:0000:0000:0000:0000:0000:0000:0000"
assert expand_ipv6("FE80::204:61FF:FE9D:F156") == "fe80:0000:0000:0000:0204:61ff:fe9d:f156"
assert expand_ipv6("1::") == "0001:0000:0000:0000:0000:0000:0000:0000"
def test_rejects_invalid_text():
"""raises ValueError for text that is not an IPv6 address"""
assert raises_value_error(expand_ipv6, "2001:db8::1::1")
assert raises_value_error(expand_ipv6, "2001:db8:0:0:0:0:0:0:1")
assert raises_value_error(expand_ipv6, "12345::1")
assert raises_value_error(compress_ipv6, "2001:db8:0:0:0:0:1")
def test_leading_zeros_and_case():
"""drops leading zeros and writes hex digits in lower case"""
assert compress_ipv6("2001:0db8:0000:0000:0000:0000:0000:0001") == "2001:db8::1"
assert compress_ipv6("2001:DB8:0:0:8:800:200C:417A") == "2001:db8::8:800:200c:417a"
def test_longest_run_wins():
"""replaces the longest run of zero groups, the first one on a tie"""
assert compress_ipv6("2001:0:0:1:0:0:0:1") == "2001:0:0:1::1"
assert compress_ipv6("2001:db8:0:0:1:0:0:1") == "2001:db8::1:0:0:1"
assert compress_ipv6("2001:db8:0:0:1::1") == "2001:db8::1:0:0:1"
def test_single_zero_group_and_edges():
"""keeps a single zero group, and handles runs at either end"""
assert compress_ipv6("2001:db8:0:1:1:1:1:1") == "2001:db8:0:1:1:1:1:1"
assert compress_ipv6("2001:db8::1:1:1:1:1") == "2001:db8:0:1:1:1:1:1"
assert compress_ipv6("0:0:0:0:0:0:0:0") == "::"
assert compress_ipv6("0:0:0:0:0:0:0:1") == "::1"
assert compress_ipv6("1:0:0:0:0:0:0:0") == "1::"
assert compress_ipv6("fe80:0:0:0:0:0:0:1") == "fe80::1"
def test_lan_subnets():
"""numbers /64 subnets inside a /48 and a /56, from 0"""
assert nth_subnet("2001:db8:abcd::/48", 64, 0) == "2001:db8:abcd::/64"
assert nth_subnet("2001:db8:abcd::/48", 64, 18) == "2001:db8:abcd:12::/64"
assert nth_subnet("2001:db8:abcd::/48", 64, 65535) == "2001:db8:abcd:ffff::/64"
assert nth_subnet("2001:db8:abcd:ef00::/56", 64, 255) == "2001:db8:abcd:efff::/64"
def test_other_sizes():
"""works for any lengths, and for subnet numbers too large to count to"""
assert nth_subnet("2001:db8::/32", 48, 65535) == "2001:db8:ffff::/48"
assert nth_subnet("2001:db8:abcd::/48", 52, 2) == "2001:db8:abcd:2000::/52"
assert nth_subnet("2001:db8::/32", 64, 2**32 - 1) == "2001:db8:ffff:ffff::/64"
assert nth_subnet("2001:db8:abcd:12::/64", 128, 1) == "2001:db8:abcd:12::1/128"
def test_subnet_errors():
"""raises ValueError for impossible requests"""
assert raises_value_error(nth_subnet, "2001:db8:abcd::/48", 64, 65536)
assert raises_value_error(nth_subnet, "2001:db8:abcd::/48", 64, -1)
assert raises_value_error(nth_subnet, "2001:db8:abcd::/48", 40, 0)
assert raises_value_error(nth_subnet, "2001:db8:abcd::/48", 129, 0) A hint
Do everything through one helper that turns text into a list of eight integers: split on :: (at most once), split each side on :, and fill the gap with zeros until there are eight groups. expand_ipv6() then formats each group with f"{g:04x}". For compress_ipv6(), walk the groups once and remember where the longest run of zeros starts and how long it is, replacing it only when it is at least two groups long and strictly longer than the best run so far. For nth_subnet(), turn the eight groups into one 128-bit integer and add index << (128 - new_prefix).
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Check yourself
Five questions on addresses and prefixes
5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Interview questions
What does :: mean in an IPv6 address, and why may it appear only once?
It stands for one or more groups of zeros, as many as the address needs to reach eight groups, so 2001:db8::1
is 2001:db8:0:0:0:0:0:1. A reader recovers the address by counting the groups that are written and filling the
gap. With two :: there would be two gaps and no way to split the missing groups between them: 2001::1::1 could
be 2001:0:1:0:0:0:0:1 or 2001:0:0:0:0:1:0:1. RFC 5952’s canonical form uses it once, for the longest run of two
or more zero groups, and the first such run on a tie.
Why does every IPv6 LAN get a /64, when no LAN has anywhere near 2⁶⁴ hosts?
Because the size is about how hosts choose addresses, not how many there are. The addressing architecture fixes interface IDs at 64 bits, and with SLAAC each host builds its own 64-bit interface ID, often a random one, without a server or any coordination; a host ignores an advertised prefix whose length plus 64 is not 128. A LAN numbered as a /80 or a /120 would break automatic configuration for those hosts, and there is no shortage to save: a /48 alone holds 65,536 LANs.
Key takeaways
- An IPv6 address is 128 bits, written as eight hex groups; the canonical form drops leading zeros, uses
::once for the longest run of two or more zero groups, keeps a lone0and is lower case. - Software must accept every valid spelling, so compare parsed addresses, not text, and print the canonical form.
- The first bits give the type:
2000::/3global unicast,fd00::/8unique local,fe80::/10link-local,ff00::/8multicast;2001:db8::/32and3fff::/20are for documentation; there is no broadcast. - Link-local addresses need a zone index such as
%en0, which only means something on the host that wrote it. - LANs are /64s. A /48 holds 65,536 of them and a /56 holds 256; plan the subnet ID in whole hex digits.
References
- RFC 4291: IP Version 6 Addressing Architecture (RFC Editor (IETF))
- RFC 5952: A Recommendation for IPv6 Address Text Representation (RFC Editor (IETF))
- RFC 4193: Unique Local IPv6 Unicast Addresses (RFC Editor (IETF))
- RFC 3849: IPv6 Address Prefix Reserved for Documentation (RFC Editor (IETF))
- RFC 9637: Expanding the IPv6 Documentation Space (RFC Editor (IETF))
- RFC 4007: IPv6 Scoped Address Architecture (RFC Editor (IETF))
- RFC 9844: Entering IPv6 Zone Identifiers in User Interfaces (RFC Editor (IETF))
- RFC 4862: IPv6 Stateless Address Autoconfiguration (RFC Editor (IETF))
- RFC 6177: IPv6 Address Assignment to End Sites (RFC Editor (IETF))
- RIPE-690: IPv6 prefix assignment for end-users (best current operational practice) (RIPE NCC)
- IANA IPv6 Special-Purpose Address Registry (IANA)
- Root servers and their addresses (IANA)
- ipaddress: IPv4/IPv6 manipulation library (Python Software Foundation)
- Use of IPv6 for India (IN) (APNIC Labs)
- Use of IPv6 for the United States of America (US) (APNIC Labs)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress