IP Fragmentation and Reassembly

IP fragmentation is how the Internet Protocol carries a datagram larger than a link’s Maximum Transmission Unit (MTU) — the largest payload a link-layer frame can hold (1500 bytes on classic Ethernet). The sending IP layer (or, in IPv4, any forwarding router) splits the datagram into fragments that each fit the MTU, and the destination host reassembles them back into the original datagram before delivering it up to TCP/UDP. In Linux 6.12 the transmit side is ip_fragment/ip_do_fragment (net/ipv4/ip_output.c) and the receive side is ip_defrag, built on a per-flow fragment queue (struct ipq) tracked in net/ipv4/ip_fragment.c, with a reassembly timeout and a memory cap (ipfrag_high_thresh). Fragmentation is correct but widely considered harmfulRFC 8900, “IP Fragmentation Considered Fragile” catalogs why — because it amplifies loss, breaks stateless middleboxes, and opens a family of attacks (overlapping fragments, resource exhaustion such as FragmentSmack, CVE-2018-5391). IPv6 (RFC 8200) responds by forbidding routers to fragment at all: only the source may fragment, using a Fragment extension header. This note owns the mechanism; The IP Layer owns where the dispatch into and out of it sits.


Mental Model

Think of a datagram as a long document and the MTU as the size of the envelopes you have. Fragmentation is photocopying the document onto MTU-sized pages, stamping each page with “this is bytes 0–1479 of document #4217, more pages follow,” and mailing each in its own envelope. Reassembly is the receiver collecting all pages bearing document #4217, sorting them by their byte offset, checking that the run from byte 0 to the last page is gap-free, and stapling them back into the original document — but only waiting a bounded time and a bounded amount of desk space before giving up.

The defining asymmetry to internalize: fragmentation is cheap and distributed (any sender or IPv4 router can do it independently), but reassembly is expensive and centralized (only the final destination does it, and it must hold per-flow state). That asymmetry is the root of every problem in this note — losing one fragment wastes all the others, and an attacker can force the destination to hold half-finished reassembly state cheaply.

flowchart TB
  subgraph TX["Transmit (IPv4: source OR router; IPv6: source only)"]
    BIG["Datagram > MTU"] --> DEC{"DF set?"}
    DEC -->|"DF=1"| ICMP["drop +<br/>ICMP Frag Needed / Pkt Too Big<br/>(drives PMTUD)"]
    DEC -->|"DF=0 (IPv4 only)"| FRAG["ip_fragment -> ip_do_fragment:<br/>copy into MTU-sized skbs,<br/>set offset + MF flag, same ID"]
    FRAG --> WIRE["fragments on the wire"]
  end
  subgraph RX["Reassemble (destination host only)"]
    WIRE --> DEFRAG["ip_defrag: hash (src,dst,id,proto)<br/>-> find/create ipq queue"]
    DEFRAG --> INS["insert into rbtree by offset<br/>REJECT overlaps, check memory"]
    INS --> DONE{"first..last<br/>gap-free?"}
    DONE -->|"yes"| REASM["ip_frag_reasm:<br/>splice -> original datagram"]
    DONE -->|"no, timer fires"| TMO["ip_expire (30s v4 / 60s v6):<br/>drop queue, ICMP Time Exceeded"]
  end

The fragmentation/reassembly lifecycle in Linux 6.12. What it shows: the transmit side branches on the Don’t-Fragment (DF) bit — DF set turns “too big” into an ICMP error that feeds Path MTU Discovery, DF clear lets IPv4 actually fragment; the receive side keys fragments into a per-flow queue, rejects overlaps, and either completes or times out. The insight to take: the two sides are radically unequal in cost. The sender just copies bytes into smaller buffers; the receiver must allocate a queue, hold every fragment in memory, defend a memory budget and a timer, and reject malicious overlaps — which is exactly why fragmentation-based denial-of-service attacks target the receiver.


The Fragmentation Control Fields

IPv4 packs fragmentation control into 32 bits split across two header fields (RFC 791; constants from include/net/ip.h):

  • Identification (16 bits) — a value the sender stamps on every fragment of one datagram so the receiver can group them. All fragments of datagram N share the same ID; the receiver’s queue is keyed in part on it.
  • The frag_off 16-bit field combines three 1-bit flags and the offset. Walked bit by bit (most-significant first), with the kernel’s masks:
    • Bit 15 — Reserved, must be zero.
    • Bit 14 — DF (Don’t Fragment), IP_DF = 0x4000. If set, a node that would have to fragment must instead drop the packet and signal an error. This is the bit that enables Path MTU Discovery.
    • Bit 13 — MF (More Fragments), IP_MF = 0x2000. Set on every fragment except the last; the last fragment has MF=0, which is how the receiver knows it has the tail.
    • Bits 12–0 — Fragment Offset, IP_OFFSET = 0x1FFF. The position of this fragment’s payload within the original datagram, measured in 8-byte units. Thirteen bits times 8 bytes = 65,528, which (plus a final fragment up to the header length) covers the full 65,535-byte maximum datagram. The 8-byte granularity is why every fragment except the last must carry a payload that is a multiple of 8 bytes — the kernel enforces this by masking lengths with ~7 (len &= ~7).

The kernel’s ip_is_fragment(iph) test is simply (iph->frag_off & htons(IP_MF | IP_OFFSET)) != 0 — i.e., “MF is set, or the offset is nonzero.” A non-fragment has both clear.

IPv6 (RFC 8200) moves all of this out of the base header into a dedicated Fragment extension header (struct frag_hdr, Next Header value NEXTHDR_FRAGMENT = 44, include/net/ipv6.h):

struct frag_hdr {
        __u8    nexthdr;        /* the header type that follows the fragmentable part */
        __u8    reserved;
        __be16  frag_off;       /* 13-bit offset, 2 reserved bits, 1-bit M flag */
        __be32  identification; /* 32-bit, not 16 — vastly reduces ID collisions */
};

Two upgrades over IPv4 are visible here: the Identification is 32 bits (not 16), which makes the ID-wrapping problem RFC 8900 §4.6 describes far rarer; and there is no DF bit because in IPv6 routers never fragment regardless — DF is implicitly always on for forwarding.


Mechanical Walk-through — Fragmentation (TX)

IPv4: ip_fragment and ip_do_fragment

The output path reaches fragmentation from __ip_finish_output: if (skb->len > mtu || IPCB(skb)->frag_max_size) return ip_fragment(net, sk, skb, mtu, ip_finish_output2); (see The IP Layer for how it gets there). ip_fragment (ip_output.c:578) is a thin policy gate over ip_do_fragment:

static int ip_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
                       unsigned int mtu, int (*output)(...))
{
        struct iphdr *iph = ip_hdr(skb);
 
        if ((iph->frag_off & htons(IP_DF)) == 0)
                return ip_do_fragment(net, sk, skb, output);
 
        if (unlikely(!skb->ignore_df || ...)) {
                IP_INC_STATS(net, IPSTATS_MIB_FRAGFAILS);
                icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));
                kfree_skb(skb);
                return -EMSGSIZE;
        }
        return ip_do_fragment(net, sk, skb, output);
}

The gate is the DF bit: if DF is clear, fragment freely. If DF is set, the kernel normally refuses to fragment — it sends ICMP_DEST_UNREACH/ICMP_FRAG_NEEDED carrying the MTU and returns -EMSGSIZE. (The skb->ignore_df escape is for the rare local-socket case that explicitly opted out of PMTUD.) This ICMP message is what the remote sender’s PMTUD logic consumes to shrink its packets.

ip_do_fragment (ip_output.c:763) does the actual splitting and has two implementations chosen at runtime:

  • The fast path (fraglist). If the skb is already a chain of sub-skbs (skb_has_frag_list) whose geometry happens to fit the MTU — common when the data arrived as a GSO/GRO super-packet — the kernel reuses those existing buffers as fragments rather than copying. ip_fraglist_init and ip_fraglist_prepare just stamp each existing sub-skb with the right IPv4 header, offset, and MF flag. This is the zero-copy fragmentation path.
  • The slow path. Otherwise ip_frag_init sets up a struct ip_frag_state and a loop calls ip_frag_next to allocate a fresh skb per fragment and skb_copy_bits the right slice of payload into it. The per-fragment header construction in ip_frag_next is the canonical reference for “how a fragment is built”:
iph->frag_off = htons((state->offset >> 3));   /* offset in 8-byte units */
if (state->DF)
        iph->frag_off |= htons(IP_DF);
/* keep MF set on every fragment except the last */
if (state->left > 0 || state->not_last_frag)
        iph->frag_off |= htons(IP_MF);
iph->tot_len = htons(len + state->hlen);
ip_send_check(iph);                            /* recompute header checksum */

Each fragment gets the same Identification (copied from the original via ip_copy_metadata), an offset that advances by the data length each iteration, MF set on all but the last, and a freshly computed header checksum. Each finished fragment is handed straight to output (which is ip_finish_output2, the neighbour/driver stage) — they are emitted immediately, not buffered. Successful fragmentation bumps IPSTATS_MIB_FRAGOKS and IPSTATS_MIB_FRAGCREATES per fragment; failure bumps IPSTATS_MIB_FRAGFAILS.

IPv6: source-only fragmentation via ip6_fragment

ip6_fragment (ip6_output.c:863) is structurally similar (same fast/slow split), but with two crucial differences. First, it inserts a Fragment extension header carrying a 32-bit frag_id (ipv6_select_ident) — fragmentation is an extension-header operation, not a base-header field flip. Second, and decisively, the “too big” branch is unconditional:

/* We must not fragment if the socket is set to force MTU discovery
 * or if the skb is not generated by a local socket. */
if (unlikely(!skb->ignore_df && skb->len > mtu))
        goto fail_toobig;
...
fail_toobig:
        icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu);
        err = -EMSGSIZE;

A forwarding IPv6 router never calls ip6_fragment at allip6_forward sends ICMPV6_PKT_TOOBIG and drops, exactly like an IPv4 router facing a DF-set packet. ip6_fragment runs only on the output path of a packet this host originated, when the local source itself must fragment. The minimum link MTU of 1280 bytes (IPV6_MIN_MTU) guarantees a source can always fall back to packets that need no fragmentation below that floor.


Mechanical Walk-through — Reassembly (RX)

Reassembly is the expensive, stateful, attack-exposed half, and it lives in net/ipv4/ip_fragment.c (IPv4) and net/ipv6/reassembly.c (IPv6), both built on the shared net/ipv4/inet_fragment.c engine. The IP Layer dispatches into it: ip_local_deliver calls ip_defrag when ip_is_fragment() is true.

1. ip_defrag and the fragment queue (ipq)

ip_defrag (ip_fragment.c:484) hashes the fragment into a fragment queue keyed by frag_v4_compare_key:

struct frag_v4_compare_key {
        __be32  saddr;     /* source address   */
        __be32  daddr;     /* destination addr  */
        u32     user;      /* who is defragging: LOCAL_DELIVER, CONNTRACK_IN, ... */
        u32     vif;       /* l3mdev / VRF interface index */
        __be16  id;        /* IPv4 Identification field */
        u16     protocol;  /* IPPROTO_* */
};

The key is (source, destination, ID, protocol) plus the namespace/VRF context — this is exactly the tuple that uniquely identifies “fragments of the same original datagram.” inet_frag_find looks the queue up in a per-namespace resizable hash table (rhashtable), creating a new struct ipq if none exists. Each ipq embeds a struct inet_frag_queue holding the partial fragments (in an rbtree, see below), a length, a “meat” byte count of how much has arrived, flags (INET_FRAG_FIRST_IN/INET_FRAG_LAST_IN), and a timer.

2. ip_frag_queue — insert, validate, reject overlaps

ip_frag_queue (ip_fragment.c:275) computes this fragment’s offset (from frag_off & IP_OFFSET, shifted left 3 to get bytes) and end = offset + payload_len. It tracks the last fragment specially: a fragment with MF clear sets INET_FRAG_LAST_IN and fixes the total datagram length qp->q.len = end; an inconsistency (a later fragment claiming bytes beyond a known end, or two different “last” fragments) is treated as corruption and the whole queue is discarded (discard_qpIPSTATS_MIB_REASMFAILS).

The fragment is then inserted by inet_frag_queue_insert (inet_fragment.c:386) into an rbtree ordered by offset, and this is where the modern kernel’s security posture lives:

/* Detect and discard overlaps. */
if (offset < FRAG_CB(last)->ip_defrag_offset + last->len)
        return IPFRAG_OVERLAP;
...
else if (offset >= curr->offset && end <= curr_run_end)
        return IPFRAG_DUP;
else
        return IPFRAG_OVERLAP;

An IPFRAG_OVERLAP return causes ip_frag_queue to bump IPSTATS_MIB_REASM_OVERLAPS and kill the entire queue (discard_qp). Linux rejects any overlapping fragment outright — even for IPv4, where RFC 791 historically permitted later fragments to overwrite earlier bytes. This is a post-2018 behavior: the reassembler was rewritten in 2018 to use an offset-ordered rbtree (replacing the old linked list), precisely to make overlap detection cheap and to mitigate the FragmentSmack denial-of-service (CVE-2018-5391, see Failure Modes). The kernel comment cites RFC 5722 (which mandates discarding overlapping IPv6 datagrams) and applies the same rule to IPv4. A pure duplicate (IPFRAG_DUP, exact same range already present) is dropped but does not poison the queue.

If the inserted fragment completes the datagram — qp->q.flags == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) and qp->q.meat == qp->q.len, i.e. both ends seen and the byte count matches the length with no gaps — ip_frag_reasm runs.

3. ip_frag_reasm — splice into the original datagram

ip_frag_reasm (ip_fragment.c:412) kills the timer, splices the rbtree of fragments into one skb (inet_frag_reasm_prepare/inet_frag_reasm_finish chain them as page fragments — a near-zero-copy join), rejects a reassembled length over 65,535 (out_oversize), recomputes the IP header (tot_len, checksum, restoring the ECN bits from ip_frag_ecn_table), and returns the reassembled datagram for normal local delivery. Success bumps IPSTATS_MIB_REASMOKS.

4. The timeout and the memory cap — bounding the cost

Reassembly state cannot live forever, and this is where the two protective limits live (ip_fragment.c init):

  • Reassembly timeout. Each queue arms a timer for IP_FRAG_TIME = 30 * HZ30 seconds for IPv4 (net.ipv4.ipfrag_time). If the datagram is not completed in time, ip_expire (ip_fragment.c:133) fires: it drops the queue (IPSTATS_MIB_REASMTIMEOUT/REASMFAILS) and, per RFC 792, sends an ICMP_TIME_EXCEEDED/ICMP_EXC_FRAGTIME back to the source if the first fragment had arrived (so the source learns the offset-0 fragment, hence which datagram timed out). IPv6’s timeout is 60 seconds (IPV6_FRAG_TIMEOUT = 60 * HZ, include/net/ipv6.h) — not the same as IPv4, a real per-protocol difference.
  • Memory cap (ipfrag_high_thresh). All reassembly queues in a namespace share a memory budget. ipv4_frags_init_net sets high_thresh = 4 * 1024 * 1024 (4 MB) and low_thresh = 3 * 1024 * 1024 (3 MB); IPv6 uses the same IPV6_FRAG_HIGH_THRESH/LOW_THRESH = 4 MB/3 MB. When accounted memory exceeds the high threshold, inet_frag_find simply refuses to create new queues (if (... frag_mem_limit(fqdir) > high_thresh) return NULL;), and incomplete queues are pruned down toward the low threshold. The kernel’s own init comment does the arithmetic: a 64 KB datagram reassembled from 1500-byte fragments consumes ~129 KB of skb truesize, so 4 MB caps the system at roughly 32 simultaneous in-flight 64 KB reassemblies — deliberately small. A third knob, ipfrag_max_dist = 64, bounds how far “out of order” a later datagram’s fragments may be relative to earlier ones from the same peer, reinitializing the queue if a peer races too far ahead.

The user field in the queue key (IP_DEFRAG_LOCAL_DELIVER, IP_DEFRAG_CONNTRACK_IN, etc., include/net/ip.h) means reassembly can also be driven by connection tracking before the routing/NAT decision — conntrack defragments so it can read the L4 ports of a fragmented flow. That is the cross-link to Connection Tracking conntrack: a fragmented packet through a NAT box is reassembled, NAT’d, and (in IPv4) potentially re-fragmented on the way out.


Why Fragmentation Is Harmful

RFC 8900, “IP Fragmentation Considered Fragile” (2020) is the canonical statement of the case against relying on fragmentation. The harms, with their mechanisms:

  • Loss amplification (RFC 8900 §3). A datagram is reassembled all-or-nothing: lose one fragment and every other fragment of that datagram is wasted, the reassembly times out after 30 seconds, and the entire datagram must be retransmitted. A 1% per-fragment loss rate inflates dramatically for a datagram split into many fragments. Worse, RFC 8900 cites measurements (RFC 7872) that a substantial fraction of paths drop IPv6 fragments entirely — many middleboxes simply discard the IPv6 Fragment extension header — so fragmented traffic can fail outright on paths where unfragmented traffic succeeds.
  • Stateless-middlebox and NAT breakage (RFC 8900 §3). Only the first fragment carries the L4 header (TCP/UDP ports). A firewall, load balancer, or NAT making decisions on ports cannot see them in the non-first fragments, forcing it either to “virtually reassemble” (expensive, stateful, attack-exposed — the kernel’s IP_DEFRAG_CONNTRACK_IN path) or to guess. Equal-cost multipath routers that hash on the 4-tuple may also send fragments of one datagram down different paths.
  • Security — overlapping fragments and evasion. An attacker can craft fragments whose byte ranges overlap, so that a firewall/IDS reassembling one way sees benign content while the destination host reassembles a different way and sees an attack — the classic Ptacek-Newsham IDS-evasion and “teardrop”-family problem. The defense Linux now uses is to reject overlaps outright (see IPFRAG_OVERLAP above), the rule RFC 5722 mandates for IPv6 and that the kernel applies to IPv4 as well; this is the security payoff of the 2018 rbtree rewrite.
  • Security — resource-exhaustion DoS (FragmentSmack, CVE-2018-5391). Because the receiver must hold incomplete reassembly state, an attacker can send a low rate of incomplete fragment sets (or many tiny/overlapping fragments) that each open a queue but never complete, exhausting CPU and the reassembly memory budget. CVE-2018-5391, “FragmentSmack” (published 2018-09-06, CWE-400 Uncontrolled Resource Consumption), affected Linux 3.9+ after the reassembly queue limit had been raised; the fix combined lowering the defaults back toward 4 MB/3 MB and the rbtree rewrite that makes per-fragment insertion O(log n) instead of O(n), so a flood of fragments can no longer pin a CPU walking a long list.
  • IP ID wrapping at speed (RFC 8900 §4.6). The IPv4 Identification field is only 16 bits, so at high data rates a sender wraps the ID counter and two different datagrams to the same destination can share an ID — the receiver can then mis-staple fragments from different datagrams into a corrupt result that nonetheless passes the (header-only) checksum. IPv6’s 32-bit ID makes this far rarer.
  • PMTUD black-holing (RFC 8900 §3). The alternative to fragmentation — Path MTU Discovery — depends on ICMP “Fragmentation Needed”/“Packet Too Big” messages getting back to the sender. Operators who blanket-filter ICMP (a common but misguided “security” measure) break PMTUD silently: DF-set packets too large for some hop are dropped, no ICMP returns, and the connection black-holes with no error.

The RFC’s recommendation is blunt: upper layers should avoid relying on IP fragmentation. TCP does this by negotiating a Maximum Segment Size and using PMTUD/PLPMTUD so it never produces an IP datagram larger than the path MTU; UDP applications that send large datagrams (DNS over UDP being the notorious case) are advised to keep messages small or switch to TCP/QUIC. This is why, in practice, you rarely see IPv4 fragmentation from well-behaved TCP — the fragmentation code paths in this note exist mostly for UDP, tunneling/encapsulation, and forwarding of traffic from less careful senders.


Path MTU Discovery and the DF Bit

Path MTU Discovery (PMTUD) is the mechanism that lets a sender find the smallest MTU along the path and size its packets to it, avoiding fragmentation entirely. The sender sets the DF (Don’t Fragment) bit on its packets. If a router on the path has a smaller outgoing MTU, it cannot fragment a DF-set packet, so it drops it and returns ICMPICMP_FRAG_NEEDED (IPv4) or ICMPV6_PKT_TOOBIG (IPv6) — carrying the next-hop MTU. The sender caches that MTU (Linux stores it in the route/dst PMTU cache) and reduces its packet size. Linux defaults to PMTUD for TCP (net.ipv4.ip_no_pmtu_disc = 0), which is why almost all TCP traffic is DF-set and unfragmented. For IPv6 the entire model is PMTUD: since routers never fragment, a too-big packet always yields ICMPV6_PKT_TOOBIG, and the source either shrinks or fragments at the source with a Fragment extension header. The failure mode — ICMP filtering causing a black hole — is the §3 harm above; the modern robust answer is PLPMTUD (Packetization-Layer PMTUD, RFC 8899/4821), which probes for the MTU using the transport’s own packets and does not depend on ICMP arriving.


Failure Modes and How to Diagnose Them

Reassembly failures climbing. nstat//proc/net/snmp exposes the IP reassembly counters: ReasmReqds (fragments seen), ReasmOKs (datagrams completed), ReasmFails (timed out or discarded), and the newer ReasmOverlaps. A high ReasmFails with low ReasmOKs means fragments are arriving but not completing — packet loss on a fragmented path, a too-aggressive firewall dropping non-first fragments, or an attack. A nonzero ReasmOverlaps is a red flag for either a broken middlebox or a deliberate overlapping-fragment attack.

“Works for small transfers, hangs for large ones” — the PMTUD black hole. A connection that completes the handshake (small packets) but stalls as soon as it sends a full-size segment is the textbook PMTUD-black-hole signature: a smaller-MTU hop is dropping the DF-set full-size packets and the ICMP “too big” is being filtered. Confirm by lowering the interface MTU or ip route PMTU and watching it recover; fix by un-filtering ICMP type 3 code 4 (IPv4) / ICMPv6 type 2 (IPv6), or by enabling TCP MTU probing (net.ipv4.tcp_mtu_probing = 1, which turns on PLPMTUD).

Tunnels and double headers. Encapsulation (VXLAN, GRE, WireGuard, IPIP) adds outer-header bytes, shrinking the effective MTU for the inner payload. If the inner sender does not learn the reduced MTU, the tunnel endpoint must fragment — often the outer datagram — which compounds every harm above. The standard mitigation is MSS clamping (iptables ... --clamp-mss-to-pmtu, or nft equivalent) so TCP negotiates a segment size that fits inside the tunnel. See Tunnel and Overlay Interfaces.

Memory-cap drops under load. If ipfrag_high_thresh is exceeded, new fragment queues are silently refused (no new reassembly can start) until memory drains below ipfrag_low_thresh. On a busy reassembling host (a NAT box, a DNS resolver) this shows as ReasmFails with the high-water memory visible in cat /proc/sys/net/ipv4/ipfrag_high_thresh versus actual usage; the tuning lever is to raise the thresholds — at the cost of widening the DoS surface, which is the exact trade-off FragmentSmack exploited.


Production Notes

The strongest production lesson is the one RFC 8900 distills: design to avoid fragmentation. TCP-based services almost never fragment because MSS negotiation plus PMTUD keeps segments path-sized; the systems that do suffer fragmentation in practice are UDP-heavy — large DNS responses (DNSSEC pushed responses past 1500 bytes, which is part of why DNS-over-TCP and EDNS message-size limits matter), and overlay/tunnel fabrics in cloud and Kubernetes networking where an encapsulation header silently eats MTU. A very common Kubernetes/overlay incident is exactly the PMTUD black hole inside a VXLAN or IP-in-IP fabric: pods can do small request/response but large payloads hang, and the fix is MTU-aware CNI configuration or MSS clamping rather than blaming the application.

On the security side, the FragmentSmack episode is the cautionary tale: a performance tuning (raising the reassembly memory limit) reopened a denial-of-service surface that had been closed, and the durable fix was both algorithmic (the rbtree rewrite making overlap detection and insertion cheap) and a return to conservative memory defaults. The current Linux posture — reject all overlapping fragments, cap reassembly memory at a few megabytes, time queues out in 30/60 seconds — reflects that history. When hardening a fragment-reassembling host, the levers are ipfrag_high_thresh/ipfrag_low_thresh, ipfrag_time, and ipfrag_max_dist, and the conservative default is to leave them small and instead engineer the traffic to not fragment.

Uncertain

Verify: the precise mapping of RFC 8900 section numbers to each harm (loss §3.x, ID wrapping §4.6, PMTUD §3.x). The summary was extracted via a fetched read of the RFC and the section attributions are approximate. Reason: section numbering read from a fast-model summary, not transcribed line-by-line from the RFC text. To resolve: open RFC 8900 and confirm the exact subsection for each enumerated harm before relying on a specific §-citation. uncertain


See Also