Skip to content

Commit ea9c38f

Browse files
committed
stats: Add support for RTM_NEWSTATS/RTM_GETSTATS
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>
1 parent d66448f commit ea9c38f

8 files changed

Lines changed: 1384 additions & 2 deletions

File tree

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub mod nsid;
3131
pub mod prefix;
3232
pub mod route;
3333
pub mod rule;
34+
pub mod stats;
3435
pub mod tc;
3536

3637
mod message;

src/message.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::{
1414
prefix::{PrefixMessage, PrefixMessageBuffer},
1515
route::{RouteHeader, RouteMessage, RouteMessageBuffer},
1616
rule::{RuleMessage, RuleMessageBuffer},
17+
stats::{StatsMessage, StatsMessageBuffer},
1718
tc::{TcActionMessage, TcActionMessageBuffer, TcMessage, TcMessageBuffer},
1819
};
1920

@@ -66,8 +67,8 @@ const RTM_SETNEIGHTBL: u16 = 67;
6667
const RTM_NEWNSID: u16 = 88;
6768
const RTM_DELNSID: u16 = 89;
6869
const RTM_GETNSID: u16 = 90;
69-
// const RTM_NEWSTATS: u16 = 92;
70-
// const RTM_GETSTATS: u16 = 94;
70+
const RTM_NEWSTATS: u16 = 92;
71+
const RTM_GETSTATS: u16 = 94;
7172
// const RTM_NEWCACHEREPORT: u16 = 96;
7273
const RTM_NEWCHAIN: u16 = 100;
7374
const RTM_DELCHAIN: u16 = 101;
@@ -302,6 +303,21 @@ impl<'a, T: AsRef<[u8]> + ?Sized>
302303
}
303304
}
304305

306+
// Stats messages
307+
RTM_NEWSTATS | RTM_GETSTATS => {
308+
let err = "invalid stats message";
309+
let msg = StatsMessage::parse(
310+
&StatsMessageBuffer::new_checked(&buf.inner())
311+
.context(err)?,
312+
)
313+
.context(err)?;
314+
match message_type {
315+
RTM_NEWSTATS => RouteNetlinkMessage::NewStats(msg),
316+
RTM_GETSTATS => RouteNetlinkMessage::GetStats(msg),
317+
_ => unreachable!(),
318+
}
319+
}
320+
305321
// ND ID Messages
306322
RTM_NEWNSID | RTM_GETNSID | RTM_DELNSID => {
307323
let err = "invalid nsid message";
@@ -365,6 +381,8 @@ pub enum RouteNetlinkMessage {
365381
NewTrafficChain(TcMessage),
366382
DelTrafficChain(TcMessage),
367383
GetTrafficChain(TcMessage),
384+
NewStats(StatsMessage),
385+
GetStats(StatsMessage),
368386
NewNsId(NsidMessage),
369387
DelNsId(NsidMessage),
370388
GetNsId(NsidMessage),
@@ -522,6 +540,14 @@ impl RouteNetlinkMessage {
522540
matches!(self, RouteNetlinkMessage::DelRule(_))
523541
}
524542

543+
pub fn is_new_stats(&self) -> bool {
544+
matches!(self, RouteNetlinkMessage::NewStats(_))
545+
}
546+
547+
pub fn is_get_stats(&self) -> bool {
548+
matches!(self, RouteNetlinkMessage::GetStats(_))
549+
}
550+
525551
pub fn message_type(&self) -> u16 {
526552
match self {
527553
Self::NewLink(_) => RTM_NEWLINK,
@@ -561,6 +587,8 @@ impl RouteNetlinkMessage {
561587
Self::GetNsId(_) => RTM_GETNSID,
562588
Self::NewNsId(_) => RTM_NEWNSID,
563589
Self::DelNsId(_) => RTM_DELNSID,
590+
Self::NewStats(_) => RTM_NEWSTATS,
591+
Self::GetStats(_) => RTM_GETSTATS,
564592
Self::GetRule(_) => RTM_GETRULE,
565593
Self::NewRule(_) => RTM_NEWRULE,
566594
Self::DelRule(_) => RTM_DELRULE,
@@ -613,6 +641,10 @@ impl Emitable for RouteNetlinkMessage {
613641
| Self::DelNsId(ref msg)
614642
| Self::GetNsId(ref msg) => msg.buffer_len(),
615643

644+
Self::NewStats(ref msg) | Self::GetStats(ref msg) => {
645+
msg.buffer_len()
646+
}
647+
616648
Self::NewRule(ref msg)
617649
| Self::DelRule(ref msg)
618650
| Self::GetRule(ref msg) => msg.buffer_len(),
@@ -667,6 +699,10 @@ impl Emitable for RouteNetlinkMessage {
667699
| Self::DelNsId(ref msg)
668700
| Self::GetNsId(ref msg) => msg.emit(buffer),
669701

702+
Self::NewStats(ref msg) | Self::GetStats(ref msg) => {
703+
msg.emit(buffer)
704+
}
705+
670706
Self::NewRule(ref msg)
671707
| Self::DelRule(ref msg)
672708
| Self::GetRule(ref msg) => msg.emit(buffer),

src/stats/attribute.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
use netlink_packet_core::{
4+
DecodeError, DefaultNla, Emitable, ErrorContext, Nla, NlaBuffer, Parseable,
5+
};
6+
7+
use super::xstats::{
8+
parse_offload_xstats_inner, AfSpecStats, LinkXstatGroup, OffloadXstat,
9+
VecLinkXstats,
10+
};
11+
use crate::link::{Stats64, Stats64Buffer};
12+
13+
const IFLA_STATS_LINK_64: u16 = 1;
14+
const IFLA_STATS_LINK_XSTATS: u16 = 2;
15+
const IFLA_STATS_LINK_XSTATS_SLAVE: u16 = 3;
16+
const IFLA_STATS_LINK_OFFLOAD_XSTATS: u16 = 4;
17+
const IFLA_STATS_AF_SPEC: u16 = 5;
18+
19+
#[derive(Debug, PartialEq, Eq, Clone)]
20+
#[non_exhaustive]
21+
pub enum StatsAttribute {
22+
Link64(Stats64),
23+
LinkXstats(Vec<LinkXstatGroup>),
24+
LinkXstatsPort(Vec<LinkXstatGroup>),
25+
LinkOffloadXstats(Vec<OffloadXstat>),
26+
AfSpec(AfSpecStats),
27+
Other(DefaultNla),
28+
}
29+
30+
impl Nla for StatsAttribute {
31+
fn value_len(&self) -> usize {
32+
match self {
33+
Self::Link64(v) => v.buffer_len(),
34+
Self::LinkXstats(v) | Self::LinkXstatsPort(v) => {
35+
v.as_slice().buffer_len()
36+
}
37+
Self::LinkOffloadXstats(v) => v.as_slice().buffer_len(),
38+
Self::AfSpec(v) => v.buffer_len(),
39+
Self::Other(attr) => attr.value_len(),
40+
}
41+
}
42+
43+
fn emit_value(&self, buffer: &mut [u8]) {
44+
match self {
45+
Self::Link64(v) => v.emit(buffer),
46+
Self::LinkXstats(v) | Self::LinkXstatsPort(v) => {
47+
v.as_slice().emit(buffer)
48+
}
49+
Self::LinkOffloadXstats(v) => v.as_slice().emit(buffer),
50+
Self::AfSpec(v) => v.emit(buffer),
51+
Self::Other(attr) => attr.emit_value(buffer),
52+
}
53+
}
54+
55+
fn kind(&self) -> u16 {
56+
match self {
57+
Self::Link64(_) => IFLA_STATS_LINK_64,
58+
Self::LinkXstats(_) => IFLA_STATS_LINK_XSTATS,
59+
Self::LinkXstatsPort(_) => IFLA_STATS_LINK_XSTATS_SLAVE,
60+
Self::LinkOffloadXstats(_) => IFLA_STATS_LINK_OFFLOAD_XSTATS,
61+
Self::AfSpec(_) => IFLA_STATS_AF_SPEC,
62+
Self::Other(attr) => attr.kind(),
63+
}
64+
}
65+
}
66+
67+
impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<NlaBuffer<&'a T>>
68+
for StatsAttribute
69+
{
70+
fn parse(buf: &NlaBuffer<&'a T>) -> Result<Self, DecodeError> {
71+
let payload = buf.value();
72+
Ok(match buf.kind() {
73+
IFLA_STATS_LINK_64 => Self::Link64(
74+
Stats64::parse(&Stats64Buffer::new(payload))
75+
.context("invalid IFLA_STATS_LINK_64 value")?,
76+
),
77+
IFLA_STATS_LINK_XSTATS => {
78+
let err = "invalid IFLA_STATS_LINK_XSTATS value";
79+
Self::LinkXstats(
80+
VecLinkXstats::parse(&NlaBuffer::new(payload))
81+
.context(err)?
82+
.0,
83+
)
84+
}
85+
IFLA_STATS_LINK_XSTATS_SLAVE => {
86+
let err = "invalid IFLA_STATS_LINK_XSTATS_SLAVE value";
87+
Self::LinkXstatsPort(
88+
VecLinkXstats::parse(&NlaBuffer::new(payload))
89+
.context(err)?
90+
.0,
91+
)
92+
}
93+
IFLA_STATS_LINK_OFFLOAD_XSTATS => {
94+
let err = "invalid IFLA_STATS_LINK_OFFLOAD_XSTATS value";
95+
Self::LinkOffloadXstats(
96+
parse_offload_xstats_inner(payload).context(err)?,
97+
)
98+
}
99+
IFLA_STATS_AF_SPEC => Self::AfSpec(AfSpecStats::parse(payload)),
100+
kind => Self::Other(
101+
DefaultNla::parse(buf)
102+
.context(format!("unknown IFLA_STATS type {kind}"))?,
103+
),
104+
})
105+
}
106+
}

src/stats/header.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
use netlink_packet_core::{
4+
DecodeError, Emitable, NlaBuffer, NlasIterator, Parseable,
5+
};
6+
7+
use crate::AddressFamily;
8+
9+
const STATS_HEADER_LEN: usize = 12;
10+
11+
buffer!(StatsMessageBuffer(STATS_HEADER_LEN) {
12+
family: (u8, 0),
13+
pad1: (u8, 1),
14+
pad2: (u16, 2..4),
15+
ifindex: (u32, 4..8),
16+
filter_mask: (u32, 8..STATS_HEADER_LEN),
17+
payload: (slice, STATS_HEADER_LEN..),
18+
});
19+
20+
impl<'a, T: AsRef<[u8]> + ?Sized> StatsMessageBuffer<&'a T> {
21+
pub fn attributes(
22+
&self,
23+
) -> impl Iterator<Item = Result<NlaBuffer<&'a [u8]>, DecodeError>> {
24+
NlasIterator::new(self.payload())
25+
}
26+
}
27+
28+
#[derive(Debug, PartialEq, Eq, Clone, Default)]
29+
pub struct StatsHeader {
30+
pub family: AddressFamily,
31+
pub ifindex: u32,
32+
pub filter_mask: u32,
33+
}
34+
35+
impl Emitable for StatsHeader {
36+
fn buffer_len(&self) -> usize {
37+
STATS_HEADER_LEN
38+
}
39+
40+
fn emit(&self, buffer: &mut [u8]) {
41+
let mut packet = StatsMessageBuffer::new(buffer);
42+
packet.set_family(self.family.into());
43+
packet.set_pad1(0);
44+
packet.set_pad2(0);
45+
packet.set_ifindex(self.ifindex);
46+
packet.set_filter_mask(self.filter_mask);
47+
}
48+
}
49+
50+
impl<T: AsRef<[u8]>> Parseable<StatsMessageBuffer<T>> for StatsHeader {
51+
fn parse(buf: &StatsMessageBuffer<T>) -> Result<Self, DecodeError> {
52+
Ok(StatsHeader {
53+
family: buf.family().into(),
54+
ifindex: buf.ifindex(),
55+
filter_mask: buf.filter_mask(),
56+
})
57+
}
58+
}

src/stats/message.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
use netlink_packet_core::{DecodeError, Emitable, ErrorContext, Parseable};
4+
5+
use crate::stats::{StatsAttribute, StatsHeader, StatsMessageBuffer};
6+
7+
#[derive(Debug, PartialEq, Eq, Clone, Default)]
8+
#[non_exhaustive]
9+
pub struct StatsMessage {
10+
pub header: StatsHeader,
11+
pub attributes: Vec<StatsAttribute>,
12+
}
13+
14+
impl<'a, T: AsRef<[u8]> + 'a> Parseable<StatsMessageBuffer<&'a T>>
15+
for StatsMessage
16+
{
17+
fn parse(buf: &StatsMessageBuffer<&'a T>) -> Result<Self, DecodeError> {
18+
Ok(Self {
19+
header: StatsHeader::parse(buf)
20+
.context("failed to parse stats message header")?,
21+
attributes: Vec::<StatsAttribute>::parse(buf)
22+
.context("failed to parse stats message NLAs")?,
23+
})
24+
}
25+
}
26+
27+
impl<'a, T: AsRef<[u8]> + 'a> Parseable<StatsMessageBuffer<&'a T>>
28+
for Vec<StatsAttribute>
29+
{
30+
fn parse(buf: &StatsMessageBuffer<&'a T>) -> Result<Self, DecodeError> {
31+
let mut attributes = vec![];
32+
for nla_buf in buf.attributes() {
33+
attributes.push(StatsAttribute::parse(&nla_buf?)?);
34+
}
35+
Ok(attributes)
36+
}
37+
}
38+
39+
impl Emitable for StatsMessage {
40+
fn buffer_len(&self) -> usize {
41+
self.header.buffer_len() + self.attributes.as_slice().buffer_len()
42+
}
43+
44+
fn emit(&self, buffer: &mut [u8]) {
45+
self.header.emit(buffer);
46+
self.attributes
47+
.as_slice()
48+
.emit(&mut buffer[self.header.buffer_len()..]);
49+
}
50+
}

src/stats/mod.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
mod attribute;
4+
mod header;
5+
mod message;
6+
mod xstats;
7+
8+
#[cfg(test)]
9+
mod tests;
10+
11+
pub use self::{
12+
attribute::StatsAttribute,
13+
header::{StatsHeader, StatsMessageBuffer},
14+
message::StatsMessage,
15+
xstats::{
16+
AfSpecStatEntry, AfSpecStats, Bond3adStats, BondXstat,
17+
BridgeMcastStats, BridgeStpXstats, BridgeVlanXstats, BridgeXstat,
18+
HwSInfo, HwStats64, LinkXstatGroup, OffloadXstat,
19+
},
20+
};

0 commit comments

Comments
 (0)