The Address Space That Built the Internet#

Every packet that crosses the internet carries a source and a destination IP address. Routers use the destination to decide where to send it; hosts use the source to know where to reply. The history of the internet is, in large part, the history of running out of these addresses, and today every network engineer works with two address families side by side.

IPv4#

An IPv4 address is a 32-bit number written as four decimal octets (192.168.0.1), each 0-255. That gives about 4.3 billion addresses, generous in 1981 and hopelessly insufficient today. An address is split into a network portion and a host portion by a prefix length or subnet mask; the arithmetic is covered in Subnetting and CIDR.

The original design used classful addressing (Class A = /8, Class B = /16, Class C = /24), which handed out enormous blocks to organizations that used a fraction of them. CIDR (Classless Inter-Domain Routing) replaced classes with arbitrary prefix lengths in the 1990s, and NAT stretched the remaining public space by letting whole private networks share one public address (see NAT and Port Forwarding). The regional registries have long since exhausted their free pools, so IPv4 addresses are now bought, sold and leased.

IPv6#

IPv6 uses 128-bit addresses, written as eight groups of four hex digits: 2001:0db8:0000:0000:0000:8a2e:0370:7334. Two shortening rules apply:

  1. Leading zeros in each group may be dropped (0db8 becomes db8).
  2. One run of consecutive all-zero groups may be replaced by ::, only once per address (otherwise it would be ambiguous how many zeros each :: hides). The recommended form (RFC 5952) collapses the longest run and uses lowercase.

So the address above is written 2001:db8::8a2e:370:7334. The number of addresses, about 3.4 x 10^38, means the design question is no longer "how few addresses can we get away with?" but "how cleanly can we aggregate routes?"

The standard subnet size is a /64: the first 64 bits identify the network, the last 64 bits (the interface identifier) identify the host. Interface IDs used to be derived from the MAC address (EUI-64, recognizable by ff:fe in the middle), which let anyone track a device across networks. Modern operating systems instead generate stable per-network random IDs and additionally use rotating temporary addresses (RFC 4941) for outgoing connections. That is why a laptop shows several IPv6 addresses at once: it is a privacy feature, not a misconfiguration.

How the Two Headers Differ#

IPv6 changes more than address size:

Feature IPv4 IPv6
Address size 32 bits 128 bits
Header size 20-60 bytes (options) fixed 40 bytes, plus optional extension headers
Header checksum yes, recomputed at every hop none (link and transport layers already check)
Fragmentation routers may fragment only the sender fragments; routers send "Packet Too Big"
Minimum link MTU 68 bytes (576 must be accepted) 1280 bytes
Hop counter TTL Hop Limit
Address resolution ARP (broadcast) Neighbor Discovery (ICMPv6 multicast)
Host configuration DHCP or static SLAAC, DHCPv6, or static
Broadcast yes no; multicast replaces it

Two operational consequences follow. First, because routers never fragment IPv6, blocking ICMPv6 breaks IPv6: Path MTU Discovery and Neighbor Discovery both depend on it. Firewalls must allow the necessary ICMPv6 types. Second, with SLAAC (stateless address autoconfiguration) a host builds its own address from the /64 prefix in a router advertisement, with no DHCP server involved. IPsec support was mandatory in the original IPv6 specifications but was later relaxed to a recommendation; in practice it is used about as often as with IPv4.

Special Address Ranges#

Recognizing reserved ranges is essential for reading firewall rules, logs and cloud configs:

Purpose IPv4 IPv6
Private / unique local 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 fc00::/7 (in practice fd00::/8)
Loopback 127.0.0.0/8 ::1
Link-local 169.254.0.0/16 fe80::/10
Multicast 224.0.0.0/4 ff00::/8
Carrier-grade NAT (shared) 100.64.0.0/10 not applicable
Documentation / examples 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24 2001:db8::/32
Unspecified 0.0.0.0 ::
IPv4-mapped IPv6 not applicable ::ffff:0:0/96
Global unicast everything else not reserved 2000::/3

Every IPv6 interface always has a fe80:: link-local address, even when it has no global one. Link-local addresses are valid only on one link, so when you use them you must name the interface: ping fe80::1%eth0.

Classifying Addresses in Python#

The standard library's ipaddress module knows these ranges, which is far safer than hand-written string checks like startswith("10."):

import ipaddress

for raw in ["192.168.1.10", "100.64.12.7", "8.8.8.8", "169.254.10.2",
            "fe80::1c2a:3bff:fe4d:5e6f", "fd12:3456:789a::10", "2001:db8::1", "::ffff:10.0.0.5"]:
    ip = ipaddress.ip_address(raw)
    flags = [name for name in ("is_private", "is_global", "is_link_local", "is_loopback", "is_multicast")
             if getattr(ip, name)]
    print(f"{raw:28} v{ip.version} {flags}")

v6 = ipaddress.IPv6Address("2001:0db8:0000:0000:0000:8a2e:0370:7334")
print(v6.compressed)
print(v6.exploded)
print(ipaddress.ip_address("::ffff:10.0.0.5").ipv4_mapped)

Output on Python 3.11:

192.168.1.10                 v4 ['is_private']
100.64.12.7                  v4 []
8.8.8.8                      v4 ['is_global']
169.254.10.2                 v4 ['is_private', 'is_link_local']
fe80::1c2a:3bff:fe4d:5e6f    v6 ['is_private', 'is_link_local']
fd12:3456:789a::10           v6 ['is_private']
2001:db8::1                  v6 ['is_private']
::ffff:10.0.0.5              v6 ['is_private']
2001:db8::8a2e:370:7334
2001:0db8:0000:0000:0000:8a2e:0370:7334
10.0.0.5

Details worth noticing:

  • 100.64.12.7 is neither private nor global. The CGNAT range is reserved for carriers, so a rule that only checks is_private misses it.
  • is_private in this module means "not globally reachable" according to the IANA special-purpose registries, so it is true for documentation and link-local ranges too. Newer Python releases have refined some of these edge cases, so pin your expectations with tests.
  • An IPv4-mapped IPv6 address wraps an IPv4 address. Code that validates destinations (for example, to block requests to internal services in an SSRF defence) should unwrap ipv4_mapped first, and must resolve hostnames to addresses before checking, because an attacker controls which address a name points to.

Dual Stack and Transition#

The internet runs both protocols at once. In a dual-stack network, hosts hold an IPv4 and an IPv6 address, DNS returns both A and AAAA records, and clients prefer IPv6. To avoid hanging when IPv6 is advertised but broken, browsers and many libraries use Happy Eyeballs (RFC 8305): start the IPv6 connection, and if it has not succeeded within a short delay, race an IPv4 attempt and use whichever connects first.

Where networks are IPv6-only (common in mobile carriers), NAT64 translates IPv6 packets to IPv4 at the edge and DNS64 synthesizes AAAA records for IPv4-only names, typically inside the well-known prefix 64:ff9b::/96. If you see a destination like 64:ff9b::5db8:d822, the last 32 bits are the embedded IPv4 address (93.184.216.34). Older tunnelling mechanisms such as 6to4 and Teredo are largely retired.

Inspecting Your Own Addresses#

ip -4 addr show            # Linux IPv4
ip -6 addr show            # Linux IPv6: look for "scope global" vs "scope link", and "temporary"
ip -6 route show default   # is there an IPv6 default route at all?
ipconfig /all              # Windows

A line such as inet6 2001:db8:1:2:9c1e:4ab2:77f0:1d3e/64 scope global temporary dynamic shows a global address, its /64 prefix, and that it is a rotating privacy address.

Diagnosing Addressing Problems#

Symptom Likely cause Next check
Address in 169.254.x.x DHCP failed; the host self-assigned link-local Is the DHCP server reachable? Is the port on the right VLAN?
Only fe80:: IPv6 addresses No router advertisements on the link Router config, or RAs filtered by a switch
Sites load slowly, then work Broken IPv6 path, Happy Eyeballs falling back curl -6 -v to a dual-stack site; check MTU and ICMPv6 filtering
WAN address of your router in 100.64.0.0/10 You are behind carrier-grade NAT Inbound port forwarding will not work
IPv4 works, IPv6 does not (or vice versa) Single-stack network or broken transition path Test each explicitly with ping -4 and ping -6

Never assume one family working implies the other does. They have separate routes, separate firewall rules and often separate DNS records; a server listening only on 0.0.0.0 accepts IPv4 but not IPv6 unless it also binds ::.

Practice#

Take the prefix 2001:db8:abcd::/48 and write down the first three /64 subnets and the last one, then confirm your answer with list(itertools.islice(ipaddress.ip_network("2001:db8:abcd::/48").subnets(new_prefix=64), 3)). Then decide which of your own machine's current addresses are global, link-local and temporary.