NetKubeLab ไทย

IPv6 — not IPv4 with longer addresses

A header twice the size that makes routers do less work, the four things that were deleted, why broadcast disappeared entirely, how a machine finds its own address, and why ICMPv6 cannot be switched off

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

The number of IPv4 addresses against IPv6, differing by a factor of two to the ninety-sixth, too much to draw to scale

  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

The same address written three ways: in full, with leading zeros dropped, and with the longest run of zeros collapsed

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 a through f "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 twenty-byte IPv4 header against the forty-byte IPv6 header at the same scale, with red boxes marking the fields that were deleted

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

The source sends 1500 bytes, a router with a 1400 MTU discards it and returns ICMPv6 Packet Too Big, and the source resends at 1400

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

The address type table from unspecified through multicast, with no broadcast in the list at all

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

ARP broadcasting to wake every machine on the wire, against NDP's solicited-node multicast that reaches only machines whose last 24 bits match

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

The SLAAC sequence: form a link-local address, run DAD, send a router solicitation, receive an advertisement, and build the global 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

Dual stack with both protocols on one machine, and NAT64 with DNS64 for machines that have only IPv6

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

Neighbour discovery and self-configuration

Packet size and ICMPv6

Coexistence

What is on the machine

  • ifconfig en0 | grep inet6 — four addresses on one card, with the secured, temporary and dynamic flags saying where each came from
  • netstat -rn -f inet6 — a default route pointing at a link-local address, and several lines separated only by their zone index
  • sysctl net.inet6.ip6.use_tempaddr and .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 inet6 shows 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

อ่านหน้านี้เป็นภาษาไทย

← Back to the basics