Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Intermediate
  • 30 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free: 30 minutes

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

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:

  1. Drop the leading zeros of every group: 0db8 becomes db8, and 0000 becomes 0.
  2. Replace the longest run of two or more zero groups with ::, once. When two runs are equally long, replace the first.
  3. Leave a single zero group as 0; :: never stands for just one group.
  4. 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:

Short and full forms of eight addresses Python · canonical_forms.py
# 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

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:

Valid spellings against the canonical one Python · canonical_check.py
# 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

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::1 could mean 2001:0:1:0:0:0:0:1 or 2001:0:0:0:0:1:0:1, so it is not an address.
  • Dropping trailing zeros: only leading zeros may go. db8 is 0db8, never db80.
  • Writing :: for a single zero group, or upper-case digits, in output that people or programs will compare.
IP Address Converter Paste any IPv6 address to see its compressed and expanded forms and what the IANA registries say about it.

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 address 2001:db8:abcd:12::1 split into a 48-bit routing prefix, a 16-bit subnet ID and a 64-bit interface ID.2001:db8:abcd:12::1 (128 bits)The first 64 bits,2001:db8:abcd:12::/64,name the LAN.A /48 holds 65,536 LANs,a /56 holds 256.2001:db8:abcdrouting prefix48 bitsfrom the ISP0012subnet ID16 bitschosen bythe site0:0:0:1interface ID64 bitschosen bythe host

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.

  1. 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.
  2. The subnet ID, 0012, the next 16 bits. The site chooses it, one value for each LAN.
  3. 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 as 2001:7fd::1, the root DNS server k.root-servers.net.
  • Unique local (ULA), fc00::/7, in practice fd00::/8: used inside a site and never routed on the Internet, such as fd3c:8a91:7f20:4::25.
  • Link-local, fe80::/10, used as fe80::/64: valid on one link only, and routers never forward it, such as fe80::1%en0.
  • Multicast, ff00::/8: a group of interfaces within a scope, such as ff02::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::/32 and 3fff::/20: for examples and manuals only, never on a real network, such as 2001: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:

Address types by prefix, and Python's flags Python · address_types.py
# 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

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.

What Is My IP Address See whether your connection has a public IPv6 address right now, and which one websites see.

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.

LANs from a /48 and a /56, and a plan in hex digits Python · plan_subnets.py
# 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

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 Subnet Calculator Split 2001:db8:abcd::/48 into /64s, show subnet number 19 (subnet ID 0x12) and see the ip6.arpa zone to delegate.

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).

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.

  1. Question 1 of 5 What is the canonical (RFC 5952) form of 2001:0db8:0000:0000:0001:0000:0000:0001?

    Choose one answer.

    Show the answer to question 1

    Answer: 2001:db8::1:0:0:1

    The address has two runs of two zero groups. When runs are equally long, RFC 5952 compresses the first one, so :: goes after db8. 2001:db8:0:0:1::1 is the same address and valid input, but not the canonical text. :: may appear only once, and leading zeros are dropped.

  2. Question 2 of 5 A home router receives a /56 from its ISP. How many /64 LAN subnets can it number?

    Type a number.

    Show the answer to question 2

    Answer: 256 subnets

    The subnet ID is the bits between the delegation and the /64: 64 − 56 = 8 bits, and 2⁸ = 256. A /48 leaves 16 bits, so 65,536 subnets, and a /60 leaves 4 bits, so 16.

  3. Question 3 of 5 A printer on an office LAN has the address fd3c:8a91:7f20:4::25. What kind of address is it?

    Choose one answer.

    Show the answer to question 3

    Answer: A unique local address, valid inside the site and not routed on the Internet

    Addresses that start with fd fall in fc00::/7, the unique local range of RFC 4193, with the L bit set to 1. The next 40 bits (3c, 8a91 and 7f20) are the site's random Global ID, and the group after them, 4, is the subnet ID. Link-local addresses start with fe80, and the documentation prefixes are 2001:db8::/32 and 3fff::/20.

  4. Question 4 of 5 What does this print?

    Read the code, then choose one answer.

    import ipaddress
    a = ipaddress.IPv6Address("ff02::1")
    b = ipaddress.IPv6Address("2001:db8::1")
    print(a.is_global, b.is_private)
    Show the answer to question 4

    Answer: True True

    is_global and is_private follow the IANA special-purpose registry. Link-scope multicast is not in it, so ff02::1 counts as global even though it never leaves the link; the documentation prefix is listed as not globally reachable, so 2001:db8::1 counts as private. Classify by prefix when the type matters.

  5. Question 5 of 5 Why does ping fe80::1 often need a zone index such as %en0 or %eth0?

    Choose one answer.

    Show the answer to question 5

    Answer: Every interface has its own fe80::/64, so the same link-local address can exist on several links of one host

    Link-local addresses are only unique on their own link, and every IPv6 interface has one. With two interfaces up, the host cannot tell which link fe80::1 is on, so RFC 4007 adds % and the zone (usually the interface name or number). Global addresses never need one.

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 lone 0 and 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::/3 global unicast, fd00::/8 unique local, fe80::/10 link-local, ff00::/8 multicast; 2001:db8::/32 and 3fff::/20 are 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

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.