Skip to content

feat(bpf): implement allow_proto L3 protocol gatekeeper#53

Open
leodido wants to merge 7 commits intofeat/ethertype-vlan-namesfrom
feat/allow-proto
Open

feat(bpf): implement allow_proto L3 protocol gatekeeper#53
leodido wants to merge 7 commits intofeat/ethertype-vlan-namesfrom
feat/allow-proto

Conversation

@leodido
Copy link
Copy Markdown
Owner

@leodido leodido commented May 4, 2026

L3 IP protocol allowlist program. Drops IPv4 packets whose IP protocol
is not in the allowed set. Non-IPv4 traffic passes through (L2 filtering
is allow_ethertype's job). Accepts a +-delimited list of symbolic
names (tcp, udp, icmp, sctp) or decimal numbers.

Second multi-value input program, following the pattern established by
allow_ethertype in PR #51.

Changes

  1. BPF program (bpf/allow_proto.bpf.c): reads ip_header->protocol,
    linear scans allowed[MAX_MULTI_VALUES] array. Match: tail-call next.
    No match: TC_ACT_SHOT. Non-IPv4 passthrough (TC_ACT_OK).
    Fail-closed on truncated headers and invalid IHL.

  2. Parser wiring: case program_allow_proto in parse_input()
    calling the existing parse_protos() helper. Added to
    program_requires_input(). Added sctp (132) to g_proto_names[].
    input_fields entry in api.lua:
    allow_proto = { field = "protos", multi = true }.

  3. Chain loader: case program_allow_proto in
    load_chain_program(). Passes the protos struct (values + count)
    to set_chain_rodata(). Added to program_supports_chaining().

  4. Tests: 11 flag validation tests (missing input, unknown name,
    invalid decimal, out of range, duplicates, delimiter edge cases,
    too many values, sctp cross-repr). 4 integration tests (standalone
    allow, standalone block, decimal input, chain with allow_ethertype).
    1 CNI test with fixture.

  5. Docs: README.txt and docs/README.md with chain ordering notes.

Design decisions

  • Non-IPv4 passthrough: consistent with allow_ipv4, allow_port,
    allow_dns. The chain ordering bypass where L3+ passthrough
    short-circuits downstream L2 filters is a chain-model issue, not
    specific to this program.
  • No fragment handling: the protocol field is at byte 9 in the fixed
    IP header, always readable regardless of fragmentation.
  • Recommended chain position: after allow_ethertype, before
    allow_port (L2 → L3 → L4).

Stacked on

leodido and others added 5 commits May 4, 2026 22:36
L3 IP protocol allowlist program. Drops IPv4 packets whose IP protocol
is not in the allowed set. Non-IPv4 traffic passes through (L2
filtering is allow_ethertype's job).

Rodata layout: __u8 allowed[MAX_MULTI_VALUES] + __u8 num_allowed +
__u32 slot. Same pattern as allow_ethertype but with __u8 values
for IP protocol numbers.

No fragment handling needed — the protocol field is in the fixed
20-byte IP header, always readable regardless of fragmentation.

Co-authored-by: Ona <no-reply@ona.com>
Add case program_allow_proto in parse_input() calling the existing
parse_protos() helper. Add to program_requires_input(). Add sctp (132)
to g_proto_names[] as the 4th symbolic protocol name.

Add input_fields entry in api.lua:
allow_proto = { field = "protos", multi = true }.

Co-authored-by: Ona <no-reply@ona.com>
Add case program_allow_proto in load_chain_program(). Passes the
protos struct (values[] + count) to set_chain_rodata(). Add to
program_supports_chaining().

Co-authored-by: Ona <no-reply@ona.com>
Flag tests (11): missing input, unknown name, invalid decimal, out of
range (256), duplicate, cross-representation duplicate (tcp+6),
trailing/leading +, consecutive ++, too many values, sctp+132 dup.

Integration tests (4): standalone tcp+udp+icmp allows ping, standalone
tcp+udp blocks ping (ICMP not in set), decimal input 6+17+1, chain
allow_ethertype+allow_proto blocks ICMP at L3.

CNI test (1): allow_proto with tcp+udp+icmp via CNI fixture.

Fix missing trailing newline in cni.bats.

Co-authored-by: Ona <no-reply@ona.com>
Co-authored-by: Ona <no-reply@ona.com>
@leodido leodido self-assigned this May 4, 2026
leodido and others added 2 commits May 4, 2026 22:59
…0 test

Add missing #include "allow_proto.skel.h" in chain.h. Without it,
the chain loader compiles only because of accidental include ordering
in traffico.c.

Split parse_protos error into two distinct messages: "unknown protocol
name (use a number 0-255)" for non-numeric tokens and "protocol number
out of range (0-255)" for values > 255. Previously both cases used
the ambiguous "invalid protocol number".

Add test verifying protocol 0 (HOPOPT) is accepted by the parser,
documenting the intentional difference from EtherType 0x0000 which
is rejected.

Co-authored-by: Ona <no-reply@ona.com>
Reject whitespace in multi-value input tokens. Both parse_ethertypes
and parse_protos now call token_has_whitespace() before processing
each token. Prevents strtoul from silently accepting inputs like
"tcp+ 6" via CNI JSON where the CLI argument parser would not
split on spaces.

Remove stale "(future)" annotations from ethertypes and protos
union fields in api.h.in and chain.h — both programs are implemented.

Add chain integration test verifying TCP traffic passes through
allow_ethertype:ipv4+arp,allow_proto:tcp+udp (allow path). The
existing chain test only covered the block path (ICMP dropped).

Co-authored-by: Ona <no-reply@ona.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant