Fill TCP flags/window/header/payload in eBPF parser; migrate stats maps to per-CPU

- common/src/ebpf/parsing.rs: refactor parse_tcp_port/parse_udp_port into
  parse_tcp/parse_udp returning (src_port, dst_port, TcpFlags, header_length,
  window_size); compute payload_length from tot_len/payload_len; write all
  previously-missing fields (tcp_flags, tcp_window_size, header_length,
  payload_length, is_forward) into the Event; remove redundant nested unsafe
  blocks inside unsafe fn bodies

- ingress-ebpf/src/action/statistics.rs,
  egress-ebpf/src/action/statistics.rs: replace LruHashMap with
  LruPerCpuHashMap (BPF_MAP_TYPE_LRU_PERCPU_HASH) to eliminate multi-CPU
  races on per-flow counter updates

- mantis/src/core/ebpf/statistics.rs: replace AyaHashMap with
  PerCpuHashMap; aggregate per-CPU values in get_map() (sum bytes/packets,
  max last_seen) and use max last_seen for expiry check in cleanup()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0138PxtKH73hqxv7h1oaoSdS
This commit is contained in:
Claude 2026-06-17 07:40:53 +00:00
parent 3cd23ae153
commit 77c427c298
No known key found for this signature in database
4 changed files with 161 additions and 156 deletions

View File

@ -5,7 +5,7 @@ use network_types::tcp::TcpHdr;
use network_types::udp::UdpHdr;
use crate::define::offset::*;
use crate::model::event::{Event, IPv4Event, IPv6Event};
use crate::model::event::{Event, IPv4Event, IPv6Event, TcpFlags};
pub fn parse_packet(start: usize, end: usize, target: *mut Event) -> Result<(), ()> {
unsafe {
@ -24,112 +24,85 @@ pub fn parse_packet(start: usize, end: usize, target: *mut Event) -> Result<(),
#[inline(always)]
unsafe fn parse_ipv4_packet(start: usize, end: usize, target: *mut Event) -> Result<(), ()> {
unsafe {
if start + IPV4_HEADER_END > end {
return Err(());
}
let ipv4 = &*((start + IPV4_HEADER_START) as *const Ipv4Hdr);
// ihl() returns actual IP header length in bytes (2060); use it so IP Options
// don't shift the L4 header offset and corrupt port extraction.
let ip_hdr_len = ipv4.ihl() as usize;
if ip_hdr_len < core::mem::size_of::<Ipv4Hdr>() {
return Err(());
}
let l4_start = ETHER_HEADER_END + ip_hdr_len;
let (source_port, destination_port) = match ipv4.proto {
IpProto::Tcp => parse_tcp_port(start, end, l4_start)?,
IpProto::Udp => parse_udp_port(start, end, l4_start)?,
_ => return Err(()),
};
*(target as *mut u32) = 0;
let ipv4_data_ptr = (target as *mut u8).add(16);
core::ptr::write(ipv4_data_ptr as *mut IpProto, ipv4.proto);
core::ptr::copy_nonoverlapping(
ipv4.src_addr.as_ptr(),
ipv4_data_ptr.add(core::mem::offset_of!(IPv4Event, src_ip)),
4,
);
core::ptr::copy_nonoverlapping(
ipv4.dst_addr.as_ptr(),
ipv4_data_ptr.add(core::mem::offset_of!(IPv4Event, dst_ip)),
4,
);
core::ptr::write(
ipv4_data_ptr.add(core::mem::offset_of!(IPv4Event, src_port)) as *mut u16,
source_port,
);
core::ptr::write(
ipv4_data_ptr.add(core::mem::offset_of!(IPv4Event, dst_port)) as *mut u16,
destination_port,
);
core::ptr::write(
ipv4_data_ptr.add(core::mem::offset_of!(IPv4Event, packet_length)) as *mut u32,
(end - start) as u32,
);
core::ptr::write(
ipv4_data_ptr.add(core::mem::offset_of!(IPv4Event, timestamp_us)) as *mut u64,
bpf_ktime_get_ns(),
);
Ok(())
if start + IPV4_HEADER_END > end {
return Err(());
}
let ipv4 = &*((start + IPV4_HEADER_START) as *const Ipv4Hdr);
// ihl() returns actual IP header length in bytes (2060); use it so IP Options
// don't shift the L4 header offset and corrupt port extraction.
let ip_hdr_len = ipv4.ihl() as usize;
if ip_hdr_len < core::mem::size_of::<Ipv4Hdr>() {
return Err(());
}
let l4_start = ETHER_HEADER_END + ip_hdr_len;
let (src_port, dst_port, tcp_flags, l4_hdr_len, window_size) = match ipv4.proto {
IpProto::Tcp => parse_tcp(start, end, l4_start)?,
IpProto::Udp => parse_udp(start, end, l4_start)?,
_ => return Err(()),
};
let payload_length = (ipv4.tot_len() as u32)
.saturating_sub(ip_hdr_len as u32)
.saturating_sub(l4_hdr_len as u32);
*(target as *mut u32) = 0;
let p = (target as *mut u8).add(16);
core::ptr::write(p as *mut IpProto, ipv4.proto);
core::ptr::copy_nonoverlapping(ipv4.src_addr.as_ptr(), p.add(core::mem::offset_of!(IPv4Event, src_ip)), 4);
core::ptr::copy_nonoverlapping(ipv4.dst_addr.as_ptr(), p.add(core::mem::offset_of!(IPv4Event, dst_ip)), 4);
core::ptr::write(p.add(core::mem::offset_of!(IPv4Event, src_port)) as *mut u16, src_port);
core::ptr::write(p.add(core::mem::offset_of!(IPv4Event, dst_port)) as *mut u16, dst_port);
core::ptr::write(p.add(core::mem::offset_of!(IPv4Event, packet_length)) as *mut u32, (end - start) as u32);
core::ptr::write(p.add(core::mem::offset_of!(IPv4Event, payload_length)) as *mut u32, payload_length);
core::ptr::write(p.add(core::mem::offset_of!(IPv4Event, header_length)) as *mut u16, l4_hdr_len);
core::ptr::write(p.add(core::mem::offset_of!(IPv4Event, timestamp_us)) as *mut u64, bpf_ktime_get_ns());
core::ptr::write(p.add(core::mem::offset_of!(IPv4Event, tcp_flags)) as *mut TcpFlags, tcp_flags);
core::ptr::write(p.add(core::mem::offset_of!(IPv4Event, tcp_window_size)) as *mut u16, window_size);
core::ptr::write(p.add(core::mem::offset_of!(IPv4Event, is_forward)) as *mut bool, false);
Ok(())
}
#[inline(always)]
unsafe fn parse_ipv6_packet(start: usize, end: usize, target: *mut Event) -> Result<(), ()> {
unsafe {
if start + IPV6_HEADER_END > end {
return Err(());
}
let ipv6 = &*((start + IPV6_HEADER_START) as *const Ipv6Hdr);
let (source_port, destination_port) = match ipv6.next_hdr {
IpProto::Tcp => parse_tcp_port(start, end, IPV6_TCP_HEADER_START)?,
IpProto::Udp => parse_udp_port(start, end, IPV6_UDP_HEADER_START)?,
_ => return Err(()),
};
*(target as *mut u32) = 1;
let ipv6_data_ptr = (target as *mut u8).add(16);
core::ptr::write(ipv6_data_ptr as *mut IpProto, ipv6.next_hdr);
core::ptr::copy_nonoverlapping(
ipv6.src_addr.as_ptr(),
ipv6_data_ptr.add(core::mem::offset_of!(IPv6Event, src_ip)),
16,
);
core::ptr::copy_nonoverlapping(
ipv6.dst_addr.as_ptr(),
ipv6_data_ptr.add(core::mem::offset_of!(IPv6Event, dst_ip)),
16,
);
core::ptr::write(
ipv6_data_ptr.add(core::mem::offset_of!(IPv6Event, src_port)) as *mut u16,
source_port,
);
core::ptr::write(
ipv6_data_ptr.add(core::mem::offset_of!(IPv6Event, dst_port)) as *mut u16,
destination_port,
);
core::ptr::write(
ipv6_data_ptr.add(core::mem::offset_of!(IPv6Event, packet_length)) as *mut u32,
(end - start) as u32,
);
core::ptr::write(
ipv6_data_ptr.add(core::mem::offset_of!(IPv6Event, timestamp_us)) as *mut u64,
bpf_ktime_get_ns(),
);
Ok(())
if start + IPV6_HEADER_END > end {
return Err(());
}
let ipv6 = &*((start + IPV6_HEADER_START) as *const Ipv6Hdr);
let (src_port, dst_port, tcp_flags, l4_hdr_len, window_size) = match ipv6.next_hdr {
IpProto::Tcp => parse_tcp(start, end, IPV6_TCP_HEADER_START)?,
IpProto::Udp => parse_udp(start, end, IPV6_UDP_HEADER_START)?,
_ => return Err(()),
};
// payload_len() is everything after the fixed 40-byte IPv6 header (L4 header + data).
let payload_length = (ipv6.payload_len() as u32).saturating_sub(l4_hdr_len as u32);
*(target as *mut u32) = 1;
let p = (target as *mut u8).add(16);
core::ptr::write(p as *mut IpProto, ipv6.next_hdr);
core::ptr::copy_nonoverlapping(ipv6.src_addr.as_ptr(), p.add(core::mem::offset_of!(IPv6Event, src_ip)), 16);
core::ptr::copy_nonoverlapping(ipv6.dst_addr.as_ptr(), p.add(core::mem::offset_of!(IPv6Event, dst_ip)), 16);
core::ptr::write(p.add(core::mem::offset_of!(IPv6Event, src_port)) as *mut u16, src_port);
core::ptr::write(p.add(core::mem::offset_of!(IPv6Event, dst_port)) as *mut u16, dst_port);
core::ptr::write(p.add(core::mem::offset_of!(IPv6Event, packet_length)) as *mut u32, (end - start) as u32);
core::ptr::write(p.add(core::mem::offset_of!(IPv6Event, payload_length)) as *mut u32, payload_length);
core::ptr::write(p.add(core::mem::offset_of!(IPv6Event, header_length)) as *mut u16, l4_hdr_len);
core::ptr::write(p.add(core::mem::offset_of!(IPv6Event, timestamp_us)) as *mut u64, bpf_ktime_get_ns());
core::ptr::write(p.add(core::mem::offset_of!(IPv6Event, tcp_flags)) as *mut TcpFlags, tcp_flags);
core::ptr::write(p.add(core::mem::offset_of!(IPv6Event, tcp_window_size)) as *mut u16, window_size);
core::ptr::write(p.add(core::mem::offset_of!(IPv6Event, is_forward)) as *mut bool, false);
Ok(())
}
// Mirror the C XDP pattern for variable-offset packet access:
@ -148,26 +121,43 @@ unsafe fn parse_ipv6_packet(start: usize, end: usize, target: *mut Event) -> Res
// instructions in LLVM IR, preventing the merge and keeping each protocol's
// bounds check and accesses in the same basic block.
#[inline(always)]
unsafe fn parse_tcp_port(start: usize, end: usize, tcp_start: usize) -> Result<(u16, u16), ()> {
unsafe {
let tcph = (start as *const u8).add(tcp_start) as *const TcpHdr;
// tcph.add(1) advances by size_of::<TcpHdr>() = 20 bytes, matching
// the C pattern "if (tcph + 1 > data_end)".
if tcph.add(1) as usize > end {
return Err(());
}
Ok((u16::from_be_bytes((*tcph).source), u16::from_be_bytes((*tcph).dest)))
unsafe fn parse_tcp(
start: usize,
end: usize,
tcp_start: usize,
) -> Result<(u16, u16, TcpFlags, u16, u16), ()> {
let tcph = (start as *const u8).add(tcp_start) as *const TcpHdr;
// tcph.add(1) advances by size_of::<TcpHdr>() = 20 bytes, matching
// the C pattern "if (tcph + 1 > data_end)".
if tcph.add(1) as usize > end {
return Err(());
}
let src_port = u16::from_be_bytes((*tcph).source);
let dst_port = u16::from_be_bytes((*tcph).dest);
// doff() is the 4-bit data offset field; multiply by 4 to get header length in bytes.
let header_length = (*tcph).doff() * 4;
let window_size = u16::from_be_bytes((*tcph).window);
let flags_byte = ((*tcph).fin() as u8)
| (((*tcph).syn() as u8) << 1)
| (((*tcph).rst() as u8) << 2)
| (((*tcph).psh() as u8) << 3)
| (((*tcph).ack() as u8) << 4)
| (((*tcph).urg() as u8) << 5)
| (((*tcph).ece() as u8) << 6)
| (((*tcph).cwr() as u8) << 7);
Ok((src_port, dst_port, TcpFlags::from_byte(flags_byte), header_length, window_size))
}
#[inline(always)]
unsafe fn parse_udp_port(start: usize, end: usize, udp_start: usize) -> Result<(u16, u16), ()> {
unsafe {
let udph = (start as *const u8).add(udp_start) as *const UdpHdr;
// udph.add(1) advances by size_of::<UdpHdr>() = 8 bytes.
if udph.add(1) as usize > end {
return Err(());
}
Ok(((*udph).src_port(), (*udph).dst_port()))
unsafe fn parse_udp(
start: usize,
end: usize,
udp_start: usize,
) -> Result<(u16, u16, TcpFlags, u16, u16), ()> {
let udph = (start as *const u8).add(udp_start) as *const UdpHdr;
// udph.add(1) advances by size_of::<UdpHdr>() = 8 bytes.
if udph.add(1) as usize > end {
return Err(());
}
Ok(((*udph).src_port(), (*udph).dst_port(), TcpFlags::default(), 8, 0))
}

View File

@ -1,34 +1,34 @@
use aya_ebpf::macros::map;
use aya_ebpf::maps::LruHashMap;
use aya_ebpf::maps::LruPerCpuHashMap;
use common::define::setting::MAX_STATS;
use common::model::event::{IPv4Event, IPv6Event};
use common::model::flow_stats::FlowStats;
use common::model::ip_address::{AddrPortV4, AddrPortV6};
#[map]
static IPV4_EGRESS_SRC_1MIN: LruHashMap<AddrPortV4, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV4_EGRESS_SRC_1MIN: LruPerCpuHashMap<AddrPortV4, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV4_EGRESS_SRC_10MIN: LruHashMap<AddrPortV4, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV4_EGRESS_SRC_10MIN: LruPerCpuHashMap<AddrPortV4, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV4_EGRESS_SRC_1HOUR: LruHashMap<AddrPortV4, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV4_EGRESS_SRC_1HOUR: LruPerCpuHashMap<AddrPortV4, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV6_EGRESS_SRC_1MIN: LruHashMap<AddrPortV6, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV6_EGRESS_SRC_1MIN: LruPerCpuHashMap<AddrPortV6, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV6_EGRESS_SRC_10MIN: LruHashMap<AddrPortV6, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV6_EGRESS_SRC_10MIN: LruPerCpuHashMap<AddrPortV6, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV6_EGRESS_SRC_1HOUR: LruHashMap<AddrPortV6, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV6_EGRESS_SRC_1HOUR: LruPerCpuHashMap<AddrPortV6, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV4_EGRESS_DST_1MIN: LruHashMap<AddrPortV4, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV4_EGRESS_DST_1MIN: LruPerCpuHashMap<AddrPortV4, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV4_EGRESS_DST_10MIN: LruHashMap<AddrPortV4, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV4_EGRESS_DST_10MIN: LruPerCpuHashMap<AddrPortV4, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV4_EGRESS_DST_1HOUR: LruHashMap<AddrPortV4, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV4_EGRESS_DST_1HOUR: LruPerCpuHashMap<AddrPortV4, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV6_EGRESS_DST_1MIN: LruHashMap<AddrPortV6, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV6_EGRESS_DST_1MIN: LruPerCpuHashMap<AddrPortV6, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV6_EGRESS_DST_10MIN: LruHashMap<AddrPortV6, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV6_EGRESS_DST_10MIN: LruPerCpuHashMap<AddrPortV6, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV6_EGRESS_DST_1HOUR: LruHashMap<AddrPortV6, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV6_EGRESS_DST_1HOUR: LruPerCpuHashMap<AddrPortV6, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
pub fn ipv4_update_stats(event: &IPv4Event) {
let source = event.source_addr();
@ -62,7 +62,7 @@ pub fn ipv6_update_stats(event: &IPv6Event) {
// high load is tolerable. A future migration to PerCpuLruHashMap would eliminate
// the race at the cost of per-CPU aggregation on the userspace read path.
#[inline(always)]
unsafe fn ipv4_update_flow_stats(map: &LruHashMap<AddrPortV4, FlowStats>, key: &AddrPortV4, event: &IPv4Event) {
unsafe fn ipv4_update_flow_stats(map: &LruPerCpuHashMap<AddrPortV4, FlowStats>, key: &AddrPortV4, event: &IPv4Event) {
if let Some(status) = map.get_ptr_mut(key) {
(*status).bytes += event.packet_length as u64;
(*status).packets += 1;
@ -77,7 +77,7 @@ unsafe fn ipv4_update_flow_stats(map: &LruHashMap<AddrPortV4, FlowStats>, key: &
}
#[inline(always)]
unsafe fn ipv6_update_flow_status(map: &LruHashMap<AddrPortV6, FlowStats>, key: &AddrPortV6, event: &IPv6Event) {
unsafe fn ipv6_update_flow_status(map: &LruPerCpuHashMap<AddrPortV6, FlowStats>, key: &AddrPortV6, event: &IPv6Event) {
if let Some(status) = map.get_ptr_mut(key) {
(*status).bytes += event.packet_length as u64;
(*status).packets += 1;

View File

@ -1,34 +1,34 @@
use aya_ebpf::macros::map;
use aya_ebpf::maps::LruHashMap;
use aya_ebpf::maps::LruPerCpuHashMap;
use common::define::setting::MAX_STATS;
use common::model::event::{IPv4Event, IPv6Event};
use common::model::flow_stats::FlowStats;
use common::model::ip_address::{AddrPortV4, AddrPortV6};
#[map]
static IPV4_INGRESS_SRC_1MIN: LruHashMap<AddrPortV4, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV4_INGRESS_SRC_1MIN: LruPerCpuHashMap<AddrPortV4, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV4_INGRESS_SRC_10MIN: LruHashMap<AddrPortV4, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV4_INGRESS_SRC_10MIN: LruPerCpuHashMap<AddrPortV4, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV4_INGRESS_SRC_1HOUR: LruHashMap<AddrPortV4, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV4_INGRESS_SRC_1HOUR: LruPerCpuHashMap<AddrPortV4, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV6_INGRESS_SRC_1MIN: LruHashMap<AddrPortV6, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV6_INGRESS_SRC_1MIN: LruPerCpuHashMap<AddrPortV6, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV6_INGRESS_SRC_10MIN: LruHashMap<AddrPortV6, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV6_INGRESS_SRC_10MIN: LruPerCpuHashMap<AddrPortV6, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV6_INGRESS_SRC_1HOUR: LruHashMap<AddrPortV6, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV6_INGRESS_SRC_1HOUR: LruPerCpuHashMap<AddrPortV6, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV4_INGRESS_DST_1MIN: LruHashMap<AddrPortV4, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV4_INGRESS_DST_1MIN: LruPerCpuHashMap<AddrPortV4, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV4_INGRESS_DST_10MIN: LruHashMap<AddrPortV4, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV4_INGRESS_DST_10MIN: LruPerCpuHashMap<AddrPortV4, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV4_INGRESS_DST_1HOUR: LruHashMap<AddrPortV4, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV4_INGRESS_DST_1HOUR: LruPerCpuHashMap<AddrPortV4, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV6_INGRESS_DST_1MIN: LruHashMap<AddrPortV6, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV6_INGRESS_DST_1MIN: LruPerCpuHashMap<AddrPortV6, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV6_INGRESS_DST_10MIN: LruHashMap<AddrPortV6, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV6_INGRESS_DST_10MIN: LruPerCpuHashMap<AddrPortV6, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
#[map]
static IPV6_INGRESS_DST_1HOUR: LruHashMap<AddrPortV6, FlowStats> = LruHashMap::with_max_entries(MAX_STATS as u32, 0);
static IPV6_INGRESS_DST_1HOUR: LruPerCpuHashMap<AddrPortV6, FlowStats> = LruPerCpuHashMap::with_max_entries(MAX_STATS as u32, 0);
pub fn ipv4_update_stats(event: &IPv4Event) {
let source = event.source_addr();
@ -62,7 +62,7 @@ pub fn ipv6_update_stats(event: &IPv6Event) {
// high load is tolerable. A future migration to PerCpuLruHashMap would eliminate
// the race at the cost of per-CPU aggregation on the userspace read path.
#[inline(always)]
unsafe fn ipv4_update_flow_stats(map: &LruHashMap<AddrPortV4, FlowStats>, key: &AddrPortV4, event: &IPv4Event) {
unsafe fn ipv4_update_flow_stats(map: &LruPerCpuHashMap<AddrPortV4, FlowStats>, key: &AddrPortV4, event: &IPv4Event) {
if let Some(status) = map.get_ptr_mut(key) {
(*status).bytes += event.packet_length as u64;
(*status).packets += 1;
@ -77,7 +77,7 @@ unsafe fn ipv4_update_flow_stats(map: &LruHashMap<AddrPortV4, FlowStats>, key: &
}
#[inline(always)]
unsafe fn ipv6_update_flow_status(map: &LruHashMap<AddrPortV6, FlowStats>, key: &AddrPortV6, event: &IPv6Event) {
unsafe fn ipv6_update_flow_status(map: &LruPerCpuHashMap<AddrPortV6, FlowStats>, key: &AddrPortV6, event: &IPv6Event) {
if let Some(status) = map.get_ptr_mut(key) {
(*status).bytes += event.packet_length as u64;
(*status).packets += 1;

View File

@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::net::{IpAddr, SocketAddrV4, SocketAddrV6};
use std::sync::Arc;
use aya::maps::{HashMap as AyaHashMap, MapData};
use aya::maps::{MapData, PerCpuHashMap};
use aya::{Ebpf, Pod};
use common::model::flow_stats::FlowStats;
use common::model::ip_address::{AddrPortV4, AddrPortV6};
@ -230,14 +230,14 @@ impl Statistics {
}
}
struct FlowMap<T> {
map: AyaHashMap<MapData, T, FlowStats>,
struct FlowMap<T: Pod> {
map: PerCpuHashMap<MapData, T, FlowStats>,
}
impl<T: NativeConvert + Pod> FlowMap<T> {
fn new(ebpf: &mut Ebpf, map_name: &str) -> Result<Self, Error> {
let map = ebpf.take_map(map_name).ok_or(EbpfError::MapNotFound)?;
let map = AyaHashMap::try_from(map).map_err(EbpfError::MapOperationError)?;
let map = PerCpuHashMap::try_from(map).map_err(EbpfError::MapOperationError)?;
Ok(Self { map })
}
@ -245,7 +245,18 @@ impl<T: NativeConvert + Pod> FlowMap<T> {
self.map
.iter()
.filter_map(Result::ok)
.map(|(key, value)| (key.into_native(), FlowStats::from(value)))
.map(|(key, per_cpu)| {
let agg = per_cpu.iter().fold(
FlowStats { bytes: 0, packets: 0, last_seen: 0 },
|mut acc, v| {
acc.bytes += v.bytes;
acc.packets += v.packets;
acc.last_seen = acc.last_seen.max(v.last_seen);
acc
},
);
(key.into_native(), agg)
})
.collect()
}
@ -254,9 +265,13 @@ impl<T: NativeConvert + Pod> FlowMap<T> {
.map
.iter()
.filter_map(|result| {
result
.ok()
.and_then(|(key, stats)| (now - stats.last_seen - boot_time > window).then_some(key))
result.ok().and_then(|(key, per_cpu)| {
let max_last_seen = per_cpu.iter().map(|s| s.last_seen).max().unwrap_or(0);
now.saturating_sub(max_last_seen)
.saturating_sub(boot_time)
.gt(&window)
.then_some(key)
})
})
.collect();
expired_keys.iter().for_each(|key| {