From 7998f8c5593f77162258ccd828b6dd931aede83e Mon Sep 17 00:00:00 2001 From: Gris Ge Date: Wed, 22 Sep 2021 13:53:29 +0800 Subject: [PATCH] ethtool: Add pause support of ethtool through netlink Utilizing genetlink, this patch introduce new crate for ethtool pause support through kernel netlink interface. Unlike other crates in this project, it is required to use `.await` for `execute()` function due to requirement of caching family ID of generic netlink. For example: * rtnetlink: ```rust let mut links = handle.link().get().execute(); while let Some(nl_msg) = links.try_next().await? { } ``` * ethtool: ```rust let mut pause_handle = handle.pause().get(iface_name).execute().await; while let Some(msg) = pause_handle.try_next().await? { } ``` Example code been places as `ethtool/examples/dump_pause.rs`. Signed-off-by: Gris Ge --- Cargo.toml | 3 + ethtool/Cargo.toml | 43 +++++++++ ethtool/README.md | 1 + ethtool/examples/dump_pause.rs | 29 ++++++ ethtool/src/connection.rs | 19 ++++ ethtool/src/error.rs | 21 +++++ ethtool/src/handle.rs | 35 ++++++++ ethtool/src/header.rs | 85 ++++++++++++++++++ ethtool/src/lib.rs | 19 ++++ ethtool/src/macros.rs | 23 +++++ ethtool/src/message.rs | 126 ++++++++++++++++++++++++++ ethtool/src/pause/attr.rs | 156 +++++++++++++++++++++++++++++++++ ethtool/src/pause/get.rs | 52 +++++++++++ ethtool/src/pause/handle.rs | 14 +++ ethtool/src/pause/mod.rs | 8 ++ 15 files changed, 634 insertions(+) create mode 100644 ethtool/Cargo.toml create mode 120000 ethtool/README.md create mode 100644 ethtool/examples/dump_pause.rs create mode 100644 ethtool/src/connection.rs create mode 100644 ethtool/src/error.rs create mode 100644 ethtool/src/handle.rs create mode 100644 ethtool/src/header.rs create mode 100644 ethtool/src/lib.rs create mode 100644 ethtool/src/macros.rs create mode 100644 ethtool/src/message.rs create mode 100644 ethtool/src/pause/attr.rs create mode 100644 ethtool/src/pause/get.rs create mode 100644 ethtool/src/pause/handle.rs create mode 100644 ethtool/src/pause/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 02d52891..c1454397 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "netlink-packet-audit/fuzz", "netlink-packet-sock-diag", "netlink-proto", + "ethtool", "genetlink", "rtnetlink", "audit", @@ -26,6 +27,7 @@ default-members = [ "netlink-packet-audit", "netlink-packet-sock-diag", "netlink-proto", + "ethtool", "genetlink", "rtnetlink", "audit", @@ -43,3 +45,4 @@ netlink-proto = { path = "netlink-proto" } genetlink = { path = "genetlink" } rtnetlink = { path = "rtnetlink" } audit = { path = "audit" } +ethtool = { path = "ethtool" } diff --git a/ethtool/Cargo.toml b/ethtool/Cargo.toml new file mode 100644 index 00000000..675a4c86 --- /dev/null +++ b/ethtool/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "ethtool" +version = "0.1.0" +authors = ["Gris Ge "] +license = "MIT" +edition = "2018" +description = "Linux Ethtool Communication Library" +keywords = ["network"] +categories = ["network-programming", "os"] +readme = "../README.md" + +[lib] +name = "ethtool" +path = "src/lib.rs" +crate-type = ["lib"] + +[features] +default = ["tokio_socket"] +tokio_socket = ["netlink-proto/tokio_socket", "tokio"] +smol_socket = ["netlink-proto/smol_socket", "async-std"] + +[dependencies] +anyhow = "1.0.44" +async-std = { version = "1.9.0", optional = true} +byteorder = "1.4.3" +futures = "0.3.17" +genetlink = { default-features = false, version = "0.1.0"} +log = "0.4.14" +netlink-packet-core = "0.2.4" +netlink-packet-generic = "0.1.0" +netlink-packet-utils = "0.4.1" +netlink-proto = { default-features = false, version = "0.7.0" } +netlink-sys = "0.7.0" +thiserror = "1.0.29" +tokio = { version = "1.0.1", features = ["rt"], optional = true} + +[dev-dependencies] +tokio = { version = "1.11.0", features = ["macros", "rt", "rt-multi-thread"] } +env_logger = "0.9.0" + +[[example]] +name = "dump_pause" +required-features = ["tokio_socket"] diff --git a/ethtool/README.md b/ethtool/README.md new file mode 120000 index 00000000..32d46ee8 --- /dev/null +++ b/ethtool/README.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/ethtool/examples/dump_pause.rs b/ethtool/examples/dump_pause.rs new file mode 100644 index 00000000..4cae96bc --- /dev/null +++ b/ethtool/examples/dump_pause.rs @@ -0,0 +1,29 @@ +use env_logger; +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() { + env_logger::init(); + let rt = tokio::runtime::Builder::new_current_thread() + .enable_io() + .build() + .unwrap(); + rt.block_on(get_pause(None)); +} + +async fn get_pause(iface_name: Option<&str>) { + let (connection, mut handle, _) = ethtool::new_connection().unwrap(); + tokio::spawn(connection); + + let mut pause_handle = handle.pause().get(iface_name).execute().await; + + let mut msgs = Vec::new(); + while let Some(msg) = pause_handle.try_next().await.unwrap() { + msgs.push(msg); + } + assert!(msgs.len() > 0); + println!("{:?}", msgs); +} diff --git a/ethtool/src/connection.rs b/ethtool/src/connection.rs new file mode 100644 index 00000000..c9d9ef2c --- /dev/null +++ b/ethtool/src/connection.rs @@ -0,0 +1,19 @@ +use std::io; + +use futures::channel::mpsc::UnboundedReceiver; +use genetlink::message::RawGenlMessage; +use netlink_packet_core::NetlinkMessage; +use netlink_proto::Connection; +use netlink_sys::SocketAddr; + +use crate::EthtoolHandle; + +#[allow(clippy::type_complexity)] +pub fn new_connection() -> io::Result<( + Connection, + EthtoolHandle, + UnboundedReceiver<(NetlinkMessage, SocketAddr)>, +)> { + let (conn, handle, messages) = genetlink::new_connection()?; + Ok((conn, EthtoolHandle::new(handle), messages)) +} diff --git a/ethtool/src/error.rs b/ethtool/src/error.rs new file mode 100644 index 00000000..08d4bce3 --- /dev/null +++ b/ethtool/src/error.rs @@ -0,0 +1,21 @@ +use thiserror::Error; + +use netlink_packet_core::{ErrorMessage, NetlinkMessage}; +use netlink_packet_generic::GenlMessage; + +use crate::EthtoolMessage; + +#[derive(Clone, Eq, PartialEq, Debug, Error)] +pub enum EthtoolError { + #[error("Received an unexpected message {0:?}")] + UnexpectedMessage(NetlinkMessage>), + + #[error("Received a netlink error message {0}")] + NetlinkError(ErrorMessage), + + #[error("A netlink request failed")] + RequestFailed(String), + + #[error("A bug in this crate")] + Bug(String), +} diff --git a/ethtool/src/handle.rs b/ethtool/src/handle.rs new file mode 100644 index 00000000..e869ef72 --- /dev/null +++ b/ethtool/src/handle.rs @@ -0,0 +1,35 @@ +use futures::Stream; +use genetlink::GenetlinkHandle; +use netlink_packet_core::NetlinkMessage; +use netlink_packet_generic::GenlMessage; +use netlink_packet_utils::DecodeError; + +use crate::{EthtoolError, EthtoolMessage, EthtoolPauseHandle}; + +#[derive(Clone, Debug)] +pub struct EthtoolHandle { + pub handle: GenetlinkHandle, +} + +impl EthtoolHandle { + pub(crate) fn new(handle: GenetlinkHandle) -> Self { + EthtoolHandle { handle } + } + + pub fn pause(&mut self) -> EthtoolPauseHandle { + EthtoolPauseHandle::new(self.clone()) + } + + pub async fn request( + &mut self, + message: NetlinkMessage>, + ) -> Result< + impl Stream>, DecodeError>>, + EthtoolError, + > { + self.handle + .request(message) + .await + .map_err(|e| EthtoolError::RequestFailed(format!("BUG: Request failed with {}", e))) + } +} diff --git a/ethtool/src/header.rs b/ethtool/src/header.rs new file mode 100644 index 00000000..fdce773d --- /dev/null +++ b/ethtool/src/header.rs @@ -0,0 +1,85 @@ +use std::ffi::CString; + +use anyhow::Context; +use byteorder::{ByteOrder, NativeEndian}; +use netlink_packet_utils::{ + nla::{self, DefaultNla, NlaBuffer}, + parsers::{parse_string, parse_u32}, + DecodeError, + Parseable, +}; + +const ALTIFNAMSIZ: usize = 128; +const ETHTOOL_A_HEADER_DEV_INDEX: u16 = 1; +const ETHTOOL_A_HEADER_DEV_NAME: u16 = 2; +const ETHTOOL_A_HEADER_FLAGS: u16 = 3; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum EthtoolHeader { + DevIndex(u32), + DevName(String), + Flags(u32), + Other(DefaultNla), +} + +impl nla::Nla for EthtoolHeader { + fn value_len(&self) -> usize { + match self { + Self::DevIndex(_) | Self::Flags(_) => 4, + Self::DevName(s) => { + if s.len() + 1 > ALTIFNAMSIZ { + ALTIFNAMSIZ + } else { + s.len() + 1 + } + } + Self::Other(attr) => attr.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + Self::DevIndex(_) => ETHTOOL_A_HEADER_DEV_INDEX, + Self::DevName(_) => ETHTOOL_A_HEADER_DEV_NAME, + Self::Flags(_) => ETHTOOL_A_HEADER_FLAGS, + Self::Other(attr) => attr.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + Self::DevIndex(value) | Self::Flags(value) => NativeEndian::write_u32(buffer, *value), + Self::DevName(s) => str_to_zero_ended_u8_array(s, buffer, ALTIFNAMSIZ), + Self::Other(ref attr) => attr.emit_value(buffer), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for EthtoolHeader { + fn parse(buf: &NlaBuffer<&'a T>) -> Result { + let payload = buf.value(); + Ok(match buf.kind() { + ETHTOOL_A_HEADER_DEV_INDEX => Self::DevIndex( + parse_u32(payload).context("invalid ETHTOOL_A_HEADER_DEV_INDEX value")?, + ), + ETHTOOL_A_HEADER_FLAGS => { + Self::Flags(parse_u32(payload).context("invalid ETHTOOL_A_HEADER_FLAGS value")?) + } + ETHTOOL_A_HEADER_DEV_NAME => Self::DevName( + parse_string(payload).context("invalid ETHTOOL_A_HEADER_DEV_NAME value")?, + ), + _ => Self::Other(DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?), + }) + } +} + +fn str_to_zero_ended_u8_array(src_str: &str, buffer: &mut [u8], max_size: usize) { + if let Ok(src_cstring) = CString::new(src_str.as_bytes()) { + let src_null_ended_str = src_cstring.into_bytes_with_nul(); + if src_null_ended_str.len() > max_size { + buffer[..max_size].clone_from_slice(&src_null_ended_str[..max_size]) + } else { + buffer[..src_null_ended_str.len()].clone_from_slice(&src_null_ended_str) + } + } +} diff --git a/ethtool/src/lib.rs b/ethtool/src/lib.rs new file mode 100644 index 00000000..021edf68 --- /dev/null +++ b/ethtool/src/lib.rs @@ -0,0 +1,19 @@ +mod connection; +mod error; +mod handle; +mod header; +mod macros; +mod message; +mod pause; + +pub use connection::new_connection; +pub use error::EthtoolError; +pub use handle::EthtoolHandle; +pub use header::EthtoolHeader; +pub use message::{EthtoolAttr, EthtoolMessage}; +pub use pause::{ + EthtoolPauseAttr, + EthtoolPauseGetRequest, + EthtoolPauseHandle, + EthtoolPauseStatAttr, +}; diff --git a/ethtool/src/macros.rs b/ethtool/src/macros.rs new file mode 100644 index 00000000..341d42a4 --- /dev/null +++ b/ethtool/src/macros.rs @@ -0,0 +1,23 @@ +#[macro_export] +macro_rules! try_ethtool { + ($msg: expr) => {{ + use netlink_packet_core::{NetlinkMessage, NetlinkPayload}; + use $crate::EthtoolError; + + match $msg { + Ok(msg) => { + let (header, payload) = msg.into_parts(); + match payload { + NetlinkPayload::InnerMessage(msg) => msg, + NetlinkPayload::Error(err) => return Err(EthtoolError::NetlinkError(err)), + _ => { + return Err(EthtoolError::UnexpectedMessage(NetlinkMessage::new( + header, payload, + ))) + } + } + } + Err(e) => return Err(EthtoolError::Bug(format!("BUG: decode error {:?}", e))), + } + }}; +} diff --git a/ethtool/src/message.rs b/ethtool/src/message.rs new file mode 100644 index 00000000..0106560a --- /dev/null +++ b/ethtool/src/message.rs @@ -0,0 +1,126 @@ +use std::convert::{TryFrom, TryInto}; + +use netlink_packet_core::DecodeError; +use netlink_packet_generic::{GenlFamily, GenlHeader}; +use netlink_packet_utils::{nla::Nla, Emitable, ParseableParametrized}; + +use crate::{ + pause::{parse_pause_nlas, EthtoolPauseAttr}, + EthtoolHeader, +}; + +const ETHTOOL_MSG_PAUSE_GET: u8 = 21; +const ETHTOOL_MSG_PAUSE_GET_REPLY: u8 = 22; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum EthtoolCmd { + PauseGet, + PauseGetReply, +} + +impl From for u8 { + fn from(cmd: EthtoolCmd) -> Self { + match cmd { + EthtoolCmd::PauseGet => ETHTOOL_MSG_PAUSE_GET, + EthtoolCmd::PauseGetReply => ETHTOOL_MSG_PAUSE_GET_REPLY, + } + } +} + +impl TryFrom for EthtoolCmd { + type Error = DecodeError; + + fn try_from(value: u8) -> Result { + Ok(match value { + ETHTOOL_MSG_PAUSE_GET => Self::PauseGet, + ETHTOOL_MSG_PAUSE_GET_REPLY => Self::PauseGetReply, + cmd => { + return Err(DecodeError::from(format!( + "Unsupported ethtool command: {}", + cmd + ))) + } + }) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum EthtoolAttr { + Pause(EthtoolPauseAttr), +} + +impl Nla for EthtoolAttr { + fn value_len(&self) -> usize { + match self { + Self::Pause(attr) => attr.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + Self::Pause(attr) => attr.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + Self::Pause(attr) => attr.emit_value(buffer), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct EthtoolMessage { + pub cmd: EthtoolCmd, + pub nlas: Vec, +} + +impl GenlFamily for EthtoolMessage { + fn family_name() -> &'static str { + "ethtool" + } + + fn version(&self) -> u8 { + 1 + } + + fn command(&self) -> u8 { + self.cmd.into() + } +} + +impl EthtoolMessage { + pub fn new_pause_get(iface_name: Option<&str>) -> Self { + let nlas = match iface_name { + Some(s) => vec![EthtoolAttr::Pause(EthtoolPauseAttr::Header(vec![ + EthtoolHeader::DevName(s.to_string()), + ]))], + None => vec![EthtoolAttr::Pause(EthtoolPauseAttr::Header(vec![]))], + }; + EthtoolMessage { + cmd: EthtoolCmd::PauseGet, + nlas, + } + } +} + +impl Emitable for EthtoolMessage { + fn buffer_len(&self) -> usize { + self.nlas.as_slice().buffer_len() + } + + fn emit(&self, buffer: &mut [u8]) { + self.nlas.as_slice().emit(buffer) + } +} + +impl ParseableParametrized<[u8], GenlHeader> for EthtoolMessage { + fn parse_with_param(buffer: &[u8], header: GenlHeader) -> Result { + let cmd = header.cmd.try_into()?; + let nlas = match cmd { + EthtoolCmd::PauseGetReply => parse_pause_nlas(buffer)?, + _ => return Err(format!("Unsupported ethtool command {:?}", cmd).into()), + }; + Ok(Self { cmd, nlas }) + } +} diff --git a/ethtool/src/pause/attr.rs b/ethtool/src/pause/attr.rs new file mode 100644 index 00000000..f4b88322 --- /dev/null +++ b/ethtool/src/pause/attr.rs @@ -0,0 +1,156 @@ +use anyhow::Context; +use byteorder::{ByteOrder, NativeEndian}; +use netlink_packet_utils::{ + nla::{DefaultNla, Nla, NlaBuffer, NlasIterator, NLA_F_NESTED}, + parsers::{parse_u64, parse_u8}, + DecodeError, + Emitable, + Parseable, +}; + +use crate::{EthtoolAttr, EthtoolHeader}; + +const ETHTOOL_A_PAUSE_HEADER: u16 = 1; +const ETHTOOL_A_PAUSE_AUTONEG: u16 = 2; +const ETHTOOL_A_PAUSE_RX: u16 = 3; +const ETHTOOL_A_PAUSE_TX: u16 = 4; +const ETHTOOL_A_PAUSE_STATS: u16 = 5; + +const ETHTOOL_A_PAUSE_STAT_TX_FRAMES: u16 = 2; +const ETHTOOL_A_PAUSE_STAT_RX_FRAMES: u16 = 3; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum EthtoolPauseStatAttr { + Rx(u64), + Tx(u64), + Other(DefaultNla), +} + +impl Nla for EthtoolPauseStatAttr { + fn value_len(&self) -> usize { + match self { + Self::Rx(_) | Self::Tx(_) => 8, + Self::Other(attr) => attr.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + Self::Rx(_) => ETHTOOL_A_PAUSE_STAT_RX_FRAMES, + Self::Tx(_) => ETHTOOL_A_PAUSE_STAT_RX_FRAMES, + Self::Other(attr) => attr.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + Self::Rx(value) | Self::Tx(value) => NativeEndian::write_u64(buffer, *value), + Self::Other(ref attr) => attr.emit_value(buffer), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for EthtoolPauseStatAttr { + fn parse(buf: &NlaBuffer<&'a T>) -> Result { + let payload = buf.value(); + Ok(match buf.kind() { + ETHTOOL_A_PAUSE_STAT_TX_FRAMES => Self::Tx( + parse_u64(payload).context("invalid ETHTOOL_A_PAUSE_STAT_TX_FRAMES value")?, + ), + ETHTOOL_A_PAUSE_STAT_RX_FRAMES => Self::Rx( + parse_u64(payload).context("invalid ETHTOOL_A_PAUSE_STAT_RX_FRAMES value")?, + ), + _ => Self::Other(DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?), + }) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum EthtoolPauseAttr { + Header(Vec), + AutoNeg(bool), + Rx(bool), + Tx(bool), + Stats(Vec), + Other(DefaultNla), +} + +impl Nla for EthtoolPauseAttr { + fn value_len(&self) -> usize { + match self { + Self::Header(hdrs) => hdrs.as_slice().buffer_len(), + Self::AutoNeg(_) | Self::Rx(_) | Self::Tx(_) => 1, + Self::Stats(ref nlas) => nlas.as_slice().buffer_len(), + Self::Other(attr) => attr.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + Self::Header(_) => ETHTOOL_A_PAUSE_HEADER | NLA_F_NESTED, + Self::AutoNeg(_) => ETHTOOL_A_PAUSE_AUTONEG, + Self::Rx(_) => ETHTOOL_A_PAUSE_RX, + Self::Tx(_) => ETHTOOL_A_PAUSE_TX, + Self::Stats(_) => ETHTOOL_A_PAUSE_STATS | NLA_F_NESTED, + Self::Other(attr) => attr.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + Self::Header(ref nlas) => nlas.as_slice().emit(buffer), + Self::AutoNeg(value) | Self::Rx(value) | Self::Tx(value) => buffer[0] = *value as u8, + Self::Stats(ref nlas) => nlas.as_slice().emit(buffer), + Self::Other(ref attr) => attr.emit(buffer), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for EthtoolPauseAttr { + fn parse(buf: &NlaBuffer<&'a T>) -> Result { + let payload = buf.value(); + Ok(match buf.kind() { + ETHTOOL_A_PAUSE_HEADER => { + let mut nlas = Vec::new(); + let error_msg = "failed to parse pause 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_PAUSE_AUTONEG => Self::AutoNeg( + parse_u8(payload).context("invalid ETHTOOL_A_PAUSE_AUTONEG value")? == 1, + ), + ETHTOOL_A_PAUSE_RX => { + Self::Rx(parse_u8(payload).context("invalid ETHTOOL_A_PAUSE_RX value")? == 1) + } + ETHTOOL_A_PAUSE_TX => { + Self::Tx(parse_u8(payload).context("invalid ETHTOOL_A_PAUSE_TX value")? == 1) + } + ETHTOOL_A_PAUSE_STATS => { + let mut nlas = Vec::new(); + let error_msg = "failed to parse pause stats attributes"; + for nla in NlasIterator::new(payload) { + let nla = &nla.context(error_msg)?; + let parsed = EthtoolPauseStatAttr::parse(nla).context(error_msg)?; + nlas.push(parsed); + } + Self::Stats(nlas) + } + _ => Self::Other(DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?), + }) + } +} + +pub(crate) fn parse_pause_nlas(buffer: &[u8]) -> Result, DecodeError> { + let mut nlas = Vec::new(); + for nla in NlasIterator::new(buffer) { + let error_msg = format!("Failed to parse ethtool pause message attribute {:?}", nla); + let nla = &nla.context(error_msg.clone())?; + let parsed = EthtoolPauseAttr::parse(nla).context(error_msg)?; + nlas.push(EthtoolAttr::Pause(parsed)); + } + Ok(nlas) +} diff --git a/ethtool/src/pause/get.rs b/ethtool/src/pause/get.rs new file mode 100644 index 00000000..8d9acfb4 --- /dev/null +++ b/ethtool/src/pause/get.rs @@ -0,0 +1,52 @@ +use futures::{self, future::Either, FutureExt, StreamExt, TryStream}; +use netlink_packet_core::{NetlinkMessage, NLM_F_ACK, NLM_F_DUMP, NLM_F_REQUEST}; +use netlink_packet_generic::GenlMessage; + +use crate::{try_ethtool, EthtoolError, EthtoolHandle, EthtoolMessage}; + +pub struct EthtoolPauseGetRequest { + handle: EthtoolHandle, + iface_name: Option, +} + +impl EthtoolPauseGetRequest { + pub(crate) fn new(handle: EthtoolHandle, iface_name: Option<&str>) -> Self { + EthtoolPauseGetRequest { + handle, + iface_name: iface_name.map(|i| i.to_string()), + } + } + + pub async fn execute( + self, + ) -> impl TryStream, Error = EthtoolError> { + let EthtoolPauseGetRequest { + mut handle, + iface_name, + } = self; + + let nl_header_flags = match iface_name { + // The NLM_F_ACK is required due to bug of kernel: + // https://bugzilla.redhat.com/show_bug.cgi?id=1953847 + // without `NLM_F_MULTI`, rust-netlink will not parse + // multiple netlink message in single socket reply. + // Using NLM_F_ACK will force rust-netlink to parse all till + // acked at the end. + None => NLM_F_DUMP | NLM_F_REQUEST | NLM_F_ACK, + Some(_) => NLM_F_REQUEST, + }; + + let ethtool_msg = EthtoolMessage::new_pause_get(iface_name.as_deref()); + + let mut nl_msg = NetlinkMessage::from(GenlMessage::from_payload(ethtool_msg)); + + nl_msg.header.flags = nl_header_flags; + + match handle.request(nl_msg).await { + Ok(response) => Either::Left(response.map(move |msg| Ok(try_ethtool!(msg)))), + Err(e) => Either::Right( + futures::future::err::, EthtoolError>(e).into_stream(), + ), + } + } +} diff --git a/ethtool/src/pause/handle.rs b/ethtool/src/pause/handle.rs new file mode 100644 index 00000000..fc1d128d --- /dev/null +++ b/ethtool/src/pause/handle.rs @@ -0,0 +1,14 @@ +use crate::{EthtoolHandle, EthtoolPauseGetRequest}; + +pub struct EthtoolPauseHandle(EthtoolHandle); + +impl EthtoolPauseHandle { + pub fn new(handle: EthtoolHandle) -> Self { + EthtoolPauseHandle(handle) + } + + /// Retrieve the pause setting of a interface (equivalent to `ethtool -a eth1`) + pub fn get(&mut self, iface_name: Option<&str>) -> EthtoolPauseGetRequest { + EthtoolPauseGetRequest::new(self.0.clone(), iface_name) + } +} diff --git a/ethtool/src/pause/mod.rs b/ethtool/src/pause/mod.rs new file mode 100644 index 00000000..2307a1cc --- /dev/null +++ b/ethtool/src/pause/mod.rs @@ -0,0 +1,8 @@ +mod attr; +mod get; +mod handle; + +pub(crate) use attr::parse_pause_nlas; +pub use attr::{EthtoolPauseAttr, EthtoolPauseStatAttr}; +pub use get::EthtoolPauseGetRequest; +pub use handle::EthtoolPauseHandle;