Skip to content

lib: cgen: fix IPv6 fragment header size and short L4 header handling - #596

Merged
qdeslandes merged 3 commits into
facebook:mainfrom
prashantpcm94:ipv6-frag-l4-parse
Oct 12, 2026
Merged

qdeslandes merged 3 commits into
facebook:mainfrom
prashantpcm94:ipv6-frag-l4-parse

Conversation

@prashantpcm94

@prashantpcm94 prashantpcm94 commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The IPv6 Fragment header is always 8 bytes, and its second byte is reserved (RFC 8200, section 4.5). Both IPv6 extension header parsers added that byte to the header size, so a non-zero value moved the L4 header offset.

When the L4 header couldn't be read, for example because the packet ends inside it, the program returned ACCEPT before any rule ran. It now continues as it does for an unsupported L4 protocol, so the rules and the chain policy decide.

The first commit adds Hop-by-Hop Options and Fragment header layers to the test packet builder. The second adds a packet to each of the tcp.dport, ip6.nexthdr and ip6.saddr matcher tests. They fail until the third commit, which has the fix.

Related issue

None. The tests in the second commit show both cases on main (836eb74).

Testing

CMake release build (-DNO_DOCS=1) on CentOS Stream 9, kernel 6.16.1 (x86_64), gcc 11.5, clang 22.1.3, libbpf 1.6.3, then make -C $BUILD test_bin and ctest. The tests ran in private mount, network and cgroup namespaces with their own bpffs, in place of CI's privileged container. fuzzing.parser.infinite is not counted (it runs until stopped), and check.iwyu did not run (/usr/bin/iwyu_tool.py is missing here).

  • First commit: 127 of 128 pass (all but check.iwyu).
  • Second commit: 124 of 128 pass. e2e.matchers.tcp_dport, ip6_nexthdr and ip6_saddr fail on all 10 hooks, each at its new packet (DROP expected, ACCEPT returned).
  • Third commit: 127 of 128 pass (all but check.iwyu).
  • Each part of the fix is needed by one of the new packets: without the parse_ipv6_eh.bpf.c change tcp_dport fails, without the parse_ipv6_nh.bpf.c change ip6_nexthdr fails, and without the stub.c change ip6_saddr fails.
  • With the fix, bfcli --verbose bpf loads chains using both extension header parsers on XDP, TC, NF and cgroup_skb hooks, so the debug print in the new block also passes the verifier.

Notes for the reviewer

  • The second commit's tests fail on purpose until the third commit. That gets in the way of git bisect, so I'm happy to squash the last two or put the fix first.
  • Behaviour change: packets whose L4 header can't be read now get L4 protocol 0 and go through the rules and the chain policy, as unsupported L4 protocols already do. L4 matchers don't match them, and they are still counted in the error counter. This includes non-first fragments shorter than the L4 header, as bpfilter doesn't check the fragment offset.
  • The Fragment header in the tests follows a Hop-by-Hop Options header because a Fragment header right after the IPv6 header doesn't reach the extension header parsers today: _BF_LOW_EH_BITMASK in stub.c doesn't match the bits its comment lists. I left that for a separate PR.
  • Also left as is: L2, L3 and dynptr setup failures still return ACCEPT, and the unsupported-L4 path doesn't set bf_runtime.l4_hdr, so it could be set to NULL as on the new path.

AI disclosure

Claude Code drafted the tests, the packet builder change, the fix, the commit messages and this description under my direction, and ran the tests listed under Testing.
I reviewed the change and the test results.

Checklist

  • I understand every line in this PR and can explain why it is correct
  • I built the project and ran the tests covering this change
  • make -C $BUILD test_bin test passes and the code follows the style guide
  • Commits are formatted as component: subcomponent: short description
  • I have disclosed any AI usage above

@meta-cla meta-cla Bot added the cla signed label Oct 8, 2026
@prashantpcm94
prashantpcm94 marked this pull request as ready for review October 8, 2026 15:58
@prashantpcm94

Copy link
Copy Markdown
Contributor Author

@claude review

@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Claude review of PR #596 (01df0e8)

Both fixes are correct: the Fragment header size change matches RFC 8200 section 4.5, and the L4 dynptr slice failure path now correctly mirrors the "unsupported L4 protocol" state (r8=0, r0=NULL) instead of unconditionally accepting. BPF register usage is sound — r8 is callee-saved so the explicit zeroing after the kfunc calls is needed, and r0 must be explicitly set to 0 because EMIT_FIXUP_ELFSTUB/EMIT_PRINT clobber it. Downstream consumers are safe (bf_stub_rule_check_protocol gates L4 matchers on r8, pkt_log.bpf.c guards on l4_hdr != NULL). Tests cover both parser paths across all testable hooks, with good edge cases (reserved byte values, truncated packets). Style, documentation, and commit messages all follow project conventions.

Suggestions

  • Deduplicate kTestableHooks — tests/e2e/packets/l4_header.cpp:23 — kTestableHooks is an identical copy of the array in tests/e2e/verdicts/next.cpp; consider moving it into the shared test harness so future hook additions only need one update

Workflow run

Comment thread tests/e2e/packets/l4_header.cpp Outdated

/* All hooks that support BPF_PROG_TEST_RUN (excludes BF_FLAVOR_CGROUP_SOCK_ADDR). */
static constexpr enum bf_hook kTestableHooks[] = {
BF_HOOK_XDP,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude: suggestion: kTestableHooks is an identical copy of the array in tests/e2e/verdicts/next.cpp. Consider moving it into the shared test harness (e.g., tests/harness/test.hpp) so future hook additions only need one update.

@qdeslandes qdeslandes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks mostly good, except for a comment from Claude and one from me.

Comment thread tests/e2e/packets/l4_header.cpp Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test should use MatcherTestsSuite, see e2e/matchers/*.cpp for examples.

Regarding fake packet creation, create relevant classes in Packet.cpp/hpp to easy creation of packets with extension headers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@prashantpcm94
prashantpcm94 marked this pull request as draft October 9, 2026 17:57
@prashantpcm94
prashantpcm94 force-pushed the ipv6-frag-l4-parse branch 3 times, most recently from 79b1b13 to 9792dbd Compare October 9, 2026 19:26
@prashantpcm94
prashantpcm94 marked this pull request as ready for review October 9, 2026 19:39
The packet builder can't create packets with IPv6 extension headers,
which are needed to test the IPv6 extension header parsers from the
matcher tests.

Add Hop-by-Hop Options and Fragment header layers. They are inserted
between the IPv6 and transport layers, and the Next Header field of
each extension header is set to the protocol of the layer following it:

    Ethernet{} / IPv6{} / IPv6HopByHop{} / IPv6Fragment{} / TCP{}
Both IPv6 extension header parsers use the reserved second byte of the
Fragment header (RFC 8200, section 4.5) as a length, so a non-zero
value moves the L4 header offset. When the L4 header can't be read,
the program accepts the packet before any rule runs.

Add a packet with a non-zero Fragment reserved byte to the tcp.dport
test, which uses parse_ipv6_eh.bpf.c, and to the ip6.nexthdr test,
whose matcher makes bpfilter use parse_ipv6_nh.bpf.c. The Fragment
header follows a Hop-by-Hop Options header because the extension
header bitmask in bf_stub_parse_l3_hdr() doesn't include the Fragment
header, so one right after the IPv6 header isn't parsed.

Add a packet with a truncated TCP header to the ip6.saddr test: rules
that don't use the L4 header must still apply to it.

These tests fail until the next commit.
The Fragment header is always 8 bytes and its second byte is reserved
(RFC 8200, section 4.5), but both IPv6 extension header parsers added
it to the header size. If the L4 header can't be read, continue as for
an unsupported L4 protocol instead of accepting the packet before any
rule, so the rules and the chain policy still apply.
@qdeslandes
qdeslandes enabled auto-merge (rebase) October 12, 2026 16:28

@qdeslandes qdeslandes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, will be merged on CI passes after I rebased the branch. Thanks!

@qdeslandes
qdeslandes merged commit 60a61d8 into facebook:main Oct 12, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants