Skip to content

stats: Add support for RTM_NEWSTATS/RTM_GETSTATS#283

Draft
cathay4t wants to merge 1 commit into
rust-netlink:mainfrom
cathay4t:to_upstream
Draft

stats: Add support for RTM_NEWSTATS/RTM_GETSTATS#283
cathay4t wants to merge 1 commit into
rust-netlink:mainfrom
cathay4t:to_upstream

Conversation

@cathay4t

Copy link
Copy Markdown
Member

Introduce support for stats messages in the route netlink protocol,
enabling parsing and emission of per-interface statistics.

The new stats module provides:

  • StatsHeader with family, ifindex, and filter_mask fields
  • StatsMessage with header and NLA attributes
  • StatsAttribute for individual stats attributes
  • Xstats support including hardware stats, bridge stats, bond stats
    and per-address-family stats

Unit tests are included.

@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 62.24678% with 205 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.45%. Comparing base (1fb4bd7) to head (ea9c38f).
⚠️ Report is 67 commits behind head on main.

Files with missing lines Patch % Lines
src/stats/xstats.rs 60.27% 172 Missing ⚠️
src/stats/attribute.rs 57.77% 19 Missing ⚠️
src/message.rs 33.33% 14 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #283      +/-   ##
==========================================
+ Coverage   68.10%   68.45%   +0.34%     
==========================================
  Files         144      151       +7     
  Lines       10103    11297    +1194     
==========================================
+ Hits         6881     7733     +852     
- Misses       3222     3564     +342     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Introduce support for stats messages in the route netlink protocol,
enabling parsing and emission of per-interface statistics.

The new stats module provides:
- StatsHeader with family, ifindex, and filter_mask fields
- StatsMessage with header and NLA attributes
- StatsAttribute for individual stats attributes
- Xstats support including hardware stats, bridge stats, bond stats
  and per-address-family stats

Unit tests are included.

Signed-off-by: Gris Ge <cnfourt@gmail.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for Netlink stats messages (RTM_NEWSTATS and RTM_GETSTATS), implementing the parsing and serialization of various network interface statistics including bridge, bond, offload, and address family-specific stats. The review feedback highlights several critical correctness and performance improvements: correcting the nla_len field in HwSInfo::emit to exclude padding bytes per the Netlink protocol standard, using Stats64Buffer::new_checked to prevent potential out-of-bounds panics on malformed payloads, refactoring duplicated helper functions to ensure endianness safety, and replacing eager ok_or calls with lazy ok_or_else closures to avoid unnecessary error allocations.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/stats/xstats.rs
Comment on lines +690 to +704

pub fn emit(&self, buffer: &mut [u8]) {
let mut off = 0;
if let Some(v) = self.request {
let padded = 4 + 4; // NLA header + 4 bytes (u8 + 3 padding)
buffer[off..off + 2]
.copy_from_slice(&(padded as u16).to_ne_bytes());
buffer[off + 2..off + 4].copy_from_slice(&1u16.to_ne_bytes());
buffer[off + 4] = v;
off += padded;
}
if let Some(v) = self.used {
let padded = 4 + 4;
buffer[off..off + 2]
.copy_from_slice(&(padded as u16).to_ne_bytes());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The nla_len field of a Netlink Attribute (NLA) must not include padding bytes. It should be the sum of the header length (4 bytes) and the payload length (1 byte for u8), which is 5. Currently, the code writes padded (8) as the length, which violates the Netlink protocol standard and can cause parsing issues. Additionally, the padding bytes should be explicitly zeroed out to prevent leaking uninitialized memory.

        if let Some(v) = self.request {
            let len = 4 + 1;
            let padded = 8;
            buffer[off..off + 2]
                .copy_slice(&(len as u16).to_ne_bytes());
            buffer[off + 2..off + 4].copy_from_slice(&1u16.to_ne_bytes());
            buffer[off + 4] = v;
            buffer[off + 5..off + 8].fill(0);
            off += padded;
        }
        if let Some(v) = self.used {
            let len = 4 + 1;
            let padded = 8;
            buffer[off..off + 2]
                .copy_from_slice(&(len as u16).to_ne_bytes());
            buffer[off + 2..off + 4].copy_from_slice(&2u16.to_ne_bytes());
            buffer[off + 4] = v;
            buffer[off + 5..off + 8].fill(0);
        }
References
  1. The nla_len field of a Netlink Attribute (NLA) does not include padding bytes. It is the sum of the header length (4 bytes) and the payload length. Padding is added to the buffer for alignment but is not reflected in the nla_len value.

Comment thread src/stats/attribute.rs
Comment on lines +73 to +76
IFLA_STATS_LINK_64 => Self::Link64(
Stats64::parse(&Stats64Buffer::new(payload))
.context("invalid IFLA_STATS_LINK_64 value")?,
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using Stats64Buffer::new(payload) directly without checking the payload length can lead to out-of-bounds reads or panics if the kernel sends a malformed or shorter payload than LINK_STATS64_LEN (200 bytes). Use Stats64Buffer::new_checked(payload) instead to safely validate the payload length before parsing.

            IFLA_STATS_LINK_64 => {
                let err = "invalid IFLA_STATS_LINK_64 value";
                Self::Link64(
                    Stats64::parse(&Stats64Buffer::new_checked(payload).context(err)?)
                        .context(err)?,
                )
            }

Comment thread src/stats/xstats.rs
Comment on lines +211 to +215
return None;
}
fn le64(d: &[u8]) -> u64 {
let mut b = [0u8; 8];
b.copy_from_slice(&d[..8]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The helper function le64 is duplicated 5 times across different parse methods in this file. Additionally, the name le64 is a misnomer because it uses u64::from_ne_bytes (native endianness), which could be big-endian on big-endian architectures. Consider refactoring this into a single file-level helper function named read_u64 or ne_u64 using u64::from_ne_bytes(d[..8].try_into().unwrap()) to improve maintainability and correctness.

Comment thread src/stats/xstats.rs
Comment on lines +96 to +107
let val = nla.value();
result.push(match kind {
BRIDGE_XSTATS_MCAST => BridgeXstat::Mcast(
BridgeMcastStats::parse(val)
.ok_or(DecodeError::from("invalid bridge mcast stats"))?,
),
BRIDGE_XSTATS_STP => BridgeXstat::Stp(
BridgeStpXstats::parse(val)
.ok_or(DecodeError::from("invalid bridge stp stats"))?,
),
BRIDGE_XSTATS_VLAN => BridgeXstat::Vlan(
BridgeVlanXstats::parse(val)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using ok_or with DecodeError::from(...) eagerly evaluates and allocates the error even in the happy path. Use ok_or_else with a closure instead to avoid unnecessary allocations and improve performance.

            BRIDGE_XSTATS_MCAST => BridgeXstat::Mcast(
                BridgeMcastStats::parse(val)
                    .ok_or_else(|| DecodeError::from("invalid bridge mcast stats"))?,
            ),
            BRIDGE_XSTATS_STP => BridgeXstat::Stp(
                BridgeStpXstats::parse(val)
                    .ok_or_else(|| DecodeError::from("invalid bridge stp stats"))?,
            ),
            BRIDGE_XSTATS_VLAN => BridgeXstat::Vlan(
                BridgeVlanXstats::parse(val)
                    .ok_or_else(|| DecodeError::from("invalid bridge vlan stats"))?,
            )

Comment thread src/stats/xstats.rs
Comment on lines +569 to +579
let kind = nla.kind() & !NLA_F_NESTED;
let val = nla.value();
result.push(match kind {
IFLA_OFFLOAD_XSTATS_CPU_HIT => OffloadXstat::CpuHit(
HwStats64::parse(val)
.ok_or(DecodeError::from("invalid cpu hit stats"))?,
),
IFLA_OFFLOAD_XSTATS_HW_S_INFO => {
OffloadXstat::HwSInfo(HwSInfo::parse(val))
}
IFLA_OFFLOAD_XSTATS_L3_STATS => OffloadXstat::L3Stats(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using ok_or with DecodeError::from(...) eagerly evaluates and allocates the error even in the happy path. Use ok_or_else with a closure instead to avoid unnecessary allocations and improve performance.

            IFLA_OFFLOAD_XSTATS_CPU_HIT => OffloadXstat::CpuHit(
                HwStats64::parse(val)
                    .ok_or_else(|| DecodeError::from("invalid cpu hit stats"))?,
            ),
            IFLA_OFFLOAD_XSTATS_HW_S_INFO => {
                OffloadXstat::HwSInfo(HwSInfo::parse(val))
            },
            IFLA_OFFLOAD_XSTATS_L3_STATS => OffloadXstat::L3Stats(
                HwStats64::parse(val)
                    .ok_or_else(|| DecodeError::from("invalid l3 stats"))?,
            )

@cathay4t
cathay4t marked this pull request as draft July 13, 2026 05:45
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