Skip to content

## feat: integrate hillock XDP, TC, nftables, and iptables firewall backends#435

Open
Gepsonka wants to merge 14 commits into
mainfrom
hillock-xdp-integration
Open

## feat: integrate hillock XDP, TC, nftables, and iptables firewall backends#435
Gepsonka wants to merge 14 commits into
mainfrom
hillock-xdp-integration

Conversation

@Gepsonka

Copy link
Copy Markdown
Collaborator

Replaces synapse's own eBPF pipeline, nft shelling-out, and raw iptables crate usage with hillock as the single firewall enforcement layer. Hillock provides the XDP dispatcher, TC attachment, nftables netlink backend, and iptables chain management — synapse wraps them with IP ban/allow logic and fingerprint program injection.

What changed

  • XDP pipeline replaced: synapse's xdp_pipeline skeleton → hillock XdpDispatcher with tail-call chain (ip_filter → port_filter → firewall_rules → ja4_capture → tcp_fingerprint)
  • TC firewall: hillock OpenTcFirewallRules attached ingress+egress per interface, backend registered globally
  • Nftables backend: replaced 604-line nft CLI shelling-out with hillock::linux::nftables::FirewallRuleBackend
  • Iptables backend: replaced 413-line raw iptables crate with hillock::linux::iptables::IpTablesFirewallRules, auto-managing INPUT jump rules
  • Split FirewallBackend trait into IpFirewallBackend (L3/L4) and FingerprintFirewallBackend (L7 JA4) — callers depend only on what they need
  • Access rules refactored to use hillock dispatchers instead of synapse BPF skeletons, with resettable OnceLock<Mutex<Option<...>>> globals
  • Bumped hillock from 0.0.1 to 0.1.0 at commit 58fb0ac (with iptables/nftables feature flags)
  • Replaced std::mem::transmute with Box::leak for BPF skeleton lifetime management
  • Zero compiler warnings across the full --features bpf,amygdala-reactor workspace build

E2E test coverage (34 tests, 544 checks)

Suite Tests Checks Coverage
XDP firewall 8 177 IP filter, port filter, 5-tuple rules, subnets, edge cases, access rules integration
TC + XDP 1 36 Combined IP + port + firewall rules via dispatcher
Nftables 14 178 Layer 1 (synapse wrapper) + Layer 2 (hillock FirewallRuleBackend directly)
Iptables 12 177 Layer 1 (synapse wrapper) + Layer 2 (hillock IpTablesFirewallRules directly)

All use real hping3 traffic through veth pairs + netns. Run with:

make test-e2e-all   # or test-e2e-nftables / test-e2e-iptables / test-e2e-xdp

Files changed

33 files, +7,376 / −1,635 lines

@Gepsonka Gepsonka self-assigned this Jul 10, 2026
@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown

All contributors have signed the CLA. Thank you!
Posted by the CLA Assistant Lite bot.

@Gepsonka

Copy link
Copy Markdown
Collaborator Author

I have read the CLA Document and I hereby sign the CLA

pigri added a commit that referenced this pull request Jul 10, 2026
Gepsonka and others added 7 commits July 10, 2026 23:01
Redo of the botched merge 0322c46, which resolved conflicts by taking the
branch side wholesale and silently reverted ~8500 lines of main's post-fork
work (identity, edge_set, service_graph, passive_scan, penalty_signals,
identity_mmdb, fqdn/sni stores, tcx overlay_capture, egress edge-drop,
CLI/ML config, blocking-log, IDS, config, xdp-e2e CI, ...).

This merge instead keeps main's additions (82 files at main parity, 0 reverted)
and layers the hillock XDP/TC/iptables/nftables firewall integration on top.

Conflict resolutions:
- firewall/mod.rs: keep both (branch ja4/tcp-fp capture + main tcx overlay).
- firewall/iptables.rs, nftables.rs: keep hillock backends; port main's egress
  edge-drop (ban_edge/unban_edge) onto them (native FORWARD-chain layered
  alongside hillock's IP filter).
- access-rules: keep both (XDP/TC firewall-rules backend + egress EdgeFallback);
  reimplement ban_edge_kernel_global/unban_edge_kernel_global as hillock 5-tuple
  drop rulesets via the dispatcher (FIREWALL_SKELS was replaced by dispatchers).
- app/lib.rs: keep the branch's hillock dispatcher init (replaces the inline
  bpf::XdpSkelBuilder attach loop main improved).
- amygdala 0.1.5 -> 0.1.9 (needed for main's dns.fqdn / identity.k8s scheme
  fields); hillock stays 0.1.0 (git patch; not yet published to the registry).

Verified: cargo check --workspace --all-targets clean; firewall+fingerprint
e2e all pass (iptables 12/12, nftables 14/14, xdp 7/7, tc 1/1, fingerprint
e2e 1/1, fingerprint unit 14/14).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds egress_edge_e2e_tests covering the edge-drop (per-flow src->dst) paths:
- egress_edge_nftables / egress_edge_iptables: the REAL enforcement path
  (native FORWARD-hook chain) — verifies rule/element install+remove, TTL,
  idempotency. Both pass (nft 8/8, ipt 6/6).
- egress_edge_xdp_kernel_is_safe_noop: safety regression guard.

The tests surfaced a real bug: hillock's XDP firewall-rules backend cannot
express a per-flow src->dst drop. A rule with both src_ip and dst_ip matches
the destination ONLY (ignoring src) and logs a kernel "integer out of range" —
installing it blackholes ALL traffic to the destination. The previous
ban_edge_kernel_global port did exactly this.

Fix: ban_edge_kernel_global / unban_edge_kernel_global are now deliberate
no-ops (return false) so ban_edge_global falls through to the correct
nftables/iptables FORWARD-hook fallback. Documented with a TODO to re-enable a
kernel edge-drop once hillock exposes a dedicated banned_edges map.

Known gap (follow-up): app/lib.rs only registers the nftables/iptables edge
fallback for those backends (`_ => {}` for XDP), so in XDP-primary mode an
egress edge-drop is currently a silent no-op — a fallback should be registered
there too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hillock 0.2.1 is now published to the gen0sec registry, so the load-bearing
`[patch]` sourcing hillock from git (which CI couldn't fetch —
"revision ... not found") is removed and all crates depend on the registry
version:
  synapse-security / synapse-smart-firewall / synapse-access-rules /
  synapse-app: hillock 0.1.0 -> 0.2.1.

The 0.1->0.2 API is compatible with the branch's usage — cargo check
--workspace --all-targets is clean and the full firewall+fingerprint e2e suite
passes on 0.2.1 (iptables 12/12, nftables 14/14, xdp 7/7, tc 1/1, fingerprint
e2e 1/1, egress edge 3/3, fingerprint unit 14/14).

Also applies nightly rustfmt (fixes the CI Formatting/Windows fmt failures):
import ordering in firewall/{iptables,nftables}.rs + egress_edge_e2e_tests.rs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI's Wellness Check + Windows clippy jobs (stable clippy, now 1.97) surfaced
lints once the earlier fmt / hillock-git-fetch failures were cleared:

- synapse-access-rules: for_kv_map (`map.iter()` ignoring the key ->
  `map.values()`), unneeded returns, a very-complex type factored into a
  `DispatcherList` alias.
- synapse-app / synapse-utils / tests/e2e: collapsible if, unneeded return,
  redundant `&` in warn!, unit let-bindings (cargo clippy --fix).
- smart-firewall e2e/test files: `#![allow(clippy::disallowed_methods)]` —
  unwrap/expect are idiomatic in tests (matches the repo convention in
  synapse-waf / synapse-utils).

Verified locally with the exact CI commands (stable clippy 1.97):
  cargo clippy --locked --workspace --all-targets -- --deny warnings         => clean
  cargo clippy --locked --workspace --features classifier --all-targets ...   => clean
  cargo +nightly fmt --check                                                   => clean

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thalamus_ids_windows.rs: replace a `match { Some => .., None => return None }`
with the `?` operator (clippy::question_mark, stable 1.97). Windows-only code,
so not caught by the Linux clippy jobs; surfaced by the Windows Build clippy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Windows clippy job (unix=false ⇒ all(unix, feature="bpf") is false) hit
real compile errors in the merged hillock wiring:

- synapse-app/src/lib.rs always declares the local `hillock_handles` /
  `hillock_tc_handles` vecs, typed with `hillock_init::{HillockXdpHandle,
  HillockTcHandle}` — which only existed under cfg(all(unix, feature="bpf")).
  Add non-(unix+bpf) stub types so the declarations type-check; they are never
  constructed (all populate/read paths are cfg-gated), so the vecs stay empty.
- Allow unused `hillock_tc_handles` (populated/consumed only under the bpf cfg).
- Gate `BPF_FIREWALL_PIN_ROOT` to the bpf cfg (unused otherwise).

Validated the non-bpf path on Linux (unix=true ⇒ same cfg as Windows):
  cargo clippy -p synapse-app --no-default-features --all-targets -- -D warnings => clean
Default Linux+bpf clippy + nightly fmt still clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lib.rs:6652 (Windows branch of network-interface detection): drop the `return`
on the tail `match` (clippy::needless_return). Windows-only code, invisible to
the Linux clippy jobs.

Verified the ENTIRE Windows target is now clippy-clean via cross-compilation
(mingw): cargo clippy --all-targets --target x86_64-pc-windows-gnu -- -D warnings
=> clean. No more Windows-only lints hiding behind the fail-fast.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.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