Networking · IP and Routing

IPv6 Forwarding, Extension Header Chains, and Scoped Addresses

How a fixed forty-byte header pushes options into a chain a router must walk, why scopes are part of the address, and which rules a forwarder applies differently than in IPv4.

IPv6 keeps IPv4’s forwarding model. Packets are still routed on destination address alone, the table is still a set of prefixes searched by longest match, and the next hop is still either a gateway or a directly attached neighbour. What changes is the encoding: a fixed header with no options, a chain of extension headers that a forwarder may have to walk, addresses whose meaning depends on a scope, and a set of forwarding rules that remove work from routers and place it on endpoints.

Forty bytes, and nothing optional inside

The fixed header is forty bytes: version, traffic class, flow label, payload length, next header, hop limit, and two 128-bit addresses. There is no header length field, because the length is constant. There is no checksum, because the transport layers and the link layer already provide integrity checks, and the designers judged the per-hop recomputation to be unnecessary work.

The removal of the checksum is the change with the widest consequences. A router no longer needs to validate or repair anything in the header, so the forwarding path is a read of the destination address, a table lookup, a decrement of the hop limit, and a rewrite of the link-layer addresses. Nothing in the packet is recomputed. That is what makes it possible to forward without buffering the whole packet, and it also means a corrupted header can be forwarded, with the error detected only at the endpoints or by a link-layer check.

The absence of a header length field is compensated by the next header field, which names what follows. A value of 6 means TCP, 17 means UDP, 58 means ICMPv6, and 43, 44, 60, and others mean that another extension header follows. The chain is a linked list: each extension header’s own next header field names its successor, and the final one names the transport protocol. The header is therefore self-describing but not fixed-length, and a router that must inspect beyond the fixed part has to walk the chain.

What a router must walk, and what it may skip

Most extension headers are for the destination only. A Hop-by-Hop Options header must be examined by every router, and it is the only header with that property; RFC 8200 requires it to appear first in the chain, immediately after the fixed header. A Routing header lists intermediate destinations and must be processed by the routers named in it. A Destination Options header is for the final destination. A Fragment header is for the destination. An Authentication Header, from IPsec, is processed by whatever the security association says.

The forwarding path therefore has a defined amount of work: read the fixed header, check whether the next header is Hop-by-Hop, and if it is, process it and continue. Any other extension header is skipped by advancing past it using its length field, without interpretation, until either the transport header or a header the router must process is reached. Skipping is not free, because each step is a dependent memory read, but it is bounded by the number of headers present.

The design intent is that a router which sees no Hop-by-Hop header does the minimum. The intent is undermined by the fact that the chain can be arbitrarily long within the payload length limit, so an attacker can force a router to walk hundreds of headers before deciding the packet is malformed. Implementations bound the number of headers they will traverse and drop packets that exceed the bound, which is a policy the specification does not state.

/* Walk the extension chain to find the upper-layer protocol. */
static int upper_layer(uint8_t *pkt, uint8_t *next_header, uint8_t **payload)
{
    uint8_t nh = *next_header;
    uint8_t *p = pkt + 40;

    for (int steps = 0; steps < 8; steps++) {
        if (nh == 6 || nh == 17 || nh == 58) {
            *payload = p;
            return nh;
        }
        if (nh == 59)                       /* No Next Header */
            return 59;
        if (nh == 51) {                     /* AH: length in 4-octet units, less 2 */
            nh = p[0];
            p += ((size_t)p[1] + 2) * 4;
            continue;
        }
        if (nh != 0 && nh != 43 && nh != 44 && nh != 60)
            return -1;                      /* cannot be walked without decoding */
        uint8_t cur = nh;                   /* header we are standing on */
        nh = p[0];                          /* next header in this one */
        if (cur == 44)
            p += 8;                         /* Fragment header is fixed length */
        else
            p += ((size_t)p[1] + 1) * 8;    /* others: 8-octet units */
    }
    return -1;                              /* too many headers */
}

The Fragment header is a special case in that walk: its length is fixed at eight bytes and its length field is reserved rather than used for sizing, so a generic length computation applied to it would advance the wrong distance.

Fragmentation moved to the source

IPv6 routers do not fragment. A packet that exceeds the outgoing MTU is discarded and ICMPv6 Packet Too Big is returned with the MTU of the next hop. Only the source may fragment, and it does so by inserting a Fragment header and splitting the payload itself, which means the sender needs to know the path MTU or accept that its packets will be rejected.

The 1280-byte floor is what keeps that rule from making IPv6 unusable. A link that cannot carry a 1280-byte packet in one piece must provide fragmentation below IPv6, at the link layer, and a link whose MTU is configurable must be set to at least that size. The effect is that a conforming path never obliges a router to split a packet, which is fortunate, because splitting is precisely what an IPv6 router is forbidden to do. The sender’s obligation is the mirror image: it either keeps its packets within the floor or learns a larger path MTU, and an application that limits itself to 1280 bytes can ignore the problem entirely.

Reassembly at the destination uses the Fragment header’s thirty-two-bit identification field, which is wider than IPv4’s sixteen. The width is affordable because the field only exists when fragmentation does: the fixed header carries no identification at all, so the cost is paid per fragment rather than per packet. The key for reassembly is the source address, the destination address, and the identification together, which is why a thirty-two-bit value suffices where IPv4’s sixteen-bit field, combined with the same address pair, was already considered adequate.

Scopes are part of the address

IPv6 addresses carry a scope, which is the region of the network in which the address is meaningful. A link-local address, formed from the fe80::/10 prefix, is valid only on one link. A unique local address, from fc00::/7, is valid within a site. A global address is valid everywhere. The scope is encoded in the prefix, so a router can determine it from the address alone.

The scope constrains forwarding in ways that have no IPv4 equivalent. A packet with a link-local destination is never forwarded; a router that receives one for an address not its own discards it and returns ICMPv6 Destination Unreachable. A packet with a link-local source is likewise not forwarded, because the return path cannot exist. This is why link-local addresses are used for neighbour discovery and for routing protocol adjacencies, where both endpoints are on the same link by construction.

The scope also makes the address ambiguous in a way that surfaces in APIs. A link-local address must be paired with an interface index to be usable, because fe80::1 on one interface is a different host from fe80::1 on another. RFC 4007 defines the zone index notation, appending a percent sign and the interface, and applications that accept an address without a zone cannot route to a link-local destination unambiguously. The rule is enforced at the socket API rather than in the packet, which is why it appears as a programming problem rather than a protocol one. The same interface index is what the neighbour cache uses as part of its key, as on-link determination and next-hop resolution describes for the resolution step.

Multicast addresses add a scope field of their own: the low four bits of the second byte encode whether the group is interface-local, link-local, site-local, organization-local, or global. A solicited-node multicast address is link-local by construction, which is what keeps neighbour discovery from leaking beyond the link.

Rules that remove router work

Three differences in the forwarding rules exist in IPv6 that have no IPv4 counterpart, and all of them shift work to the endpoints.

The first is that a router never forwards a packet whose source or destination is a link-local address. Link-local addresses are meaningful only on the link where they were formed, so a packet carrying one cannot have a return path if it escapes, and the check is a prefix comparison that costs almost nothing. Because the rule is applied at the forwarding step rather than at configuration, a stray route for fe80::/10 cannot leak such traffic off the link: the entry may exist, and the packet is dropped anyway.

The second is that a router forwards to a multicast group only when multicast routing is configured for that group. Without such configuration the packet is discarded, because a group address names a set of receivers and an ordinary unicast entry has no next hop that could describe it.

The third is the absence of broadcast entirely. There is no address that reaches every host on a link, so mechanisms that relied on broadcast in IPv4 use multicast groups with a defined membership. Neighbour discovery is the main example: a solicitation goes to a group derived from the target’s address, and only hosts whose address matches that derivation are members. The result is that a host does not process every neighbour solicitation on the link, which lowers the work each idle host does on a busy segment.

Router advertisements and the default route

In IPv4 a host’s default route is configured or learned from a routing protocol. In IPv6 it is learned from Router Advertisement messages, which routers send periodically to the all-nodes multicast group and in response to a solicitation. The advertisement carries the router’s link-local address, its lifetime, and prefix information for the link.

The lifetime is what makes the default route dynamic. A host keeps the router as a default next hop for the advertised lifetime, refreshing it on each advertisement. A router that stops advertising is removed after the lifetime expires, without any explicit withdrawal, which means a failed router disappears from hosts’ tables after a bounded delay rather than never. The trade is that the delay is set by the advertised lifetime rather than by any detection of the failure, so a host keeps sending to a dead router until that lifetime runs out.

Multiple routers on a link each advertise, and a host keeps a list of them rather than one default route. RFC 4861 specifies the choice: a router whose reachability is known or probable is preferred over one whose state is incomplete, and when none is known reachable the host cycles through the list in round-robin order, which has the side effect of probing each of them in turn. What the original specification did not define is a cost ranking, so two routers on a link that are both reachable are otherwise interchangeable. RFC 4191 added a two-bit preference field to the advertisement for the case where they are not, with the middle value as the default so that an unmodified router is treated as neither preferred nor deprecated. Anything finer belongs to the interior protocol running over the link, whose comparison is described in route preference and the RIB.

References

  1. RFC 8200: Internet Protocol, Version 6 (IPv6) Specification — IETF
  2. RFC 4291: IP Version 6 Addressing Architecture — IETF
  3. RFC 4861: Neighbor Discovery for IP version 6 (IPv6) — IETF
  4. RFC 4007: IPv6 Scoped Address Architecture — IETF
  5. RFC 4191: Default Router Preferences and More-Specific Routes — IETF