net/packet: clarify minFragBlks reuse for IPv6 and test chained ext header

Follow-up cleanups to the IPv6 fragment extension header support added in
the previous commit:

- Document that minFragBlks is sized for IPv4 but intentionally reused by
  decode6 for IPv6 fragments, where it is conservative (IPv6 fragments
  carry no per-fragment IP header) and only ever rejects more later
  fragments as Unknown, never fewer.

- Add a TestDecode case for a first fragment reachable only through a
  chained extension header (base Next Header = Hop-by-Hop Options, which
  chains to Fragment). decode6 only parses the Fragment header when it is
  the base header's immediate Next Header, so this must classify as
  Unknown. The test locks in that scoping decision.

Updates #20083
Updates #20140

Change-Id: Ibece03c6baf2385b0cc399f179819b08cbe921cc
Signed-off-by: James Tucker <james@tailscale.com>
This commit is contained in:
James Tucker
2026-06-16 10:16:06 -07:00
committed by James Tucker
parent ca20611d11
commit 26b2ed0a6a
2 changed files with 40 additions and 0 deletions
+5
View File
@@ -17,6 +17,11 @@ import (
const unknown = ipproto.Unknown
// RFC1858: prevent overlapping fragment attacks.
//
// The bound is sized for IPv4 (max IPv4 header + basic TCP header) but is
// intentionally reused by decode6 for IPv6 fragments. It is conservative for
// IPv6, whose fragments carry no per-fragment IP header, so it only ever
// rejects more later fragments as Unknown, never fewer.
const minFragBlks = (60 + 20) / 8 // max IPv4 header + basic TCP header in fragment blocks (8 bytes each)
// ip6FragHeader is the IANA protocol number for the IPv6 Fragment extension
+35
View File
@@ -357,6 +357,40 @@ var udp6SmallOffsetFragmentDecode = Parsed{
Dst: mustIPPort("[2607:f8b0:400a:809::200e]:0"),
}
// A first fragment reachable only through a chained extension header: the base
// header's Next Header is Hop-by-Hop Options (0), which then chains to the
// Fragment header. decode6 only parses the Fragment header when it is the
// base header's immediate Next Header, so it must classify this as Unknown
// rather than walking the chain. This locks in that scoping decision so a
// future change can't silently start (mis)parsing chained headers.
var udp6ChainedFragmentBuffer = []byte{
// IPv6 header. Next header = 0 (Hop-by-Hop Options), payload len = 24.
0x60, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x40,
// Src addr
0x20, 0x01, 0x05, 0x59, 0xbc, 0x13, 0x54, 0x00, 0x17, 0x49, 0x46, 0x28, 0x39, 0x34, 0x0e, 0x1b,
// Dst addr
0x26, 0x07, 0xf8, 0xb0, 0x40, 0x0a, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0e,
// Hop-by-Hop Options header (8 bytes): NextHeader=44 (Fragment),
// HdrExtLen=0, followed by PadN option padding.
0x2c, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00,
// Fragment extension header: NextHeader=UDP, Reserved, Offset=0 + M=1, Identification.
0x11, 0x00, 0x00, 0x01, 0xde, 0xad, 0xbe, 0xef,
// UDP header (unreachable: decoder stops at the unhandled Hop-by-Hop header).
0xd4, 0x04, 0x01, 0xbb, 0x00, 0x18, 0x00, 0x00,
}
var udp6ChainedFragmentDecode = Parsed{
b: udp6ChainedFragmentBuffer,
subofs: 40, // base header only; the Hop-by-Hop header is not parsed
dataofs: 0,
length: len(udp6ChainedFragmentBuffer),
IPVersion: 6,
IPProto: Unknown,
Src: mustIPPort("[2001:559:bc13:5400:1749:4628:3934:e1b]:0"),
Dst: mustIPPort("[2607:f8b0:400a:809::200e]:0"),
}
var udp4ReplyBuffer = []byte{
// IP header up to checksum
0x45, 0x00, 0x00, 0x29, 0x21, 0x52, 0x00, 0x00, 0x40, 0x11, 0x49, 0x5f,
@@ -696,6 +730,7 @@ func TestDecode(t *testing.T) {
{"ipv6_frag_nonfirst", udp6NonFirstFragmentBuffer, udp6NonFirstFragmentDecode},
{"ipv6_frag_short_first", udp6ShortFirstFragmentBuffer, udp6ShortFirstFragmentDecode},
{"ipv6_frag_small_offset", udp6SmallOffsetFragmentBuffer, udp6SmallOffsetFragmentDecode},
{"ipv6_frag_chained", udp6ChainedFragmentBuffer, udp6ChainedFragmentDecode},
{"ipv4_fragtooshort", tcp4ShortFragmentBuffer, tcp4ShortFragmentDecode},
{"ipv4_short_first_fragment", ipv4ShortFirstFragmentBuffer, ipv4ShortFirstFragmentDecode},
{"ipv4_small_offset_fragment", ipv4SmallOffsetFragmentBuffer, ipv4SmallOffsetFragmentDecode},