The NAT article quoted RFC 1631 from 1994: the short-term answer was CIDR, and the long-term answer was a new protocol with longer addresses.
This page is that protocol.
And the phrase to discard at the outset is "longer addresses", because the longer addresses are only the headline. What actually changed is four things that were taken away — routers stopped fragmenting, the header checksum was deleted, broadcast disappeared entirely, and ARP moved into ICMP.
Every deletion has consequences, and those consequences are what this page is about.
Longer addresses, and a size that cannot be drawn
2^32 = 4,294,967,296
2^128 = 340,282,366,920,938,463,463,374,607,431,768,211,456
The NAT article already computed that IPv4's total divided by the world population is 0.52 addresses each, which explains why it was never going to be enough.
The ratio between the two lines above is 2 to the 96th, and the way to actually see it is to convert it into distance. If the whole IPv4 space were one millimetre wide, the IPv6 space would be about 8.4 billion light-years wide.
That figure is checkable: multiply 2^96 by one millimetre and divide by the length of a light-year.
But size is not the hard part of this page. The rest is about what is missing.
If you have never looked, start here
The machine reading this page already has four IPv6 addresses on one interface.
$ ifconfig en0 | grep inet6
inet6 fe80::1a2b:3c4d:5e6f:7a8b%en0 prefixlen 64 secured scopeid 0xc
inet6 2001:db8::a41:2f77:81c3:5e02 prefixlen 64 autoconf secured
inet6 2001:db8::5d19:c8ae:3b40:9f61 prefixlen 64 autoconf temporary
inet6 2001:db8::5 prefixlen 64 dynamic
Four addresses on one card is normal, not a fault, and each has a distinct job, explained one at a time in the sections below.
Notice three things now. Every one is prefixlen 64. The first begins with fe80:: and carries a %en0 suffix. And temporary and secured are not the same thing.
Writing an address correctly
128 bits are written as eight groups of four hexadecimal digits separated by colons, and then shortened in two ways.
2001:0db8:0000:0000:0000:ff00:0042:8329 written out
2001:db8:0:0:0:ff00:42:8329 leading zeros dropped
2001:db8::ff00:42:8329 zero run collapsed
RFC 4291 states the most important rule in one line: "The '::' can only appear once in an address".
The reason is arithmetic rather than aesthetics. Used twice in one address, there is no way to know how many zero groups each stands for, because the problem becomes an equation with many solutions.
RFC 5952 adds another layer so that one address has exactly one written form, which matters because a great many systems compare addresses as strings.
- "Leading zeros MUST be suppressed" — always drop them
- "The longest run of consecutive 16-bit 0 fields MUST be shortened" — and where runs are equal in length, shorten the first
- "MUST NOT be used to shorten just one 16-bit 0 field" — no
::for a single zero group - The letters
athroughf"MUST be represented in lowercase"
The last one looks fussy until you meet a system that stores addresses as text and compares them directly. 2001:DB8::1 and 2001:db8::1 are one address and two strings.
A header twice the size that makes routers do less work
The IPv6 header is 40 bytes against IPv4's minimum of 20. At a glance that looks worse, and the number is misleading.
bits field
4 version
8 traffic class
20 flow label
16 payload length
8 next header
8 hop limit
128 source address
128 destination address
---
320 bits = 40 bytes
32 of those 40 bytes are addresses. Everything else is eight bytes, against IPv4's twelve for the same job. The header grew because the addresses grew, not because it got more complicated.
And 40 is fixed, not a minimum. An IPv4 header varies from 20 to 60 bytes, which is why it needs an IHL field to say how long it is, and why a router must read that field before it knows where the data starts. IPv6 removed that step entirely.
What was deleted
IPv4 field what happened in IPv6
IHL gone, the header is always 40
total length became payload length, header excluded
identification moved to a fragment extension header
flags + frag offset moved to a fragment extension header
header checksum deleted outright
options moved to extension headers
The header checksum is the boldest line in that table. IPv4 recomputes it at every router, because every router changes the TTL. That work is simply gone.
It could be deleted because it was redundant. The layer below has Ethernet's FCS, and the layer above has TCP and UDP's checksum. A third check in the middle cost more than it returned.
But the deletion came with a condition. RFC 8200 states that "the default behavior when UDP packets are originated by an IPv6 node is that the UDP checksum is not optional" — UDP could switch its checksum off over IPv4; over IPv6 it cannot, because switching it off would leave nothing checking at all.
Extension headers — the options that moved outside
The 8-bit next header field does the same job as IPv4's protocol: it says what comes next. But its answer has two forms. It may name an upper-layer protocol like TCP, or it may name another extension header, which carries its own next header in turn.
The header therefore becomes a chain of any length, and everything IPv4 packed into options and its fragment fields moved here.
The benefit is that a router with no interest need not read them. Forty bytes is enough to route, unlike IPv4 where options sit inside the main header and must be parsed out every time.
The drawback is that reality did not follow the design. A great deal of middleware drops packets carrying extension headers because it cannot tell what is inside. The mechanism designed for flexibility became the least usable part of the whole protocol.
Routers stopped fragmenting
RFC 8200 says it plainly: "fragmentation in IPv6 is performed only by source nodes, not by routers along a packet's delivery path".
This is a transfer of responsibility, not a removal of capability. IPv4 lets a router in the middle split an oversized packet, which is convenient for the source and very expensive for the router, which must hold state, compute, and build a new header for every piece.
IPv6 moved all of that to the source. A router meeting an oversized packet has exactly one option: discard it and send back an ICMPv6 Packet Too Big.
1280 is the floor
RFC 8200 sets the floor: "IPv6 requires that every link in the Internet have an MTU of 1280 octets or greater", and recommends further that "it is recommended that they be configured with an MTU of 1500 octets or greater".
1280 every link must carry this, no exceptions
1500 what the standard recommends configuring
1280 exists so that a source unwilling to do Path MTU Discovery has a safe value to use. Send at 1280 and there is nothing to wait for.
And depending on ICMP this way is the most fragile part of the whole story. RFC 8201 warns about it itself: filter Packet Too Big and the source never learns the real value. The symptom is that "the TCP three-way handshake completes correctly but the connection hangs when data is transferred".
A successful handshake followed by a hang on transfer is this problem's signature and almost nothing else's.
No more broadcast
RFC 4291 disposes of it in one sentence — "There are no broadcast addresses in IPv6, their function being superseded by multicast addresses".
That reads like tidying up nomenclature, and its effect runs far deeper.
Broadcast forces every machine on the wire to take the frame up to its CPU, where nearly all of them discover it was not for them. That work is entirely wasted, and the larger the network the more of it there is — the same problem the VLAN and Spanning Tree articles approach from other directions.
Multicast lets a machine choose which groups it listens to, and the network card filters the rest out in hardware before the CPU ever sees them.
One interface, many addresses, and that is normal
In IPv4, several addresses on one card is a special configuration. In IPv6 it is the default.
RFC 4291 lists the addresses a node must recognise, which includes the link-local address of every interface, the loopback address, the all-nodes multicast group, and the solicited-node group for every unicast address it holds.
That last one is checkable on this machine. There are four unicast addresses on en0, and computing the solicited-node group of each by the formula and comparing against the groups the machine has actually joined, all four match.
The fe80:: address is the strangest one for somebody arriving from IPv4. It is always there, it exists before anything else, and nobody has to hand it out. Almost all routing in a real IPv6 network points at a link-local address rather than a global one, as the worked examples below show.
The %en0 suffix is called a zone index, and it is necessary because fe80::1 on two different cards is two different machines. A link-local address is not unique in the world; it is unique only on that one wire.
ARP moved into ICMP
RFC 4861 describes itself precisely: Neighbor Discovery "corresponds to a combination of the IPv4 protocols Address Resolution Protocol [ARP], ICMP Router Discovery [RDISC], and ICMP Redirect".
Three separate things became one, and all of it runs over ICMPv6.
133 Router Solicitation
134 Router Advertisement
135 Neighbor Solicitation
136 Neighbor Advertisement
137 Redirect
The deeper change is not the merger. ARP does not run on IP; it runs beside it — its own protocol with its own EtherType. NDP is ICMPv6, which runs on IPv6.
The result is that NDP inherits everything IPv6 has for free: its ability to be authenticated, verified, and secured at the IP layer.
Solicited-node multicast
Rather than asking the whole wire, NDP asks a group computed from the address being looked for.
unicast 2001:db8::a41:2f77:81c3:5e02
low 24 bits c3 5e 02
solicited-node ff02::1:ffc3:5e02
RFC 4291 gives the construction: take the low-order 24 bits of the unicast address and append them to the prefix FF02:0:0:0:0:1:FF00::/104.
RFC 4861 puts the effect in numbers: "Address resolution multicasts are 'spread' over 16 million (2^24) multicast addresses, greatly reducing address-resolution-related interrupts on nodes other than the target".
Sixteen million groups means the chance of disturbing an uninvolved machine is very small, and the filtering happens in the network card rather than the CPU.
Hop limit 255 as a security mechanism
RFC 4861 uses a very clever trick and explains it: "By setting the Hop Limit to 255, Neighbor Discovery is immune to off-link senders".
The mechanism is that NDP transmits with a hop limit of 255, the largest value an 8-bit field can hold, and the receiver checks that what arrived is still 255.
One router in the path and the value would be 254, because everything that forwards must subtract one. Seeing 255 therefore proves the sender is genuinely on the same wire.
This is an existing field doing a job it was never designed for, at no extra cost.
A machine that finds its own address
RFC 4862 describes SLAAC's goal as requiring "no manual configuration of hosts, minimal (if any) configuration of routers, and no additional servers".
The last clause is the real one. No server, unlike DHCP, which needs somebody handing out addresses and remembering what has been handed out.
The sequence has four steps.
One, the interface comes up and immediately builds a link-local address for itself from fe80:: plus an interface identifier. This step involves nobody else at all.
Two, run DAD before using it — send a Neighbor Solicitation asking for the address it is about to take. Silence means it is free. If somebody answers, the RFC says that address "MUST NOT be assigned to an interface".
Three, send a Router Solicitation. A router answers with a Router Advertisement carrying a prefix.
Four, combine that prefix with the same interface identifier to get a global address, then run DAD again.
Why it has to be /64
RFC 4291 states it directly: "Interface IDs are required to be 64 bits long".
The 64 does not come from any calculation about how many machines are needed. It comes from the second half of the address being reserved for a machine to build itself. Subnet smaller than /64 and SLAAC cannot work, because there is no room left for an interface identifier.
This is the largest difference from IPv4 subnetting, where dividing is about conserving addresses. In IPv6 dividing is not about conservation. A subnet is always a /64, even with one machine in it.
A single /64 holds 2^64 addresses, four billion times the entire IPv4 space.
Interface identifiers and the privacy problem
The first method of building an interface identifier derived it directly from the MAC address, which works and guarantees uniqueness. But it means the second half of the address follows the machine everywhere it goes.
RFC 8981 states the problem: "Anytime a fixed identifier is used in multiple contexts, it becomes possible to correlate seemingly unrelated activity using this identifier".
Take the machine to a café and the prefix changes while the second half does not, so anybody seeing traffic in both places knows it is the same machine.
There are two layers of fix, and this machine runs both at once.
The first is a stable address not derived from the MAC, generated by a pseudorandom function that takes the prefix as an input. It is constant within one network and changes when the machine moves. The line marked secured is this one.
The second is a temporary address that regenerates on its own. RFC 8981 gives a default preferred lifetime of one day. The line marked temporary is this one, and the machine confirms it is enabled.
$ sysctl net.inet6.ip6.use_tempaddr net.inet6.ip6.prefer_tempaddr
net.inet6.ip6.use_tempaddr: 1
net.inet6.ip6.prefer_tempaddr: 1
prefer_tempaddr at 1 means outbound connections use the temporary address while the stable one is kept for other people to reach, which is exactly what the RFC describes: "Temporary addresses are typically employed for initiating outgoing sessions".
ICMPv6 cannot be switched off
People arriving from IPv4 carry the habit that ICMP is auxiliary, safe to block, and nothing breaks.
On IPv6 that habit makes the network unusable. RFC 4890 says it outright: ICMPv6 "cannot be treated as an auxiliary function with packets that can be dropped in most cases without damaging the functionality of the network".
The reason reads back off every previous section — finding a neighbour is ICMPv6, finding a router is ICMPv6, configuring an address is ICMPv6, and discovering how large a packet may be is ICMPv6.
RFC 4890 gives the list that must not be dropped.
1 Destination Unreachable
2 Packet Too Big
3 Time Exceeded code 0 only
4 Parameter Problem codes 1 and 2
128 Echo Request
129 Echo Reply
And it warns about each. On Packet Too Big it writes "Effectively, parts of the Internet will become inaccessible".
Note that Echo Request and Echo Reply are on the must-not-drop list too. Ping over IPv6 is not the optional toy it is over IPv4.
Living alongside IPv4
The two protocols cannot talk to each other at all. Different address sizes, different headers. An IPv6 packet has no way of reaching an IPv4-only machine without somebody translating.
Two approaches are used in practice.
Dual stack gives a machine both at once. DNS answers with both A and AAAA and the machine chooses. It is the simplest and the most expensive, because two networks and two sets of firewall rules have to be maintained forever.
NAT64 with DNS64 serves machines that have only IPv6. DNS64 synthesises an AAAA answer from an existing A answer, and NAT64 translates the actual packets at the boundary.
RFC 6146 defines its scope as allowing "IPv6-only clients to contact IPv4 servers", and the words to read carefully are clients and servers. This works in one direction. The IPv4 side cannot initiate without extra configuration.
The interesting observation is that NAT64 is another kind of NAT. The thing IPv6 set out to eliminate came back as the bridge that leads to IPv6.
Where it lies to you
"IPv6 is safer because you cannot scan it." Half true. Scanning a whole /64 really is impossible. But attackers do not scan — they read DNS, logs, certificates, and the traffic machines emit themselves. Being hard to scan is not security.
"With no NAT, internal machines are exposed." This confuses being routable with being reachable. A routable address does not mean everything is open. What keeps things out is a firewall, and in IPv4 that was already the firewall's job. NAT merely happened to block as a side effect of not knowing where to forward.
"Turning on IPv6 means turning off IPv4." No. Dual stack is the normal state of very nearly every network today, and will be for a long time.
"Several addresses on one card is a misconfiguration." No, it is the default, and trying to force it down to one is fighting the design.
"IPv6 works, because web pages load." That proves nothing. Modern browsers try both at once and use whichever answers first. If the IPv6 path is broken it silently falls back to IPv4. Pages load, and nobody learns anything is wrong.
Worked examples from real work
Case 1 — a successful handshake that hangs the moment data flows
Some sites load fine, others hang on a blank page, ping works, and the sites that hang are the same ones every time.
How to read it. The symptom is much narrower than it looks. Ping working means the path reaches. The handshake succeeding means small packets get through. Hanging on load means large packets do not.
Test by increasing the size.
$ ping6 -c 2 -s 1200 2001:db8::1
2 packets transmitted, 2 packets received
$ ping6 -c 2 -s 1400 2001:db8::1
2 packets transmitted, 0 packets received
1200 passes, 1400 does not, and no Packet Too Big comes back. Working correctly, an ICMPv6 type 2 would return carrying the size. The silence is the evidence that somebody is filtering.
The short-term fix is lowering the MTU on our end. The right fix is finding who filters and stopping them.
Not yet proved. We know Packet Too Big is not returning, but not where it is being dropped — our firewall, theirs, or something in between. Test segment by segment.
Case 2 — traffic leaving from an address we did not intend
A firewall rule at the far end permits one server's IPv6 address, and yet connections are refused intermittently.
How to read it. Look at how many addresses the machine has and which one it uses outbound.
$ ifconfig en0 | grep inet6
inet6 fe80::1a2b:3c4d:5e6f:7a8b%en0 prefixlen 64 secured scopeid 0xc
inet6 2001:db8::a41:2f77:81c3:5e02 prefixlen 64 autoconf secured
inet6 2001:db8::5d19:c8ae:3b40:9f61 prefixlen 64 autoconf temporary
$ sysctl -n net.inet6.ip6.prefer_tempaddr
1
The address written into the rule is probably the secured one, while the address actually used outbound is the temporary one, which replaces itself daily. The rule therefore works sometimes and fails others, in step with the address changing.
This is the difference from IPv4 that requires rethinking: the outbound address and the inbound address need not be the same one, and by default they are not.
Not yet proved. We can see the machine prefers temporary addresses, but have not confirmed the refused packets carried one. Capture the traffic and read the actual source of a refused packet.
Case 3 — the default route points at an address that looks wrong
The IPv6 routing table shows a gateway of fe80::1, which does not look like a router's address.
$ netstat -rn -f inet6 | head -4
Destination Gateway Flags Netif
default fe80::1%en0 UGcg en0
default fe80::%utun0 UGcIg utun0
default fe80::%utun1 UGcIg utun1
How to read it. This is correct, not a fault. Gateways on an IPv6 network normally are link-local addresses, because that is what a router announces itself with in its advertisements, and it always exists regardless of which prefix is configured.
Notice that several default lines begin with the same fe80:: and differ only after the %. That is what zone indices are for. Without them there would be no way to say which wire's fe80::1 is meant.
Not yet proved. The table says the route exists, not that it works. Confirm by pinging the gateway with its zone suffix attached.
Case 4 — IPv6 broken for months with nobody noticing
Everything works, nobody complains, and then somebody opens a graph and finds IPv6 traffic has been zero the whole time.
How to read it. This is the side effect of a mechanism designed so users notice nothing. A browser tries IPv6 and IPv4 together and uses whichever answers first; when IPv6 does not answer, it falls back within a fraction of a second.
Testing requires forcing IPv6 only, which removes the fallback.
$ curl -6 -sS -o /dev/null -w '%{http_code}\n' https://example.com
curl: (7) Failed to connect: Network is unreachable
$ curl -4 -sS -o /dev/null -w '%{http_code}\n' https://example.com
200
The first line is the answer, and the reason nobody found it earlier is that nobody ever forced a single path.
The wider lesson is that a mechanism good at hiding failure will hide it for a very long time. Running IPv6 means having a check that forces IPv6 only, not a check that a web page loads.
Not yet proved. We know outbound fails, but not at which layer. Work from near to far: ping the link-local gateway first, then move outward one hop at a time.
References
The core standards
- RFC 8200 — Internet Protocol, Version 6 (IPv6) Specification July 2017, replacing RFC 2460 — the 40-byte header, the rule that routers must not fragment, the 1280 MTU floor, and the requirement that UDP checksums cannot be switched off
- RFC 4291 — IP Version 6 Addressing Architecture February 2006 — the address type table, the once-only
::rule, the sentence saying there is no broadcast, and the solicited-node formula - RFC 5952 — A Recommendation for IPv6 Address Text Representation August 2010 — the rules that give one address exactly one written form
Neighbour discovery and self-configuration
- RFC 4861 — Neighbor Discovery for IP version 6 September 2007 — all five message types, the 2^24 groups figure, and the hop limit 255 trick
- RFC 4862 — IPv6 Stateless Address Autoconfiguration September 2007 — the four steps of SLAAC and the rules for DAD
- RFC 8981 — Temporary Address Extensions for SLAAC February 2021, replacing RFC 4941 — temporary addresses and the privacy reasoning behind them
Packet size and ICMPv6
- RFC 8201 — Path MTU Discovery for IP version 6 July 2017, replacing RFC 1981, and the warning about a handshake that succeeds before the connection hangs
- RFC 4890 — Recommendations for Filtering ICMPv6 Messages in Firewalls May 2007 — the must-not-drop list, and what breaks for each one dropped
Coexistence
- RFC 6146 — Stateful NAT64 April 2011 — the scope of translation, and the limit that the IPv4 side cannot initiate
What is on the machine
ifconfig en0 | grep inet6— four addresses on one card, with thesecured,temporaryanddynamicflags saying where each came fromnetstat -rn -f inet6— a default route pointing at a link-local address, and several lines separated only by their zone indexsysctl net.inet6.ip6.use_tempaddrand.prefer_tempaddr, confirming the machine uses temporary addresses for outbound traffic- Computing the solicited-node group of all four addresses by RFC 4291's formula and comparing against
netstat -g -f inet6shows the machine has joined all four
Related reading here
- Internet Protocol — the version this page replaces
- NAT — the temporary measure that lasted thirty years, and the sentence that named this page as the long-term answer
- Subnetting — dividing, whose meaning changes once /64 is the only unit in use
- TCP and UDP — the layer above, whose checksum had to change because the header did