Skip to content

Commit c79f5cf

Browse files
committed
feat(fw): track TX rates, manual-mitigation live stats, top-level totals, drop%
chore: release v0.3.1
1 parent 2feb02f commit c79f5cf

10 files changed

Lines changed: 831 additions & 196 deletions

File tree

Cargo.lock

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ members = [
1616
exclude = ["lnvps_fw"]
1717

1818
[workspace.package]
19-
version = "0.3.0"
19+
version = "0.3.1"
2020
edition = "2024"
2121

2222
[workspace.dependencies]

lnvps_fw/Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lnvps_fw/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ members = ["lnvps_ebpf", "lnvps_fw_common", "lnvps_fw_service"]
66
default-members = ["lnvps_fw_common", "lnvps_fw_service"]
77

88
[workspace.package]
9-
version = "0.3.0"
9+
version = "0.3.1"
1010
edition = "2024"
1111

1212
[workspace.dependencies]

lnvps_fw/lnvps_ebpf/src/main.rs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use maps::{
2626
count_src_v4, count_src_v6, counters_v4, counters_v6, dest_mode_v4, dest_mode_v6,
2727
learn_port_v4, learn_port_v6, manual_blocked_v4, manual_blocked_v6, mark_verified_v4,
2828
mark_verified_v6, port_is_open_v4, port_is_open_v6, protected_v4, protected_v6, scoped,
29-
src_verified_v4, src_verified_v6,
29+
src_verified_v4, src_verified_v6, tx_counters_v4, tx_counters_v6,
3030
};
3131

3232
/// Normalized L4 metadata extracted from a packet, shared between the v4 and
@@ -415,16 +415,40 @@ fn egress_service(ctx: &TcContext, proto: u8, l4_off: usize) -> Result<Option<Eg
415415
}
416416
}
417417

418+
/// Account one outbound packet against the local source IP's TX counters.
419+
/// Proto breakdown is derived from the IP header alone (no L4 parse), so it is
420+
/// cheap and works for fragments/options too. `icmp_proto` distinguishes ICMP
421+
/// (v4) from ICMPv6.
422+
#[inline(always)]
423+
fn tx_account(c: *mut lnvps_fw_common::DestCounters, pkt_len: u64, proto: u8, icmp_proto: u8) {
424+
let c = unsafe { &mut *c };
425+
c.packets += 1;
426+
c.bytes += pkt_len;
427+
if proto == PROTO_TCP {
428+
c.tcp_packets += 1;
429+
} else if proto == PROTO_UDP {
430+
c.udp_packets += 1;
431+
} else if proto == icmp_proto {
432+
c.icmp_packets += 1;
433+
}
434+
}
435+
418436
#[inline(always)]
419437
fn learn_ipv4(ctx: &TcContext) -> Result<(), ()> {
420438
let ip = unsafe { &*tc_ptr_at::<Ipv4Hdr>(ctx, EthHdr::LEN)? };
421-
// Options-bearing headers are skipped (rare); L4 offset would be wrong.
422-
if ip.ihl() as usize != Ipv4Hdr::LEN {
439+
// Only account/learn for protected servers (keeps state clean on a router
440+
// that forwards for many networks).
441+
if scoped() && !protected_v4(ip.src_addr) {
423442
return Ok(());
424443
}
425-
// Only learn ports for protected servers (keeps OPEN_PORTS clean on a
426-
// router that forwards for many networks).
427-
if scoped() && !protected_v4(ip.src_addr) {
444+
// TX accounting for every outbound packet from this source (before the
445+
// options-header early-out below, which only affects L4 port learning).
446+
if let Some(c) = tx_counters_v4(&ip.src_addr) {
447+
let pkt_len = (ctx.data_end() - ctx.data()) as u64;
448+
tx_account(c, pkt_len, ip.proto, PROTO_ICMP);
449+
}
450+
// Options-bearing headers are skipped (rare); L4 offset would be wrong.
451+
if ip.ihl() as usize != Ipv4Hdr::LEN {
428452
return Ok(());
429453
}
430454
if let Some(svc) = egress_service(ctx, ip.proto, EthHdr::LEN + Ipv4Hdr::LEN)? {
@@ -445,6 +469,11 @@ fn learn_ipv6(ctx: &TcContext) -> Result<(), ()> {
445469
if scoped() && !protected_v6(ip.src_addr) {
446470
return Ok(());
447471
}
472+
// TX accounting for every outbound packet from this source.
473+
if let Some(c) = tx_counters_v6(&ip.src_addr) {
474+
let pkt_len = (ctx.data_end() - ctx.data()) as u64;
475+
tx_account(c, pkt_len, ip.next_hdr, PROTO_ICMPV6);
476+
}
448477
// Only inspect packets whose first next-header is directly TCP/UDP.
449478
if let Some(svc) = egress_service(ctx, ip.next_hdr, EthHdr::LEN + Ipv6Hdr::LEN)? {
450479
let key = PortKeyV6 {

lnvps_fw/lnvps_ebpf/src/maps.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ pub static V4_DEST_COUNTERS: LruPerCpuHashMap<[u8; 4], DestCounters> =
3333
pub static V6_DEST_COUNTERS: LruPerCpuHashMap<[u8; 16], DestCounters> =
3434
LruPerCpuHashMap::with_max_entries(MAX_DST_IPS, 0);
3535

36+
/// Per-local-IP TX (egress) traffic counters (IPv4), updated by the TC program
37+
/// for every outbound packet from a protected/local source. Sampled by
38+
/// userspace for the tx-rate view (the `dropped`/`syn_packets` fields stay 0).
39+
#[map]
40+
pub static V4_TX_COUNTERS: LruPerCpuHashMap<[u8; 4], DestCounters> =
41+
LruPerCpuHashMap::with_max_entries(MAX_DST_IPS, 0);
42+
43+
/// Per-local-IP TX (egress) traffic counters (IPv6).
44+
#[map]
45+
pub static V6_TX_COUNTERS: LruPerCpuHashMap<[u8; 16], DestCounters> =
46+
LruPerCpuHashMap::with_max_entries(MAX_DST_IPS, 0);
47+
3648
/// Per-source packet counters (IPv4), incremented only while the destination
3749
/// is mitigating. Sampled by userspace to compute per-source rates and drive
3850
/// CIDR escalation. LRU + per-CPU: bounded memory, summed across CPUs.
@@ -145,6 +157,8 @@ macro_rules! counters_for {
145157

146158
counters_for!(counters_v4, [u8; 4], V4_DEST_COUNTERS);
147159
counters_for!(counters_v6, [u8; 16], V6_DEST_COUNTERS);
160+
counters_for!(tx_counters_v4, [u8; 4], V4_TX_COUNTERS);
161+
counters_for!(tx_counters_v6, [u8; 16], V6_TX_COUNTERS);
148162

149163
/// Generate a per-source packet-count incrementer for one address family
150164
/// (called under mitigation). Pure counting — no policy decision.

0 commit comments

Comments
 (0)