From 2ec5f1736be442ee1b5d46ac4128e46123f79208 Mon Sep 17 00:00:00 2001 From: Gris Ge Date: Fri, 24 Sep 2021 17:30:32 +0800 Subject: [PATCH] ethtool: Add ring support Equivalent to `ethtool -g ` command. Example code included. Signed-off-by: Gris Ge --- ethtool/examples/dump_rings.rs | 29 +++++++ ethtool/src/handle.rs | 5 ++ ethtool/src/lib.rs | 2 + ethtool/src/message.rs | 28 +++++++ ethtool/src/ring/attr.rs | 137 +++++++++++++++++++++++++++++++++ ethtool/src/ring/get.rs | 30 ++++++++ ethtool/src/ring/handle.rs | 14 ++++ ethtool/src/ring/mod.rs | 9 +++ 8 files changed, 254 insertions(+) create mode 100644 ethtool/examples/dump_rings.rs create mode 100644 ethtool/src/ring/attr.rs create mode 100644 ethtool/src/ring/get.rs create mode 100644 ethtool/src/ring/handle.rs create mode 100644 ethtool/src/ring/mod.rs diff --git a/ethtool/examples/dump_rings.rs b/ethtool/examples/dump_rings.rs new file mode 100644 index 00000000..43ddf66f --- /dev/null +++ b/ethtool/examples/dump_rings.rs @@ -0,0 +1,29 @@ +use ethtool; +use futures::stream::TryStreamExt; +use tokio; + +// Once we find a way to load netsimdev kernel module in CI, we can convert this +// to a test +fn main() { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_io() + .build() + .unwrap(); + rt.block_on(get_ring(None)); +} + +async fn get_ring(iface_name: Option<&str>) { + let (connection, mut handle, _) = ethtool::new_connection().unwrap(); + tokio::spawn(connection); + + let mut ring_handle = handle.ring().get(iface_name).execute().await; + + let mut msgs = Vec::new(); + while let Some(msg) = ring_handle.try_next().await.unwrap() { + msgs.push(msg); + } + assert!(msgs.len() > 0); + for msg in msgs { + println!("{:?}", msg); + } +} diff --git a/ethtool/src/handle.rs b/ethtool/src/handle.rs index 525b3816..6dea7a04 100644 --- a/ethtool/src/handle.rs +++ b/ethtool/src/handle.rs @@ -11,6 +11,7 @@ use crate::{ EthtoolLinkModeHandle, EthtoolMessage, EthtoolPauseHandle, + EthtoolRingHandle, }; #[derive(Clone, Debug)] @@ -35,6 +36,10 @@ impl EthtoolHandle { EthtoolLinkModeHandle::new(self.clone()) } + pub fn ring(&mut self) -> EthtoolRingHandle { + EthtoolRingHandle::new(self.clone()) + } + pub async fn request( &mut self, message: NetlinkMessage>, diff --git a/ethtool/src/lib.rs b/ethtool/src/lib.rs index 7d003bf0..8454b750 100644 --- a/ethtool/src/lib.rs +++ b/ethtool/src/lib.rs @@ -7,6 +7,7 @@ mod link_mode; mod macros; mod message; mod pause; +mod ring; pub use connection::new_connection; pub use error::EthtoolError; @@ -31,5 +32,6 @@ pub use pause::{ EthtoolPauseHandle, EthtoolPauseStatAttr, }; +pub use ring::{EthtoolRingAttr, EthtoolRingGetRequest, EthtoolRingHandle}; pub(crate) use handle::ethtool_execute; diff --git a/ethtool/src/message.rs b/ethtool/src/message.rs index b45221ff..bdce856a 100644 --- a/ethtool/src/message.rs +++ b/ethtool/src/message.rs @@ -6,6 +6,7 @@ use crate::{ feature::{parse_feature_nlas, EthtoolFeatureAttr}, link_mode::{parse_link_mode_nlas, EthtoolLinkModeAttr}, pause::{parse_pause_nlas, EthtoolPauseAttr}, + ring::{parse_ring_nlas, EthtoolRingAttr}, EthtoolHeader, }; @@ -15,6 +16,8 @@ const ETHTOOL_MSG_FEATURES_GET: u8 = 11; const ETHTOOL_MSG_FEATURES_GET_REPLY: u8 = 11; const ETHTOOL_MSG_LINKMODES_GET: u8 = 4; const ETHTOOL_MSG_LINKMODES_GET_REPLY: u8 = 4; +const ETHTOOL_MSG_RINGS_GET: u8 = 15; +const ETHTOOL_MSG_RINGS_GET_REPLY: u8 = 16; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum EthtoolCmd { @@ -24,6 +27,8 @@ pub enum EthtoolCmd { FeatureGetReply, LinkModeGet, LinkModeGetReply, + RingGet, + RingGetReply, } impl From for u8 { @@ -35,6 +40,8 @@ impl From for u8 { EthtoolCmd::FeatureGetReply => ETHTOOL_MSG_FEATURES_GET_REPLY, EthtoolCmd::LinkModeGet => ETHTOOL_MSG_LINKMODES_GET, EthtoolCmd::LinkModeGetReply => ETHTOOL_MSG_LINKMODES_GET_REPLY, + EthtoolCmd::RingGet => ETHTOOL_MSG_RINGS_GET, + EthtoolCmd::RingGetReply => ETHTOOL_MSG_RINGS_GET_REPLY, } } } @@ -44,6 +51,7 @@ pub enum EthtoolAttr { Pause(EthtoolPauseAttr), Feature(EthtoolFeatureAttr), LinkMode(EthtoolLinkModeAttr), + Ring(EthtoolRingAttr), } impl Nla for EthtoolAttr { @@ -52,6 +60,7 @@ impl Nla for EthtoolAttr { Self::Pause(attr) => attr.value_len(), Self::Feature(attr) => attr.value_len(), Self::LinkMode(attr) => attr.value_len(), + Self::Ring(attr) => attr.value_len(), } } @@ -60,6 +69,7 @@ impl Nla for EthtoolAttr { Self::Pause(attr) => attr.kind(), Self::Feature(attr) => attr.kind(), Self::LinkMode(attr) => attr.kind(), + Self::Ring(attr) => attr.kind(), } } @@ -68,6 +78,7 @@ impl Nla for EthtoolAttr { Self::Pause(attr) => attr.emit_value(buffer), Self::Feature(attr) => attr.emit_value(buffer), Self::LinkMode(attr) => attr.emit_value(buffer), + Self::Ring(attr) => attr.emit_value(buffer), } } } @@ -131,6 +142,19 @@ impl EthtoolMessage { nlas, } } + + pub fn new_ring_get(iface_name: Option<&str>) -> Self { + let nlas = match iface_name { + Some(s) => vec![EthtoolAttr::Ring(EthtoolRingAttr::Header(vec![ + EthtoolHeader::DevName(s.to_string()), + ]))], + None => vec![EthtoolAttr::Ring(EthtoolRingAttr::Header(vec![]))], + }; + EthtoolMessage { + cmd: EthtoolCmd::RingGet, + nlas, + } + } } impl Emitable for EthtoolMessage { @@ -158,6 +182,10 @@ impl ParseableParametrized<[u8], GenlHeader> for EthtoolMessage { cmd: EthtoolCmd::LinkModeGetReply, nlas: parse_link_mode_nlas(buffer)?, }, + ETHTOOL_MSG_RINGS_GET_REPLY => Self { + cmd: EthtoolCmd::RingGetReply, + nlas: parse_ring_nlas(buffer)?, + }, cmd => { return Err(DecodeError::from(format!( "Unsupported ethtool reply command: {}", diff --git a/ethtool/src/ring/attr.rs b/ethtool/src/ring/attr.rs new file mode 100644 index 00000000..e5280a82 --- /dev/null +++ b/ethtool/src/ring/attr.rs @@ -0,0 +1,137 @@ +use anyhow::Context; +use byteorder::{ByteOrder, NativeEndian}; +use netlink_packet_utils::{ + nla::{DefaultNla, Nla, NlaBuffer, NlasIterator, NLA_F_NESTED}, + parsers::parse_u32, + DecodeError, + Emitable, + Parseable, +}; + +use crate::{EthtoolAttr, EthtoolHeader}; + +const ETHTOOL_A_RINGS_HEADER: u16 = 1; +const ETHTOOL_A_RINGS_RX_MAX: u16 = 2; +const ETHTOOL_A_RINGS_RX_MINI_MAX: u16 = 3; +const ETHTOOL_A_RINGS_RX_JUMBO_MAX: u16 = 4; +const ETHTOOL_A_RINGS_TX_MAX: u16 = 5; +const ETHTOOL_A_RINGS_RX: u16 = 6; +const ETHTOOL_A_RINGS_RX_MINI: u16 = 7; +const ETHTOOL_A_RINGS_RX_JUMBO: u16 = 8; +const ETHTOOL_A_RINGS_TX: u16 = 9; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum EthtoolRingAttr { + Header(Vec), + RxMax(u32), + RxMiniMax(u32), + RxJumboMax(u32), + TxMax(u32), + Rx(u32), + RxMini(u32), + RxJumbo(u32), + Tx(u32), + Other(DefaultNla), +} + +impl Nla for EthtoolRingAttr { + fn value_len(&self) -> usize { + match self { + Self::Header(hdrs) => hdrs.as_slice().buffer_len(), + Self::RxMax(_) + | Self::RxMiniMax(_) + | Self::RxJumboMax(_) + | Self::TxMax(_) + | Self::Rx(_) + | Self::RxMini(_) + | Self::RxJumbo(_) + | Self::Tx(_) => 4, + Self::Other(attr) => attr.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + Self::Header(_) => ETHTOOL_A_RINGS_HEADER | NLA_F_NESTED, + Self::RxMax(_) => ETHTOOL_A_RINGS_RX_MAX, + Self::RxMiniMax(_) => ETHTOOL_A_RINGS_RX_MINI_MAX, + Self::RxJumboMax(_) => ETHTOOL_A_RINGS_RX_JUMBO_MAX, + Self::TxMax(_) => ETHTOOL_A_RINGS_TX_MAX, + Self::Rx(_) => ETHTOOL_A_RINGS_RX, + Self::RxMini(_) => ETHTOOL_A_RINGS_RX_MINI, + Self::RxJumbo(_) => ETHTOOL_A_RINGS_RX_JUMBO, + Self::Tx(_) => ETHTOOL_A_RINGS_TX, + Self::Other(attr) => attr.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + Self::Header(ref nlas) => nlas.as_slice().emit(buffer), + Self::RxMax(d) + | Self::RxMiniMax(d) + | Self::RxJumboMax(d) + | Self::TxMax(d) + | Self::Rx(d) + | Self::RxMini(d) + | Self::RxJumbo(d) + | Self::Tx(d) => NativeEndian::write_u32(buffer, *d), + Self::Other(ref attr) => attr.emit(buffer), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for EthtoolRingAttr { + fn parse(buf: &NlaBuffer<&'a T>) -> Result { + let payload = buf.value(); + Ok(match buf.kind() { + ETHTOOL_A_RINGS_HEADER => { + let mut nlas = Vec::new(); + let error_msg = "failed to parse ring header attributes"; + for nla in NlasIterator::new(payload) { + let nla = &nla.context(error_msg)?; + let parsed = EthtoolHeader::parse(nla).context(error_msg)?; + nlas.push(parsed); + } + Self::Header(nlas) + } + ETHTOOL_A_RINGS_RX_MAX => { + Self::RxMax(parse_u32(payload).context("Invalid ETHTOOL_A_RINGS_RX_MAX value")?) + } + + ETHTOOL_A_RINGS_RX_MINI_MAX => Self::RxMiniMax( + parse_u32(payload).context("Invalid ETHTOOL_A_RINGS_RX_MINI_MAX value")?, + ), + ETHTOOL_A_RINGS_RX_JUMBO_MAX => Self::RxJumboMax( + parse_u32(payload).context("Invalid ETHTOOL_A_RINGS_RX_JUMBO_MAX value")?, + ), + ETHTOOL_A_RINGS_TX_MAX => { + Self::TxMax(parse_u32(payload).context("Invalid ETHTOOL_A_RINGS_TX_MAX value")?) + } + ETHTOOL_A_RINGS_RX => { + Self::Rx(parse_u32(payload).context("Invalid ETHTOOL_A_RINGS_RX value")?) + } + ETHTOOL_A_RINGS_RX_MINI => { + Self::RxMini(parse_u32(payload).context("Invalid ETHTOOL_A_RINGS_RX_MINI value")?) + } + ETHTOOL_A_RINGS_RX_JUMBO => { + Self::RxJumbo(parse_u32(payload).context("Invalid ETHTOOL_A_RINGS_RX_JUMBO value")?) + } + ETHTOOL_A_RINGS_TX => { + Self::Tx(parse_u32(payload).context("Invalid ETHTOOL_A_RINGS_TX value")?) + } + _ => Self::Other(DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?), + }) + } +} + +pub(crate) fn parse_ring_nlas(buffer: &[u8]) -> Result, DecodeError> { + let mut nlas = Vec::new(); + for nla in NlasIterator::new(buffer) { + let error_msg = format!("Failed to parse ethtool ring message attribute {:?}", nla); + let nla = &nla.context(error_msg.clone())?; + let parsed = EthtoolRingAttr::parse(nla).context(error_msg)?; + nlas.push(EthtoolAttr::Ring(parsed)); + } + Ok(nlas) +} diff --git a/ethtool/src/ring/get.rs b/ethtool/src/ring/get.rs new file mode 100644 index 00000000..b6910f8c --- /dev/null +++ b/ethtool/src/ring/get.rs @@ -0,0 +1,30 @@ +use futures::TryStream; +use netlink_packet_generic::GenlMessage; + +use crate::{ethtool_execute, EthtoolError, EthtoolHandle, EthtoolMessage}; + +pub struct EthtoolRingGetRequest { + handle: EthtoolHandle, + iface_name: Option, +} + +impl EthtoolRingGetRequest { + pub(crate) fn new(handle: EthtoolHandle, iface_name: Option<&str>) -> Self { + EthtoolRingGetRequest { + handle, + iface_name: iface_name.map(|i| i.to_string()), + } + } + + pub async fn execute( + self, + ) -> impl TryStream, Error = EthtoolError> { + let EthtoolRingGetRequest { + mut handle, + iface_name, + } = self; + + let ethtool_msg = EthtoolMessage::new_ring_get(iface_name.as_deref()); + ethtool_execute(&mut handle, iface_name.is_none(), ethtool_msg).await + } +} diff --git a/ethtool/src/ring/handle.rs b/ethtool/src/ring/handle.rs new file mode 100644 index 00000000..e8f32b92 --- /dev/null +++ b/ethtool/src/ring/handle.rs @@ -0,0 +1,14 @@ +use crate::{EthtoolHandle, EthtoolRingGetRequest}; + +pub struct EthtoolRingHandle(EthtoolHandle); + +impl EthtoolRingHandle { + pub fn new(handle: EthtoolHandle) -> Self { + EthtoolRingHandle(handle) + } + + /// Retrieve the ethtool rings of a interface (equivalent to `ethtool -g eth1`) + pub fn get(&mut self, iface_name: Option<&str>) -> EthtoolRingGetRequest { + EthtoolRingGetRequest::new(self.0.clone(), iface_name) + } +} diff --git a/ethtool/src/ring/mod.rs b/ethtool/src/ring/mod.rs new file mode 100644 index 00000000..c755540d --- /dev/null +++ b/ethtool/src/ring/mod.rs @@ -0,0 +1,9 @@ +mod attr; +mod get; +mod handle; + +pub(crate) use attr::parse_ring_nlas; + +pub use attr::EthtoolRingAttr; +pub use get::EthtoolRingGetRequest; +pub use handle::EthtoolRingHandle;