Skip to content

v3#94

Open
Outfluencer wants to merge 9 commits into
mainfrom
v2
Open

v3#94
Outfluencer wants to merge 9 commits into
mainfrom
v2

Conversation

@Outfluencer

Copy link
Copy Markdown
Owner

No description provided.

* harden packet validation: drop fragments, SYN payload, SYN+FIN/RST

- drop fragmented tcp packets: non-first fragments have no tcp header,
  so the port check ran on payload bytes and reassembled data could
  reach the backend without passing the inspection state machine
- drop SYNs carrying payload (TCP fast open style), the data would
  bypass payload inspection
- drop SYN+FIN and SYN+RST flag combinations in detect_tcp_bypass
- bump conntrack_map to 16384 entries to reduce LRU eviction of
  legitimate in-flight handshakes during floods

* unpin all maps and use proper prometheus counter semantics
The previous blanket drop of all fragmented TCP packets ran before the
port check (non-first fragments carry no TCP header), so fragmented
traffic for every other service on the same interface was dropped too.

Split the check in two:
- non-first fragments (offset != 0) are passed up the stack: their
  ports cannot be read, and they are harmless for the filtered range
  because reassembly can never complete once the first fragment is
  dropped; the kernel discards them after the frag timeout
- first fragments (MF set, offset 0) keep the TCP header, so the port
  check runs as usual and only fragments aimed at the filtered range
  are dropped

Protection for the filtered range is unchanged; fragmented packets can
never reach the backend uninspected. Other services on the same NIC now
receive their fragmented traffic again.
* Reorganize and clean up the entire codebase

eBPF side (c/):
- Split headers by topic: config.h (runtime config externs, removes the
  include-order dependency), varint.h (bounded VarInt reader), protocol.h
  (Minecraft packet limits + inspectors); minecraft_helper.h and
  minecraft_networking.h are gone
- Deduplicate the bounds-check macros (SKIP_OR_RETURN and
  READ_VAL_OR_RETURN now build on CHECK_BOUNDS_OR_RETURN) and merge
  VARINT_OR_DIE/MAX_VARINT_OR_DIE into one READ_VARINT_OR_RETURN
- Turn the connection-state defines into an enum, document the pseudo
  states, and unify inspector signatures (cursor, payload_end, data_end,
  ...) with const-correct payload cursors
- Hoist the duplicated flow-key computation, inline the single-use
  remove_connection helper, drop the redundant switch_to_verified label,
  and fix the four const-qualifier warnings
- Rewrite comments throughout and add section banners

Verified equivalent: the disassembly differs from the previous object
only by the deduplicated flow-key construction (8 fewer instructions);
the kernel verifier walks both objects to the same state counts.

Rust side (src/):
- Split the 439-line main.rs into focused modules: logging, shutdown,
  ebpf, metrics; main.rs is now just CLI + wiring
- Define the stats struct, per-cpu summing and Prometheus counters from
  a single field list (one macro) instead of five hand-maintained copies
- Replace the per-call-site dummy mutexes around the shared Condvar with
  one Shutdown coordinator (the old setup paired two mutexes with one
  condvar) and drop the unnecessary Arc<Mutex<>> around the stats map
- Replace lazy_static with std LazyLock and drop the dependency
- Remove the redundant RUST_LOG set_var, fix typos (epbf, programm)

Build:
- build.rs: emit rerun-if-changed for the eBPF sources so the object is
  only recompiled when they change
- README: document the project layout


* Add native unit tests for the eBPF parsing code

c/tests/protocol_test.c compiles the real headers (varint.h, protocol.h,
common.h) for the host and exercises them in userspace:
- VarInt reader: wiki.vg test vectors, encoder/decoder roundtrip,
  truncation, max-size limits, overlong encodings, and that parsing never
  runs past payload_end even when data_end leaves room
- Bounds-check macros: in/out of bounds, dual-bounds behavior, unaligned
  reads
- Packet inspectors: handshake (intentions incl. the 1.20.5 transfer
  intent, legacy ping, every truncated prefix, length/host limits,
  combined handshake+status and handshake+login segments with resume
  cursor), status request, ping request, and login across all protocol
  eras (pre-1.19, 1.19 key block, 1.19.1 uuid flag, 1.19.3, 1.20.2+)
  including the online/offline username rules

Every inspector call runs on an exact-size heap copy so ASan catches any
read past data_end; slack bytes behind payload_end are terminator-shaped
so a parser that ignores payload_end produces a detectable wrong result
(validated by mutation-testing the bounds checks).

The harness is compiled with ASan/UBSan and run by the cargo integration
test tests/c_unit_tests.rs, so plain
running 7 tests
test config::tests::partial_config_falls_back_to_defaults ... ok
test config::tests::rejects_oversized_reset_window ... ok
test config::tests::embedded_default_matches_struct_default ... ok
test config::tests::rejects_inverted_port_range ... ok
test config::tests::rejects_zero_reset_window ... ok
test config::tests::rejects_zero_start_port ... ok
test config::tests::unknown_keys_are_rejected ... ok

test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

running 1 test
test c_parser_unit_tests ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.96s covers it; CI now runs
cargo test before the release build.

UBSan found one real issue: the varint byte fold did a signed left shift
that overflows __s32 for 5-byte values (e.g. 0x0F << 28), which is UB in
C. The shift now happens in unsigned; the emitted BPF instructions are
unchanged (verified via objdump diff).
@Outfluencer Outfluencer self-assigned this Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant