Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub mod nsid;
pub mod prefix;
pub mod route;
pub mod rule;
pub mod stats;
pub mod tc;

mod message;
Expand Down
40 changes: 38 additions & 2 deletions src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::{
prefix::{PrefixMessage, PrefixMessageBuffer},
route::{RouteHeader, RouteMessage, RouteMessageBuffer},
rule::{RuleMessage, RuleMessageBuffer},
stats::{StatsMessage, StatsMessageBuffer},
tc::{TcActionMessage, TcActionMessageBuffer, TcMessage, TcMessageBuffer},
};

Expand Down Expand Up @@ -66,8 +67,8 @@ const RTM_SETNEIGHTBL: u16 = 67;
const RTM_NEWNSID: u16 = 88;
const RTM_DELNSID: u16 = 89;
const RTM_GETNSID: u16 = 90;
// const RTM_NEWSTATS: u16 = 92;
// const RTM_GETSTATS: u16 = 94;
const RTM_NEWSTATS: u16 = 92;
const RTM_GETSTATS: u16 = 94;
// const RTM_NEWCACHEREPORT: u16 = 96;
const RTM_NEWCHAIN: u16 = 100;
const RTM_DELCHAIN: u16 = 101;
Expand Down Expand Up @@ -302,6 +303,21 @@ impl<'a, T: AsRef<[u8]> + ?Sized>
}
}

// Stats messages
RTM_NEWSTATS | RTM_GETSTATS => {
let err = "invalid stats message";
let msg = StatsMessage::parse(
&StatsMessageBuffer::new_checked(&buf.inner())
.context(err)?,
)
.context(err)?;
match message_type {
RTM_NEWSTATS => RouteNetlinkMessage::NewStats(msg),
RTM_GETSTATS => RouteNetlinkMessage::GetStats(msg),
_ => unreachable!(),
}
}

// ND ID Messages
RTM_NEWNSID | RTM_GETNSID | RTM_DELNSID => {
let err = "invalid nsid message";
Expand Down Expand Up @@ -365,6 +381,8 @@ pub enum RouteNetlinkMessage {
NewTrafficChain(TcMessage),
DelTrafficChain(TcMessage),
GetTrafficChain(TcMessage),
NewStats(StatsMessage),
GetStats(StatsMessage),
NewNsId(NsidMessage),
DelNsId(NsidMessage),
GetNsId(NsidMessage),
Expand Down Expand Up @@ -522,6 +540,14 @@ impl RouteNetlinkMessage {
matches!(self, RouteNetlinkMessage::DelRule(_))
}

pub fn is_new_stats(&self) -> bool {
matches!(self, RouteNetlinkMessage::NewStats(_))
}

pub fn is_get_stats(&self) -> bool {
matches!(self, RouteNetlinkMessage::GetStats(_))
}

pub fn message_type(&self) -> u16 {
match self {
Self::NewLink(_) => RTM_NEWLINK,
Expand Down Expand Up @@ -561,6 +587,8 @@ impl RouteNetlinkMessage {
Self::GetNsId(_) => RTM_GETNSID,
Self::NewNsId(_) => RTM_NEWNSID,
Self::DelNsId(_) => RTM_DELNSID,
Self::NewStats(_) => RTM_NEWSTATS,
Self::GetStats(_) => RTM_GETSTATS,
Self::GetRule(_) => RTM_GETRULE,
Self::NewRule(_) => RTM_NEWRULE,
Self::DelRule(_) => RTM_DELRULE,
Expand Down Expand Up @@ -613,6 +641,10 @@ impl Emitable for RouteNetlinkMessage {
| Self::DelNsId(ref msg)
| Self::GetNsId(ref msg) => msg.buffer_len(),

Self::NewStats(ref msg) | Self::GetStats(ref msg) => {
msg.buffer_len()
}

Self::NewRule(ref msg)
| Self::DelRule(ref msg)
| Self::GetRule(ref msg) => msg.buffer_len(),
Expand Down Expand Up @@ -667,6 +699,10 @@ impl Emitable for RouteNetlinkMessage {
| Self::DelNsId(ref msg)
| Self::GetNsId(ref msg) => msg.emit(buffer),

Self::NewStats(ref msg) | Self::GetStats(ref msg) => {
msg.emit(buffer)
}

Self::NewRule(ref msg)
| Self::DelRule(ref msg)
| Self::GetRule(ref msg) => msg.emit(buffer),
Expand Down
106 changes: 106 additions & 0 deletions src/stats/attribute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: MIT

use netlink_packet_core::{
DecodeError, DefaultNla, Emitable, ErrorContext, Nla, NlaBuffer, Parseable,
};

use super::xstats::{
parse_offload_xstats_inner, AfSpecStats, LinkXstatGroup, OffloadXstat,
VecLinkXstats,
};
use crate::link::{Stats64, Stats64Buffer};

const IFLA_STATS_LINK_64: u16 = 1;
const IFLA_STATS_LINK_XSTATS: u16 = 2;
const IFLA_STATS_LINK_XSTATS_SLAVE: u16 = 3;
const IFLA_STATS_LINK_OFFLOAD_XSTATS: u16 = 4;
const IFLA_STATS_AF_SPEC: u16 = 5;

#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub enum StatsAttribute {
Link64(Stats64),
LinkXstats(Vec<LinkXstatGroup>),
LinkXstatsPort(Vec<LinkXstatGroup>),
LinkOffloadXstats(Vec<OffloadXstat>),
AfSpec(AfSpecStats),
Other(DefaultNla),
}

impl Nla for StatsAttribute {
fn value_len(&self) -> usize {
match self {
Self::Link64(v) => v.buffer_len(),
Self::LinkXstats(v) | Self::LinkXstatsPort(v) => {
v.as_slice().buffer_len()
}
Self::LinkOffloadXstats(v) => v.as_slice().buffer_len(),
Self::AfSpec(v) => v.buffer_len(),
Self::Other(attr) => attr.value_len(),
}
}

fn emit_value(&self, buffer: &mut [u8]) {
match self {
Self::Link64(v) => v.emit(buffer),
Self::LinkXstats(v) | Self::LinkXstatsPort(v) => {
v.as_slice().emit(buffer)
}
Self::LinkOffloadXstats(v) => v.as_slice().emit(buffer),
Self::AfSpec(v) => v.emit(buffer),
Self::Other(attr) => attr.emit_value(buffer),
}
}

fn kind(&self) -> u16 {
match self {
Self::Link64(_) => IFLA_STATS_LINK_64,
Self::LinkXstats(_) => IFLA_STATS_LINK_XSTATS,
Self::LinkXstatsPort(_) => IFLA_STATS_LINK_XSTATS_SLAVE,
Self::LinkOffloadXstats(_) => IFLA_STATS_LINK_OFFLOAD_XSTATS,
Self::AfSpec(_) => IFLA_STATS_AF_SPEC,
Self::Other(attr) => attr.kind(),
}
}
}

impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<NlaBuffer<&'a T>>
for StatsAttribute
{
fn parse(buf: &NlaBuffer<&'a T>) -> Result<Self, DecodeError> {
let payload = buf.value();
Ok(match buf.kind() {
IFLA_STATS_LINK_64 => Self::Link64(
Stats64::parse(&Stats64Buffer::new(payload))
.context("invalid IFLA_STATS_LINK_64 value")?,
),
Comment on lines +73 to +76

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)?,
                )
            }

IFLA_STATS_LINK_XSTATS => {
let err = "invalid IFLA_STATS_LINK_XSTATS value";
Self::LinkXstats(
VecLinkXstats::parse(&NlaBuffer::new(payload))
.context(err)?
.0,
)
}
IFLA_STATS_LINK_XSTATS_SLAVE => {
let err = "invalid IFLA_STATS_LINK_XSTATS_SLAVE value";
Self::LinkXstatsPort(
VecLinkXstats::parse(&NlaBuffer::new(payload))
.context(err)?
.0,
)
}
IFLA_STATS_LINK_OFFLOAD_XSTATS => {
let err = "invalid IFLA_STATS_LINK_OFFLOAD_XSTATS value";
Self::LinkOffloadXstats(
parse_offload_xstats_inner(payload).context(err)?,
)
}
IFLA_STATS_AF_SPEC => Self::AfSpec(AfSpecStats::parse(payload)),
kind => Self::Other(
DefaultNla::parse(buf)
.context(format!("unknown IFLA_STATS type {kind}"))?,
),
})
}
}
58 changes: 58 additions & 0 deletions src/stats/header.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT

use netlink_packet_core::{
DecodeError, Emitable, NlaBuffer, NlasIterator, Parseable,
};

use crate::AddressFamily;

const STATS_HEADER_LEN: usize = 12;

buffer!(StatsMessageBuffer(STATS_HEADER_LEN) {
family: (u8, 0),
pad1: (u8, 1),
pad2: (u16, 2..4),
ifindex: (u32, 4..8),
filter_mask: (u32, 8..STATS_HEADER_LEN),
payload: (slice, STATS_HEADER_LEN..),
});

impl<'a, T: AsRef<[u8]> + ?Sized> StatsMessageBuffer<&'a T> {
pub fn attributes(
&self,
) -> impl Iterator<Item = Result<NlaBuffer<&'a [u8]>, DecodeError>> {
NlasIterator::new(self.payload())
}
}

#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct StatsHeader {
pub family: AddressFamily,
pub ifindex: u32,
pub filter_mask: u32,
}

impl Emitable for StatsHeader {
fn buffer_len(&self) -> usize {
STATS_HEADER_LEN
}

fn emit(&self, buffer: &mut [u8]) {
let mut packet = StatsMessageBuffer::new(buffer);
packet.set_family(self.family.into());
packet.set_pad1(0);
packet.set_pad2(0);
packet.set_ifindex(self.ifindex);
packet.set_filter_mask(self.filter_mask);
}
}

impl<T: AsRef<[u8]>> Parseable<StatsMessageBuffer<T>> for StatsHeader {
fn parse(buf: &StatsMessageBuffer<T>) -> Result<Self, DecodeError> {
Ok(StatsHeader {
family: buf.family().into(),
ifindex: buf.ifindex(),
filter_mask: buf.filter_mask(),
})
}
}
50 changes: 50 additions & 0 deletions src/stats/message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT

use netlink_packet_core::{DecodeError, Emitable, ErrorContext, Parseable};

use crate::stats::{StatsAttribute, StatsHeader, StatsMessageBuffer};

#[derive(Debug, PartialEq, Eq, Clone, Default)]
#[non_exhaustive]
pub struct StatsMessage {
pub header: StatsHeader,
pub attributes: Vec<StatsAttribute>,
}

impl<'a, T: AsRef<[u8]> + 'a> Parseable<StatsMessageBuffer<&'a T>>
for StatsMessage
{
fn parse(buf: &StatsMessageBuffer<&'a T>) -> Result<Self, DecodeError> {
Ok(Self {
header: StatsHeader::parse(buf)
.context("failed to parse stats message header")?,
attributes: Vec::<StatsAttribute>::parse(buf)
.context("failed to parse stats message NLAs")?,
})
}
}

impl<'a, T: AsRef<[u8]> + 'a> Parseable<StatsMessageBuffer<&'a T>>
for Vec<StatsAttribute>
{
fn parse(buf: &StatsMessageBuffer<&'a T>) -> Result<Self, DecodeError> {
let mut attributes = vec![];
for nla_buf in buf.attributes() {
attributes.push(StatsAttribute::parse(&nla_buf?)?);
}
Ok(attributes)
}
}

impl Emitable for StatsMessage {
fn buffer_len(&self) -> usize {
self.header.buffer_len() + self.attributes.as_slice().buffer_len()
}

fn emit(&self, buffer: &mut [u8]) {
self.header.emit(buffer);
self.attributes
.as_slice()
.emit(&mut buffer[self.header.buffer_len()..]);
}
}
20 changes: 20 additions & 0 deletions src/stats/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT

mod attribute;
mod header;
mod message;
mod xstats;

#[cfg(test)]
mod tests;

pub use self::{
attribute::StatsAttribute,
header::{StatsHeader, StatsMessageBuffer},
message::StatsMessage,
xstats::{
AfSpecStatEntry, AfSpecStats, Bond3adStats, BondXstat,
BridgeMcastStats, BridgeStpXstats, BridgeVlanXstats, BridgeXstat,
HwSInfo, HwStats64, LinkXstatGroup, OffloadXstat,
},
};
Loading
Loading