IPv6 for developers who have been avoiding it

Parneet Singh9 min read

You have been putting off IPv6. Your code has a regex for dotted quads, your database has a VARCHAR(15), and everything works. Then something arrives with eight groups of hexadecimal in it and the assumptions start falling over.

Here is the short version of what actually changes.

The address is 128 bits, and that is the whole story

IPv4 addresses are 32 bits — about 4.3 billion, which ran out. IPv6 addresses are 128 bits. The number is large enough that the usual comparisons stop being useful; what matters practically is that scarcity stops being a design constraint, and several things you built around scarcity no longer apply.

Written form is eight groups of four hex digits:

2001:0db8:0000:0000:0000:ff00:0042:8329

Two compression rules apply, and RFC 5952 makes them mandatory for canonical output:

  • Leading zeros in a group are dropped: 0042 → 42
  • One run of all-zero groups collapses to :: — the longest run, and only once, because two would be ambiguous

So the above is canonically:

2001:db8::ff00:42:8329

The practical consequence: the same address has many valid text forms. Never compare IPv6 addresses as strings. Parse them to bytes and compare those, or normalise to the canonical form first. String comparison is the single most common IPv6 bug and it fails silently — two spellings of one address simply never match.

Storage

VARCHAR(15) is not enough. Neither is VARCHAR(39) if you want to be able to query ranges.

  • PostgreSQL has inet and cidr types that handle both families, with operators for containment. Use them.
  • MySQL has VARBINARY(16) plus INET6_ATON / INET6_NTOA.
  • Generic: 16 raw bytes, with IPv4 mapped into the IPv6 space.

That last trick is worth knowing. IPv4 addresses map into IPv6 as ::ffff:0:0/96, which means one 128-bit keyspace can hold both families with correct ordering and no special cases. If you are building range lookups over mixed traffic, this removes an entire branch of code.

Prefixes, not addresses

In IPv4 you think about individual addresses. In IPv6 you almost never should.

A residential customer typically receives a /64 at minimum, often a /56 or /48. A /64 contains 2⁶⁴ addresses — more than the entire IPv4 internet, squared. A single machine routinely holds several addresses at once, and privacy extensions (RFC 4941) rotate them.

So "one address, one user" is not merely unreliable in IPv6, it is nonsense. Anything you keyed on an address — rate limits, bans, session pinning, fraud counters — must key on a prefix instead. The /64 is the usual unit; it corresponds roughly to one customer network.

This is the change that actually bites in application code, and it is not a find-and-replace.

What stops being true

IPv4 habitIPv6 reality
NAT is everywhereEnd-to-end addressing is normal again; NAT is not the firewall
One address per hostSeveral per interface, and they rotate
Address ≈ userMeaningless — use the prefix
BroadcastDoes not exist; multicast replaces it
ARPReplaced by Neighbour Discovery over ICMPv6
Blocking ICMP is fineDo not. IPv6 depends on ICMPv6 for Path MTU Discovery and Neighbour Discovery; blocking it breaks connectivity in ways that look like random slowness

That last row causes real outages. IPv4 tolerates aggressive ICMP filtering. IPv6 does not.

Geolocation of IPv6

Broadly the same as IPv4, with two differences worth knowing.

Allocations are newer and often larger, so the registry data behind them tends to be cleaner but coarser — a single allocation may cover a whole region. And because addresses within a customer prefix rotate, the individual address is even less stable than in IPv4, while the prefix is more stable.

Look up the prefix. It is the part with meaning.

What to actually do

  1. Stop comparing addresses as strings. Parse first. This is the bug you already have.
  2. Widen storage to something that holds 16 bytes and supports containment queries.
  3. Move per-address logic to per-prefix, /64 for anonymous traffic.
  4. Stop filtering ICMPv6.
  5. Test with real IPv6. Not a single literal in a unit test — a compressed address, an expanded one, an IPv4-mapped one, and a prefix.

None of this requires a migration. IPv6 runs alongside IPv4, and the work is mostly making your code stop assuming.


The IPv6 expand and compress tool converts between the full 39-character form and the canonical short form, which is useful when you are debugging why two addresses that look different are the same one. The IPv4 to IPv6 converter shows the mapped form. Every lookup through the API accepts both families.

All posts · RSS