IPv6 for developers who have been avoiding it
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
inetandcidrtypes that handle both families, with operators for containment. Use them. - MySQL has
VARBINARY(16)plusINET6_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 habit | IPv6 reality |
|---|---|
| NAT is everywhere | End-to-end addressing is normal again; NAT is not the firewall |
| One address per host | Several per interface, and they rotate |
| Address ≈ user | Meaningless — use the prefix |
| Broadcast | Does not exist; multicast replaces it |
| ARP | Replaced by Neighbour Discovery over ICMPv6 |
| Blocking ICMP is fine | Do 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
- Stop comparing addresses as strings. Parse first. This is the bug you already have.
- Widen storage to something that holds 16 bytes and supports containment queries.
- Move per-address logic to per-prefix, /64 for anonymous traffic.
- Stop filtering ICMPv6.
- 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.