- Mon 08 June 2026
- 22 min read
- Networking
- #ipv6, #networking, #slaac, #ndp, #icmp, #nat64, #foundations
Update (2026-06-12): A reader, Ben, sent in a wonderful list of questions after reading this post, the kind you only get from someone genuinely working through the material. This is the updated version. The NDP and ARP explanations now appear before they are used, the privacy-address and RDNSS parts have grown answers to “who actually enforces this?” and “where does DNS really come from?”, and there are new sections on banning abusive clients, your router’s public address and the end of port forwarding, and reaching the IPv4 internet from IPv6-only (NAT64/DNS64). Thanks, Ben.

Let me start with the thing nobody wants to say out loud: IPv6 is not “the future of the internet”. It is the internet. It has been a finished, deployed, standards-track protocol since 1998. Major mobile networks run IPv6-only internally. More than half of the traffic Google sees from many countries is already IPv6. The “future” framing is a comfortable lie we tell ourselves so we can keep treating IPv4 as the default and IPv6 as the weird optional thing the network nerd insists on.
So let me reframe it for this article. IPv6 is the current version of the Internet Protocol. IPv4 is the legacy one. It is a 32-bit address space from 1981 that ran out of room more than a decade ago, kept alive on life support by NAT, carrier-grade NAT, and an aftermarket where people trade /24s like baseball cards. It works, in the way a 40-year-old car works: lovingly, expensively, and only because a lot of people refuse to let it die.
This is a foundations post. I am going to cover the actual basics: how an IPv6 address is built, how to shorten it, how it maps onto the IPv4 you already understand, and how a host configures itself with SLAAC. There is a short sidebar on NDP, and a small rant about why you must not block ICMP. Keep your shoes off, this is meant to be relaxed.
Table of Contents
- Table of Contents
- The address: 128 bits, written in hex
- Shortening: getting rid of the zeros
- Structure: the prefix and the interface
- What is the same as IPv4 (more than you would think)
- SLAAC: how a host configures itself
- Sidebar: NDP, and why ICMP is not optional
- Your router’s “public address” and the end of port forwarding
- Dual-stack is a burden, not a solution
- Reaching the IPv4 internet from IPv6-only: NAT64 and DNS64
- Where to go from here
The address: 128 bits, written in hex
An IPv4 address is 32 bits. We write it as four decimal numbers, one per byte, separated by dots: 192.0.2.10. That gives you about 4.3 billion addresses total, which sounded enormous in 1981 and is comically small for a planet with more connected devices than people.
An IPv6 address is 128 bits. That is not “four times bigger”, it is 2^128 versus 2^32, which is a number so large it stops being meaningful. You get 340 undecillion addresses, give or take. The standing joke, which is only barely a joke, is that there are enough IPv6 addresses to assign one to every atom on the surface of the Earth and still have plenty left over for several more planets.
Writing 128 bits as decimal bytes would be miserable, so IPv6 uses hexadecimal. The address is split into eight groups of 16 bits, each group written as up to four hex digits, separated by colons:
2001:0db8:0000:0000:0000:ff00:0042:8309
That is one full, uncompressed IPv6 address. Eight groups, four hex digits each, colons in between. Each hex digit is 4 bits, four digits is 16 bits, eight groups is 128 bits. The math is tidy even if the result looks intimidating at first.
2001:db8::/32 is the documentation range, by the way, the IPv6 equivalent of 192.0.2.0/24. I will use it throughout so I am not leaking real addresses.
Shortening: getting rid of the zeros
Nobody types the full form above, and you should not either. There are exactly two rules for shortening an IPv6 address, and once they click you will read these things at a glance.
Rule 1: drop leading zeros in each group. Within any single group, leading zeros are noise. 0db8 becomes db8. 0000 becomes 0. 0042 becomes 42. ff00 stays ff00 because there is nothing leading to drop. Apply that to the address above:
2001:db8:0:0:0:ff00:42:8309
Already much friendlier. Note that this is leading zeros only. You cannot touch trailing or middle zeros inside a group, because the position of a digit is its value. 8309 stays 8309.
Rule 2: collapse one run of all-zero groups with ::. A double colon means “as many all-zero groups as it takes to make the address 128 bits again”. Our address has a run of three 0 groups in the middle, so:
2001:db8::ff00:42:8309
That is the canonical, shortened form. A parser sees ::, counts the groups present on either side (here: two on the left, three on the right, five total), and fills in the missing three groups with zeros to reach eight. Clean.
The one catch, and it is the only rule that ever trips people up: you may use :: exactly once per address. If you used it twice, the parser could not know how many zero-groups belong to each gap. So 2001:db8:0:0:1:0:0:1 collapses to 2001:db8::1:0:0:1 or 2001:db8:0:0:1::1, but never 2001:db8::1::1. When there is a choice, convention says collapse the longest run, and if two runs tie, collapse the first one.
A few useful special cases fall straight out of these rules:
::1is the loopback address. The whole thing is zeros except the final bit. This is IPv6’s127.0.0.1, except it is a single address instead of an entire wasted /8.::(just the double colon, all zeros) is the unspecified address, used as a source while a host is still figuring out who it is.2001:db8::is2001:0db8:0000:...:0000, a prefix with everything after it zeroed.
Structure: the prefix and the interface
Here is where IPv6 quietly becomes easier than IPv4, not harder.
An IPv6 address has two halves. The left half is the network prefix (which subnet you are on), and the right half is the interface identifier (which host you are within that subnet). The slash notation works exactly like CIDR in IPv4: /64 means the first 64 bits are the network and the rest is the host.
And here is the gift: in the overwhelming majority of cases, the boundary is /64, full stop. A standard IPv6 subnet is a /64. Not “depends on how many hosts you have”, not “let me get the subnet calculator”. A /64. Every LAN, every VLAN, every normal segment gets a /64, which contains 2^64 addresses, which is roughly 18 quintillion hosts on a single subnet. You will not run out.
The prefixes you actually deal with as a network operator:
- /64 - one subnet. The atom of IPv6 addressing. SLAAC (below) requires a /64 to work.
- /56 - a typical home or small-site allocation. That is 256 /64 subnets, one prefix handed to your router by your ISP, plenty for VLANs, guest networks, and IoT segregation.
- /48 - a site allocation. 65,536 /64 subnets. This is what I run for my own AS; the prefix is
2a06:9801:1c::/48, carved into /64s per jail and per VLAN. If you want the BGP side of that story, see Running Your Own AS.
The mental shift is that you stop hoarding addresses. In IPv4 you agonize over whether a subnet should be a /27 or a /28 because every address is precious. In IPv6 you hand out /64s like business cards and spend your thinking budget on the network design instead of the arithmetic.
Address scopes you will meet
IPv6 leans on the idea that an interface has several addresses at once, each with a scope. The ones worth knowing:
- Link-local (
fe80::/10): every IPv6 interface has one, automatically, always. It is only valid on the local link and is never routed. This is the address that NDP (the Neighbor Discovery Protocol, IPv6’s link-level housekeeping protocol, explained properly in the sidebar below) and your routing protocols actually talk over. If you have ever seenfe80::...%eth0, the%eth0is the zone index telling the kernel which link, because the same link-local address can exist on every interface. - Unique Local Addresses (
fc00::/7, in practicefd00::/8): the IPv6 answer to RFC 1918 private space (10.0.0.0/8and friends). Routable inside your site, not on the public internet. Useful, but do not build your whole world on them. - Global Unicast (
2000::/3): real, publicly routable addresses. The2001:db8::documentation range lives here, and so does every address your ISP delegates to you. - Multicast (
ff00::/8): IPv6 has no broadcast. None. Where IPv4 shouts at255.255.255.255, IPv6 uses targeted multicast groups, for exampleff02::1(all nodes on the link) andff02::2(all routers on the link). This is a genuine improvement: instead of waking every NIC on the segment, you talk only to the machines that signed up to listen.
What is the same as IPv4 (more than you would think)
It is easy to look at the colons and the hex and assume IPv6 is an alien protocol. It is not. It is Internet Protocol, the same job as IPv4, with the address field made sane. Almost everything you know carries straight over:
- It is still routed by longest-prefix match. A router looks at the destination, finds the most specific matching prefix in its table, forwards. Same algorithm, wider addresses.
- TCP and UDP ride on top unchanged. A socket is a socket. Your web server, your database, your SSH daemon do not care; they bind to an address and a port.
[2001:db8::1]:443is just an address and a port with brackets so the colons do not get confused with the port separator. - DNS works the same, you just publish
AAAArecords instead of (or alongside)Arecords.example.com. AAAA 2001:db8::1. Resolvers, zones, TTLs, all identical. - CIDR, prefixes, and routing tables behave the same.
/64,/48,/32, default route::/0instead of0.0.0.0/0. Same concepts. - The transport security, the application layer, TLS, HTTP, all of it sits on top and never knew which IP version carried the packets.
The differences are mostly removals, and they are removals of bad ideas:
- No NAT. Every device can have a real, globally routable address again, the way the internet was supposed to work before we ran out of room. End-to-end connectivity stops being a luxury. (You still want a firewall. A firewall is not NAT, and conflating the two is how a generation came to believe address translation was a security feature.)
- No broadcast, as mentioned. Multicast replaces it and does the job more politely.
- Routers do not fragment. In IPv4, a router could chop up a too-big packet. In IPv6 that is the sender’s job, via Path MTU Discovery, which leads directly into why you must not block ICMP. Hold that thought.
- The header is simpler and fixed-length with extension headers bolted on as needed, which makes high-speed forwarding easier.
SLAAC: how a host configures itself
This is the part that genuinely delights people the first time they see it. In IPv4-land, a host that wants an address asks a DHCP server, which hands out a lease from a pool it manages, tracks state, and hopes nobody unplugs anything. IPv6 can do that too (DHCPv6), but it also has something better for most cases: SLAAC, Stateless Address Autoconfiguration.
Here is the whole dance, and notice that there is no server keeping a database anywhere:
- The interface comes up and immediately gives itself a link-local address in
fe80::/10. No configuration, no asking. It derives the host part and checks it is unique on the link (Duplicate Address Detection, one of the NDP mechanisms covered in the sidebar below). - The host sends a Router Solicitation (RS) to
ff02::2, the all-routers multicast group. Translation: “any routers out there, what is the prefix on this link?” - A router replies with a Router Advertisement (RA). The RA carries the
/64prefix for the link, the default gateway (the router’s own link-local address), and a set of flags. Routers also send RAs periodically and unprompted, so a host often does not even need to ask. - The host takes the advertised
/64prefix and generates the host half itself, then bolts the two together into a full global address. No server assigned it. The host made it up within the prefix it was told to use, and because a /64 has 18 quintillion slots, collisions are a non-issue (and DAD catches them anyway).
How the host picks its own interface ID has two schools:
- EUI-64: derive it deterministically from the MAC address. Stable, but it leaks your hardware address and lets you be tracked across networks. Largely out of fashion for client devices for exactly that reason.
- Privacy / temporary addresses (RFC 4941, and the more modern stable-but-opaque RFC 7217): generate a random-looking interface ID, rotate the temporary ones periodically. This is what your laptop and phone do today. You will routinely see a machine holding several IPv6 addresses at once: a stable one for inbound, a rotating temporary one for outbound. That is working as intended, not a bug.
The privacy-address dance deserves a closer look, because it feels alien the first time you meet it. The key insight is that nobody assigns or tracks these addresses centrally. The device generates them itself, and the device itself decides which address to use as the source when it opens an outbound connection. The router does not need to know which address is “the outbound one”; it just routes packets like it routes everything else. In practice the device keeps one stable address (RFC 7217 style: random-looking, but constant on that network) and additionally generates a temporary one (RFC 4941), prefers the temporary one for outbound connections, and replaces it periodically, typically daily, with the old one lingering a while so existing connections do not break.
And to be clear about the threat model: rotation does not hide you from someone who already knows your current address. That is not what it is for. It exists so that the websites you visit cannot correlate your activity over time and across services using your address as a long-lived identifier, the way the bottom 64 bits would be if they were derived from your MAC. Inbound protection is a separate concern, and that is the firewall’s job: a sane IPv6 router drops all unsolicited inbound traffic to every address in your network by default, stable and temporary alike, exactly like NAT incidentally did for IPv4. You then open specific holes for the things you want reachable.
The server-side flip side: ban the /64, not the address
Privacy addresses have a consequence for anyone running a server, a WAF, or fail2ban. Banning a single abusive IPv6 address is mostly pointless, because the client can rotate within its /64 forever. The standard practice is to block the whole /64, since that is one subnet and almost always one household or one server. If abuse persists across neighbouring /64s, escalate to the /56 (a typical home allocation) or the /48 (a typical site). Modern tooling has grown IPv6 support along exactly these lines; fail2ban, for example, can ban by prefix length instead of by single address.
“But where does DNS come from if there is no DHCP?” There is no magic well-known DNS address baked into the protocol (there were proposals for one long ago; they died). Instead, modern RAs answer the question via the RDNSS option (RFC 8106): the same periodic multicast packet that announces the prefix and the gateway also carries “and here are your DNS resolvers”. So it is still configuration on the router, just delivered inside the RA instead of over DHCP, and many consumer routers send an RDNSS option pointing at themselves without ever exposing a knob for it in their UI. Worth knowing: Android famously refuses to implement DHCPv6 at all, so on a network without RDNSS, Android devices simply have no DNS. That stance alone forced a lot of router vendors to support it. The upshot stands: a host can come up on a fresh network and end up with an address, a gateway, and working DNS, having configured everything itself from a couple of multicast packets. No DHCP server, no lease database, no scope exhaustion at 3am. That is the part worth being a little smug about.
Sidebar: NDP, and why ICMP is not optional
The short version: In IPv6, ICMP is not a diagnostic afterthought you can firewall away. It is load-bearing. Block it and the network stops working, not “works worse”, stops.
First, a word on ARP for everyone who comes from a software background rather than a networking one. In IPv4, before a machine can send a packet to another machine on the same network, it needs to know the target’s hardware (MAC) address, because that is what Ethernet actually delivers frames to. ARP is the small separate protocol that solves this: it shouts “who has 192.0.2.1?” as a broadcast to everyone on the wire and waits for the owner to answer. That is the whole job. ICMP, meanwhile, is the optional-feeling thing you use for ping and that a lot of paranoid admins reflexively block at the firewall “for security”.
IPv6 throws ARP away and replaces it with the Neighbor Discovery Protocol (NDP), and NDP is built entirely on top of ICMPv6. Everything I described in the SLAAC section is ICMPv6 messages:
- Router Solicitation / Router Advertisement (the RS/RA from SLAAC) are ICMPv6 types 133 and 134.
- Neighbor Solicitation / Neighbor Advertisement are ICMPv6 types 135 and 136. This is the ARP replacement: “who has
2001:db8::1?” answered with “I do, here is my MAC.” It rides on multicast instead of broadcast, but functionally it is ARP’s job done over ICMP. - Duplicate Address Detection and Redirects are NDP too.
So when someone copies their old IPv4 firewall habit and writes “drop all ICMP” on an IPv6 interface, they have not hardened anything. They have unplugged address resolution, router discovery, and autoconfiguration. The host cannot find its neighbors or its gateway. The network is dead, and the admin spends an afternoon confused.
It gets one notch worse because of the no-fragmentation-by-routers rule from earlier. IPv6 relies on Path MTU Discovery, where a router that cannot forward an oversized packet sends back an ICMPv6 “Packet Too Big” (type 2) message so the sender can shrink its packets. Block that message and you get the classic nightmare: small packets and pings work fine, but large transfers and TLS handshakes hang forever with no obvious cause. It is one of the most maddening failure modes in networking, and it is almost always someone over-filtering ICMPv6.
The rule is simple. Do not block ICMPv6 wholesale. Permit Neighbor Discovery, permit Router Advertisements where you expect them, permit Packet Too Big, permit the essentials (RFC 4890 spells out exactly which types to allow). Rate-limit if you must, filter specific types thoughtfully, but the blanket “drop ICMP” rule that was merely rude on IPv4 is actively self-destructive on IPv6. ICMPv6 is part of the control plane now, not a luxury.
Your router’s “public address” and the end of port forwarding
A question that comes up constantly from people arriving from IPv4-land: “my router shows a single public IPv6 address, just like it shows a single public IPv4 address. Can I port forward from it to an internal machine?”
The single address your router shows is just its own WAN address, the one it uses for traffic it originates itself. Separately from that, your ISP delegates an entire prefix to it (via DHCPv6 prefix delegation, usually a /56), and the router hands out /64s from that delegation to your LAN segments. Many consumer UIs display the WAN address prominently and the delegated prefix barely or not at all, which is exactly backwards in terms of importance, but here we are.
And here is the mental flip: port forwarding exists in IPv4 because your internal machines have no public address, so the router must translate, mapping one of its own ports to an internal host. In IPv6 your internal machines already have globally routable addresses, so there is nothing to forward. You do not map router-port-to-internal-host; you simply open a firewall rule that says “allow inbound TCP 443 to 2001:db8:abcd:1::5”. Some router UIs still label this “port forwarding” out of familiarity, but it is a pinhole, not a translation. That so many consumer devices hide all of this, or reduce IPv6 to a single on/off toggle with no further configuration, is sadly the actual state of consumer networking gear, and it is fair to be annoyed by it.
Dual-stack is a burden, not a solution
The pitch for dual-stack is reasonable on its face: run IPv4 and IPv6 side by side, let clients prefer IPv6, and you have a smooth glide path. In practice, dual-stack is not a destination. It is the most expensive possible state, and we have been parked in it for over a decade.
Think about what running both actually costs you. Every firewall rule exists twice, in two syntaxes, with two chances to disagree. Every DNS name needs an A and an AAAA, and the day they point at different backends is the day you debug a “works for me” ticket for three hours. Every monitoring check, every ACL, every load-balancer config, every piece of address-parsing code in your stack has to handle two formats forever. Security review surface doubles. Happy Eyeballs papers over connection failures by racing both stacks, which is clever and also means a half-broken IPv6 path can hide for months because clients silently fall back to IPv4.
Dual-stack made sense as a transition mechanism. The problem is that “transition” implies you eventually arrive somewhere, and the somewhere is IPv6-only. Running both indefinitely is not having the best of both worlds, it is paying full price for two networks and getting the operational headaches of each. The mobile carriers worked this out years ago, which is why so many of them are IPv6-only at the core with IPv4 stapled on at the edge as a translation service for the legacy internet, rather than the other way around.
I am not telling you to rip IPv4 out tomorrow; the legacy internet still exists and your users still need to reach it. I am telling you to stop treating IPv4 as the foundation and IPv6 as the add-on. Flip the polarity. Design IPv6-first, make IPv6 the path that works without caveats, and treat IPv4 as the compatibility shim it has become, a thing you provide grudgingly at the edge for the parts of the world that have not caught up yet.
Reaching the IPv4 internet from IPv6-only: NAT64 and DNS64
I keep saying the mobile carriers are IPv6-only internally with IPv4 stapled on at the edge. The mechanism behind that staple is worth knowing, because it answers a question people sense must have an answer: if IPv6 is so much larger, can you not reach the entire IPv4 internet from inside it?
You can, and the trick is called NAT64 plus DNS64. DNS64 is a resolver trick: when a client asks for a name that only has an IPv4 address (an A record but no AAAA), the resolver synthesizes a fake IPv6 address by embedding the IPv4 address into a special prefix, 64:ff9b::/96. The client happily connects to that synthetic address, the packet lands on a NAT64 gateway, and the gateway translates the connection into a real IPv4 one. From the client’s point of view, the entire IPv4 internet appears as one tiny corner of the IPv6 address space, which only works because v6 is so much larger.
This is not built into the protocol; you need a NAT64 gateway somewhere. ISPs and mobile carriers operate them at scale, and at home you can run Jool on a Linux box or use the NAT64 modes built into OpenWrt and OPNsense. Combined with DNS64 on your resolver, that is all it takes to run an IPv6-only network whose users never notice that half the destinations they reach are still legacy v4.
Where to go from here
That is the foundation: 128-bit addresses written in hex, two simple rules for crushing out the zeros, a /64 per subnet so you stop counting hosts, the same routing and DNS and transport you already know, SLAAC so machines configure themselves, and NDP-over-ICMPv6 holding it all together so you really, truly must not block ICMP.
If you want to go deeper, the natural next steps from here are getting your own globally routable space and announcing it, which I covered in Running Your Own AS, and then firewalling it properly on the platform of your choice. The point of this post was just to knock the intimidation off. IPv6 is not exotic. It is the normal way to do networking, and it has been for a long time. The hex is the only scary part, and now even that is not.
Comments
You can use your Mastodon or other ActivityPub account to comment on this article by replying to the associated post.
Search for the copied link on your Mastodon instance to reply.
Loading comments...