NetGuardia/.gitattributes
DaLaw2 ebc7edcead feat: symmetric hash, drop events, GeoIP/DNS, performance & bug fixes (#15)
* fix: ML formula alignment, dependency cleanup, and code quality

- Restore correct MSE denominator (ae_feature_names.len())
- Align feature extraction with CICFlowMeter: payload bytes, sample std (N-1),
  min_seg_size_forward without payload filter, act_data_pkt_fwd skip first packet,
  init_win_bytes_bwd stores last packet
- Organize workspace dependencies and update all crates to latest
- Fix libxdp-sys 0.2.4 clang 20 build (enable use_cc_build + use_precompiled_bpf)
- Pin aya-ebpf =0.1.1 (0.1.2 yanked, aya-rs/aya#1400)
- Remove all comments and dead code
- Use macro-generated constructors for error types
- Replace eprintln/tracing::error with log! macro
- Clean up duplicate/unused error and log variants, fix log levels

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: correct UDP/TCP/mid-stream forward/backward direction logic

* fix: symmetric hash redirect, per-queue optimization, and ICMP ACL support

eBPF: Add XOR symmetric hash to transmission stage so both directions of
a flow land on the same XSK queue, fixing the RSS asymmetry bug that
caused FlowTracker to only see one direction. Egress uses stack-local
ParsedPacket to avoid unnecessary map allocation. Allow non-TCP/UDP
packets through the pipeline so ACL can block ICMP.

Userspace: Revert shared tracker to per-queue trackers now that symmetric
hash guarantees bidirectional visibility. Replace Arc<Mutex<Vec<FrameDesc>>>
with private Vec per XskPair to eliminate hot-path lock contention. Add
pre-allocated BufferPool and parse-before-clone to reduce per-packet malloc.
Log partial sends when frames < packets.

Config: Add mtu, packet_buffer_size, buffer_pool_capacity to [Network]
with serde defaults for backward compatibility.

Cleanup: Remove unused Direction::flip(), FlowTracker::drain_flows(),
FlowStatistics broadcast/subscribe, MAX_BUFFERED_PACKETS, and
Engine::process_packet wrapper.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: drop event ring buffer, double-buffer flow tracker, and review fixes

eBPF: Add 256KB RingBuf map for drop event reporting. Each XDP_DROP site
(ACL blacklist, rate limit, protocol filter) emits a DropEvent with
reason code. Refactor rate_limit::should_drop to return Option<u8> with
specific reason. Extract rate limit defaults to common/define/setting.

Userspace: Add DropMonitor with tokio async consumer, broadcast channel,
DropCounters, and /api/stats/drops REST + /ws/drops WebSocket endpoints.

FlowTracker: Replace O(flows) lock with O(1) take_snapshot() swap.
Inference tick now does phase-1 swap under lock, phase-2 filter outside
lock. Removes cleanup_old_flows, get_flows_for_inference,
drain_flows_for_logging.

Review fixes: explicit IPv6 match in symmetric_hash, consistent naming
(compute_symmetric_queue_id), reason_to_str helper, std::net::Ipv6Addr
formatting, WebSocketLagged typo, remove unused mtu config field.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: move IP field writes before L4 match to prevent eBPF dead store elimination

The eBPF LLVM backend was optimizing away the non-TCP/UDP path by
treating ParsedPacket writes as dead stores (reads happen in separate
tail-called programs). Moving IP-level fields (timestamp, src/dst IP,
packet_length, ip_version, protocol) before the L4 protocol match
ensures they are written regardless of protocol, sharing code path
with TCP/UDP and preventing branch merging with the error path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: GeoIP LPM Trie country blocking and DNS query blacklist

eBPF: Add LPM Trie maps (GEO_BLOCK_V4/V6) in access_control for
country-level IP blocking with big-endian keys for correct prefix
matching. Add DNS query name parser in dns_filter with bounded loops,
lowercase normalization, and subdomain matching via parent domain
iteration. Both emit drop events with new reason codes.

Userspace: Add GeoBlock manager that loads CIDR prefixes from MaxMind
GeoLite2 database into LPM Tries per blocked country. Add DnsFilter
manager with wire-format domain conversion. New REST endpoints:
PUT/DELETE/GET /api/acl/geo/{block,unblock,blocked} and
PUT/DELETE/GET /api/filter/dns/blacklist.

Review fixes: DNS header bounds check off-by-one (+2 to +3),
total_prefixes accumulation (= to +=), proper InvalidDnsName error
type replacing InvalidMapType abuse.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: review fixes for GeoIP and DNS features

GeoIP: Fix LPM Trie endianness (from_be_bytes → from_ne_bytes for
consistent memory layout). Move whitelist check before geo/blacklist
(whitelist should always take priority). Batch block/unblock API to
rebuild tries only once. Minimize rebuild gap by collecting entries
before locking. Use config path for GeoIP DB. Validate country codes
(2-letter alpha).

DNS: Fix bounds check off-by-one (dns_header+2 → +3). Add QDCOUNT>0
check. Replace InvalidMapType error with proper InvalidDnsName variant.
Add domain count limit per request (1000). Wrap API responses in JSON
objects for consistency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: add deployment and testing infrastructure

Add container definitions, compose config, and traffic generator
scripts for realistic inline deployment testing.

deploy/compose/ — Containerfiles for netguardia, router, endpoints
                  and podman-compose.yml with management network
deploy/scripts/ — setup.sh (veth/namespace wiring),
                  traffic-external.sh and traffic-internal.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: P0 bugs — IPv4 IHL parsing, TX frame leak, SSH whitelist logic

IPv4 IHL: L4 header offset was hardcoded to 34 (20-byte IPv4 header).
Now reads IHL field dynamically (20-60 bytes) so IP options don't
cause wrong port/flag parsing. Attackers could previously bypass
port-based rules by adding IP options.

TX frame leak: tx.produce() may submit fewer frames than provided.
Unsubmitted FrameDescs were lost, permanently shrinking frame_pool.
Now returns unsubmitted frames to pool.

SSH whitelist: Array<PlaceHolder>.get(0).is_some() always returns true
(zero-initialized entries exist). Changed to check actual value != 0,
matching userspace enable(1)/disable(0) semantics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: P1 bugs — rate_limit real values, ML status, DNS rate protocol check

rate_limit API: GET /api/rate-limit/config now reads actual values from
eBPF Array map instead of returning hardcoded defaults. Added getter
methods for each rate limit parameter.

ML status API: GET /api/ml/status now returns real engine state (mode,
tracker count, flow count, inference interval) instead of hardcoded
{"active": true}.

DNS rate limit: Added UDP protocol check before dst_port==53 test.
TCP connections to port 53 no longer incorrectly trigger DNS rate
limiter alongside packet rate limiter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* perf: P2 fixes — binary IPs, pre-alloc buffers, build.rs dedup

parse_packet: Replace String IP addresses with [u8; 16] binary in
UserPacket, eliminating 2M heap allocations/sec at 1Mpps. FlowKey
copies bytes directly instead of string-parse-to-bytes round-trip.

xsk_manager: Pre-allocate comp_descs (256) and rx_descs (64) once
before the main loop instead of per-iteration vec![] allocation.

build.rs: Extract duplicate build_ingress_ebpf/build_egress_ebpf into
shared build_ebpf_package(). Add cargo:rerun-if-changed for common/src
to fix stale eBPF build cache when common crate changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* perf: P2 fixes — feature_extractor precompute, filter.rs dedup

feature_extractor: Pre-compute all statistics once via PrecomputedStats
struct instead of recomputing per feature name. ~70x speedup for the
72-feature extraction step. Public API unchanged.

filter.rs: Extract ok_or_error() helper to eliminate 16 instances of
duplicated match-result-to-HttpResponse pattern. File reduced from
338 to 243 lines with identical behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: P3 code quality — release profile, dep cleanup, dead code, safety

Release profile: Enable opt-level=3, lto="thin", strip=true.

Dependencies: Remove unused `futures` crate. Change tokio from "full"
to selective features (rt-multi-thread, macros, sync, time). Remove
commented-out csv/anyhow deps.

Dead code: Remove unused get_attack_label(), unnecessary
#[allow(dead_code)] annotations where code is actually used.

Safety: Add bounds check in PortRule::to_port_vec to prevent OOB if
count is corrupted. Fix set_config to propagate errors instead of
silently ignoring with `let _ =`.

Misc: Add .env, .DS_Store, profiling files to .gitignore.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: ParrotXray <b05817691@gmail.com>
2026-03-21 15:41:36 +08:00

2 lines
19 B
Plaintext