@@ -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) ]
419437fn 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 {
0 commit comments