diff --git a/src/channel/attr.rs b/src/channel/attr.rs new file mode 100644 index 0000000..d026c5f --- /dev/null +++ b/src/channel/attr.rs @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: MIT + +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_CHANNELS_HEADER: u16 = 1; +const ETHTOOL_A_CHANNELS_RX_MAX: u16 = 2; +const ETHTOOL_A_CHANNELS_TX_MAX: u16 = 3; +const ETHTOOL_A_CHANNELS_OTHER_MAX: u16 = 4; +const ETHTOOL_A_CHANNELS_COMBINED_MAX: u16 = 5; +const ETHTOOL_A_CHANNELS_RX_COUNT: u16 = 6; +const ETHTOOL_A_CHANNELS_TX_COUNT: u16 = 7; +const ETHTOOL_A_CHANNELS_OTHER_COUNT: u16 = 8; +const ETHTOOL_A_CHANNELS_COMBINED_COUNT: u16 = 9; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum EthtoolChannelAttr { + Header(Vec), + RxMax(u32), + TxMax(u32), + OtherMax(u32), + CombinedMax(u32), + RxCount(u32), + TxCount(u32), + OtherCount(u32), + CombinedCount(u32), + Other(DefaultNla), +} + +impl Nla for EthtoolChannelAttr { + fn value_len(&self) -> usize { + match self { + Self::Header(hdrs) => hdrs.as_slice().buffer_len(), + Self::RxMax(_) + | Self::TxMax(_) + | Self::OtherMax(_) + | Self::CombinedMax(_) + | Self::RxCount(_) + | Self::TxCount(_) + | Self::OtherCount(_) + | Self::CombinedCount(_) => 4, + Self::Other(attr) => attr.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + Self::Header(_) => ETHTOOL_A_CHANNELS_HEADER | NLA_F_NESTED, + Self::RxMax(_) => ETHTOOL_A_CHANNELS_RX_MAX, + Self::TxMax(_) => ETHTOOL_A_CHANNELS_TX_MAX, + Self::OtherMax(_) => ETHTOOL_A_CHANNELS_OTHER_MAX, + Self::CombinedMax(_) => ETHTOOL_A_CHANNELS_COMBINED_MAX, + Self::RxCount(_) => ETHTOOL_A_CHANNELS_RX_COUNT, + Self::TxCount(_) => ETHTOOL_A_CHANNELS_TX_COUNT, + Self::OtherCount(_) => ETHTOOL_A_CHANNELS_OTHER_COUNT, + Self::CombinedCount(_) => ETHTOOL_A_CHANNELS_COMBINED_COUNT, + 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::TxMax(d) + | Self::OtherMax(d) + | Self::CombinedMax(d) + | Self::RxCount(d) + | Self::TxCount(d) + | Self::OtherCount(d) + | Self::CombinedCount(d) => NativeEndian::write_u32(buffer, *d), + Self::Other(ref attr) => attr.emit(buffer), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> + for EthtoolChannelAttr +{ + fn parse(buf: &NlaBuffer<&'a T>) -> Result { + let payload = buf.value(); + Ok(match buf.kind() { + ETHTOOL_A_CHANNELS_HEADER => { + let mut nlas = Vec::new(); + let error_msg = "failed to parse channel 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_CHANNELS_RX_MAX => Self::RxMax( + parse_u32(payload) + .context("Invalid ETHTOOL_A_CHANNELS_RX_MAX value")?, + ), + ETHTOOL_A_CHANNELS_TX_MAX => Self::TxMax( + parse_u32(payload) + .context("Invalid ETHTOOL_A_CHANNELS_RX_MAX value")?, + ), + ETHTOOL_A_CHANNELS_OTHER_MAX => Self::OtherMax( + parse_u32(payload) + .context("Invalid ETHTOOL_A_CHANNELS_RX_MAX value")?, + ), + ETHTOOL_A_CHANNELS_COMBINED_MAX => Self::CombinedMax( + parse_u32(payload) + .context("Invalid ETHTOOL_A_CHANNELS_RX_MAX value")?, + ), + ETHTOOL_A_CHANNELS_RX_COUNT => Self::RxCount( + parse_u32(payload) + .context("Invalid ETHTOOL_A_CHANNELS_RX_COUNT value")?, + ), + ETHTOOL_A_CHANNELS_TX_COUNT => Self::TxCount( + parse_u32(payload) + .context("Invalid ETHTOOL_A_CHANNELS_RX_COUNT value")?, + ), + ETHTOOL_A_CHANNELS_OTHER_COUNT => Self::OtherCount( + parse_u32(payload) + .context("Invalid ETHTOOL_A_CHANNELS_RX_COUNT value")?, + ), + ETHTOOL_A_CHANNELS_COMBINED_COUNT => Self::CombinedCount( + parse_u32(payload) + .context("Invalid ETHTOOL_A_CHANNELS_RX_COUNT value")?, + ), + kind => { + Self::Other(DefaultNla::parse(buf).context(format!( + "invalid ethtool channel NLA kind {kind}" + ))?) + } + }) + } +} + +pub(crate) fn parse_channel_nlas( + buffer: &[u8], +) -> Result, DecodeError> { + let mut nlas = Vec::new(); + for nla in NlasIterator::new(buffer) { + let error_msg = format!( + "Failed to parse ethtool channel message attribute {nla:?}" + ); + let nla = &nla.context(error_msg.clone())?; + let parsed = EthtoolChannelAttr::parse(nla).context(error_msg)?; + nlas.push(EthtoolAttr::Channel(parsed)); + } + Ok(nlas) +} diff --git a/src/channel/get.rs b/src/channel/get.rs new file mode 100644 index 0000000..2ebe54f --- /dev/null +++ b/src/channel/get.rs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +use futures::TryStream; +use netlink_packet_generic::GenlMessage; + +use crate::{ethtool_execute, EthtoolError, EthtoolHandle, EthtoolMessage}; + +pub struct EthtoolChannelGetRequest { + handle: EthtoolHandle, + iface_name: Option, +} + +impl EthtoolChannelGetRequest { + pub(crate) fn new(handle: EthtoolHandle, iface_name: Option<&str>) -> Self { + EthtoolChannelGetRequest { + handle, + iface_name: iface_name.map(|i| i.to_string()), + } + } + + pub async fn execute( + self, + ) -> impl TryStream, Error = EthtoolError> + { + let EthtoolChannelGetRequest { + mut handle, + iface_name, + } = self; + + let ethtool_msg = + EthtoolMessage::new_channel_get(iface_name.as_deref()); + ethtool_execute(&mut handle, iface_name.is_none(), ethtool_msg).await + } +} diff --git a/src/channel/handle.rs b/src/channel/handle.rs new file mode 100644 index 0000000..ac5e004 --- /dev/null +++ b/src/channel/handle.rs @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +use crate::{ + EthtoolChannelGetRequest, EthtoolChannelSetRequest, EthtoolHandle, +}; + +pub struct EthtoolChannelHandle(EthtoolHandle); + +impl EthtoolChannelHandle { + pub fn new(handle: EthtoolHandle) -> Self { + EthtoolChannelHandle(handle) + } + + /// Retrieve the ethtool Channels of a interface (equivalent to `ethtool -l + /// eth1`) + pub fn get( + &mut self, + iface_name: Option<&str>, + ) -> EthtoolChannelGetRequest { + EthtoolChannelGetRequest::new(self.0.clone(), iface_name) + } + + /// Set the ethtool Channels of a interface (equivalent to `ethtool -L eth1`) + pub fn set(&mut self, iface_name: &str) -> EthtoolChannelSetRequest { + EthtoolChannelSetRequest::new(self.0.clone(), iface_name) + } +} diff --git a/src/channel/mod.rs b/src/channel/mod.rs new file mode 100644 index 0000000..8d2b980 --- /dev/null +++ b/src/channel/mod.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +mod attr; +mod get; +mod handle; +mod set; + +pub(crate) use attr::parse_channel_nlas; + +pub use attr::EthtoolChannelAttr; +pub use get::EthtoolChannelGetRequest; +pub use handle::EthtoolChannelHandle; +pub use set::EthtoolChannelSetRequest; diff --git a/src/channel/set.rs b/src/channel/set.rs new file mode 100644 index 0000000..915387b --- /dev/null +++ b/src/channel/set.rs @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT + +use futures::TryStream; +use netlink_packet_generic::GenlMessage; + +use crate::{ + ethtool_execute, EthtoolAttr, EthtoolChannelAttr, EthtoolError, + EthtoolHandle, EthtoolMessage, +}; + +pub struct EthtoolChannelSetRequest { + handle: EthtoolHandle, + message: EthtoolMessage, + rx_count: Option, + tx_count: Option, + other_count: Option, + combined_count: Option, +} + +impl EthtoolChannelSetRequest { + pub(crate) fn new(handle: EthtoolHandle, iface_name: &str) -> Self { + EthtoolChannelSetRequest { + handle, + message: EthtoolMessage::new_channel_set(iface_name), + rx_count: None, + tx_count: None, + other_count: None, + combined_count: None, + } + } + + pub fn rx_count(mut self, count: u32) -> Self { + self.rx_count = Some(count); + self + } + + pub fn tx_count(mut self, count: u32) -> Self { + self.tx_count = Some(count); + self + } + + pub fn other_count(mut self, count: u32) -> Self { + self.other_count = Some(count); + self + } + + pub fn combined_count(mut self, count: u32) -> Self { + self.combined_count = Some(count); + self + } + + pub async fn execute( + self, + ) -> impl TryStream, Error = EthtoolError> + { + let EthtoolChannelSetRequest { + mut handle, + mut message, + rx_count, + tx_count, + other_count, + combined_count, + } = self; + + if let Some(count) = rx_count { + message + .nlas + .push(EthtoolAttr::Channel(EthtoolChannelAttr::RxCount(count))); + } + if let Some(count) = tx_count { + message + .nlas + .push(EthtoolAttr::Channel(EthtoolChannelAttr::TxCount(count))); + } + if let Some(count) = other_count { + message.nlas.push(EthtoolAttr::Channel( + EthtoolChannelAttr::OtherCount(count), + )); + } + if let Some(count) = combined_count { + message.nlas.push(EthtoolAttr::Channel( + EthtoolChannelAttr::CombinedCount(count), + )); + } + + ethtool_execute(&mut handle, false, message).await + } +} diff --git a/src/handle.rs b/src/handle.rs index 8b26024..0be6bf1 100644 --- a/src/handle.rs +++ b/src/handle.rs @@ -9,9 +9,9 @@ use netlink_packet_generic::GenlMessage; use netlink_packet_utils::DecodeError; use crate::{ - try_ethtool, EthtoolCoalesceHandle, EthtoolError, EthtoolFeatureHandle, - EthtoolLinkModeHandle, EthtoolMessage, EthtoolPauseHandle, - EthtoolRingHandle, EthtoolTsInfoHandle, + try_ethtool, EthtoolChannelHandle, EthtoolCoalesceHandle, EthtoolError, + EthtoolFeatureHandle, EthtoolLinkModeHandle, EthtoolMessage, + EthtoolPauseHandle, EthtoolRingHandle, EthtoolTsInfoHandle, }; #[derive(Clone, Debug)] @@ -48,6 +48,10 @@ impl EthtoolHandle { EthtoolTsInfoHandle::new(self.clone()) } + pub fn channel(&mut self) -> EthtoolChannelHandle { + EthtoolChannelHandle::new(self.clone()) + } + pub async fn request( &mut self, message: NetlinkMessage>, diff --git a/src/lib.rs b/src/lib.rs index b953c80..b9e2a09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT mod bitset_util; +mod channel; mod coalesce; mod connection; mod error; @@ -14,6 +15,10 @@ mod pause; mod ring; mod tsinfo; +pub use channel::{ + EthtoolChannelAttr, EthtoolChannelGetRequest, EthtoolChannelHandle, + EthtoolChannelSetRequest, +}; pub use coalesce::{ EthtoolCoalesceAttr, EthtoolCoalesceGetRequest, EthtoolCoalesceHandle, }; diff --git a/src/message.rs b/src/message.rs index b76f3ad..3bb6b0a 100644 --- a/src/message.rs +++ b/src/message.rs @@ -6,6 +6,7 @@ use netlink_packet_utils::{ }; use crate::{ + channel::{parse_channel_nlas, EthtoolChannelAttr}, coalesce::{parse_coalesce_nlas, EthtoolCoalesceAttr}, feature::{parse_feature_nlas, EthtoolFeatureAttr}, link_mode::{parse_link_mode_nlas, EthtoolLinkModeAttr}, @@ -27,6 +28,9 @@ const ETHTOOL_MSG_COALESCE_GET: u8 = 19; const ETHTOOL_MSG_COALESCE_GET_REPLY: u8 = 20; const ETHTOOL_MSG_TSINFO_GET: u8 = 25; const ETHTOOL_MSG_TSINFO_GET_REPLY: u8 = 26; +const ETHTOOL_MSG_CHANNELS_GET: u8 = 17; +const ETHTOOL_MSG_CHANNELS_GET_REPLY: u8 = 18; +const ETHTOOL_MSG_CHANNELS_SET: u8 = 18; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum EthtoolCmd { @@ -42,6 +46,9 @@ pub enum EthtoolCmd { CoalesceGetReply, TsInfoGet, TsInfoGetReply, + ChannelGet, + ChannelGetReply, + ChannelSet, } impl From for u8 { @@ -59,6 +66,9 @@ impl From for u8 { EthtoolCmd::CoalesceGetReply => ETHTOOL_MSG_COALESCE_GET_REPLY, EthtoolCmd::TsInfoGet => ETHTOOL_MSG_TSINFO_GET, EthtoolCmd::TsInfoGetReply => ETHTOOL_MSG_TSINFO_GET_REPLY, + EthtoolCmd::ChannelGet => ETHTOOL_MSG_CHANNELS_GET, + EthtoolCmd::ChannelGetReply => ETHTOOL_MSG_CHANNELS_GET_REPLY, + EthtoolCmd::ChannelSet => ETHTOOL_MSG_CHANNELS_SET, } } } @@ -71,6 +81,7 @@ pub enum EthtoolAttr { Ring(EthtoolRingAttr), Coalesce(EthtoolCoalesceAttr), TsInfo(EthtoolTsInfoAttr), + Channel(EthtoolChannelAttr), } impl Nla for EthtoolAttr { @@ -82,6 +93,7 @@ impl Nla for EthtoolAttr { Self::Ring(attr) => attr.value_len(), Self::Coalesce(attr) => attr.value_len(), Self::TsInfo(attr) => attr.value_len(), + Self::Channel(attr) => attr.value_len(), } } @@ -93,6 +105,7 @@ impl Nla for EthtoolAttr { Self::Ring(attr) => attr.kind(), Self::Coalesce(attr) => attr.kind(), Self::TsInfo(attr) => attr.kind(), + Self::Channel(attr) => attr.kind(), } } @@ -104,6 +117,7 @@ impl Nla for EthtoolAttr { Self::Ring(attr) => attr.emit_value(buffer), Self::Coalesce(attr) => attr.emit_value(buffer), Self::TsInfo(attr) => attr.emit_value(buffer), + Self::Channel(attr) => attr.emit_value(buffer), } } } @@ -224,6 +238,34 @@ impl EthtoolMessage { nlas, } } + + pub fn new_channel_get(iface_name: Option<&str>) -> Self { + let nlas = match iface_name { + Some(s) => { + vec![EthtoolAttr::Channel(EthtoolChannelAttr::Header(vec![ + EthtoolHeader::DevName(s.to_string()), + ]))] + } + None => { + vec![EthtoolAttr::Channel(EthtoolChannelAttr::Header(vec![]))] + } + }; + EthtoolMessage { + cmd: EthtoolCmd::ChannelGet, + nlas, + } + } + + pub fn new_channel_set(iface_name: &str) -> Self { + let nlas = + vec![EthtoolAttr::Channel(EthtoolChannelAttr::Header(vec![ + EthtoolHeader::DevName(iface_name.to_string()), + ]))]; + EthtoolMessage { + cmd: EthtoolCmd::ChannelSet, + nlas, + } + } } impl Emitable for EthtoolMessage { @@ -266,6 +308,10 @@ impl ParseableParametrized<[u8], GenlHeader> for EthtoolMessage { cmd: EthtoolCmd::TsInfoGetReply, nlas: parse_tsinfo_nlas(buffer)?, }, + ETHTOOL_MSG_CHANNELS_GET_REPLY => Self { + cmd: EthtoolCmd::ChannelGetReply, + nlas: parse_channel_nlas(buffer)?, + }, cmd => { return Err(DecodeError::from(format!( "Unsupported ethtool reply command: {cmd}" diff --git a/tests/channels.rs b/tests/channels.rs new file mode 100644 index 0000000..f37d7c7 --- /dev/null +++ b/tests/channels.rs @@ -0,0 +1,128 @@ +use ethtool::{ + EthtoolAttr, EthtoolChannelAttr, EthtoolCmd, EthtoolHeader, EthtoolMessage, +}; +use netlink_packet_generic::{GenlBuffer, GenlHeader}; +use netlink_packet_utils::{Emitable, Parseable, ParseableParametrized}; + +#[test] +fn test_channels_get_reply() { + let raw: Vec = vec![ + 0x12, 0x01, 0x00, 0x00, 0x18, 0x00, 0x01, 0x80, 0x08, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x02, 0x00, 0x65, 0x74, 0x68, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x05, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x09, 0x00, 0x02, 0x00, 0x00, 0x00, + ]; + + let expected = EthtoolMessage { + cmd: EthtoolCmd::ChannelGetReply, + nlas: vec![ + EthtoolAttr::Channel(EthtoolChannelAttr::Header(vec![ + EthtoolHeader::DevIndex(2), + EthtoolHeader::DevName("eth0".to_string()), + ])), + EthtoolAttr::Channel(EthtoolChannelAttr::CombinedMax(4)), + EthtoolAttr::Channel(EthtoolChannelAttr::CombinedCount(2)), + ], + }; + + assert_eq!( + expected, + EthtoolMessage::parse_with_param( + &raw[4..], + GenlHeader::parse(&GenlBuffer::new(&raw)).unwrap() + ) + .unwrap(), + ); +} + +#[test] +fn test_channels_set_rx() { + let expected: Vec = vec![ + 0x10, 0x00, 0x01, 0x80, 0x09, 0x00, 0x02, 0x00, 0x65, 0x74, 0x68, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x06, 0x00, 0x02, 0x00, 0x00, 0x00, + ]; + + let msg = EthtoolMessage { + cmd: EthtoolCmd::ChannelSet, + nlas: vec![ + EthtoolAttr::Channel(EthtoolChannelAttr::Header(vec![ + EthtoolHeader::DevName("eth0".to_string()), + ])), + EthtoolAttr::Channel(EthtoolChannelAttr::RxCount(2)), + ], + }; + + let mut raw = vec![0; msg.buffer_len()]; + msg.emit(&mut raw); + + assert_eq!(expected, raw,); +} + +#[test] +fn test_channels_set_tx() { + let expected: Vec = vec![ + 0x10, 0x00, 0x01, 0x80, 0x09, 0x00, 0x02, 0x00, 0x65, 0x74, 0x68, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x07, 0x00, 0x02, 0x00, 0x00, 0x00, + ]; + + let msg = EthtoolMessage { + cmd: EthtoolCmd::ChannelSet, + nlas: vec![ + EthtoolAttr::Channel(EthtoolChannelAttr::Header(vec![ + EthtoolHeader::DevName("eth0".to_string()), + ])), + EthtoolAttr::Channel(EthtoolChannelAttr::TxCount(2)), + ], + }; + + let mut raw = vec![0; msg.buffer_len()]; + msg.emit(&mut raw); + + assert_eq!(expected, raw,); +} + +#[test] +fn test_channels_set_other() { + let expected: Vec = vec![ + 0x10, 0x00, 0x01, 0x80, 0x09, 0x00, 0x02, 0x00, 0x65, 0x74, 0x68, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, + ]; + + let msg = EthtoolMessage { + cmd: EthtoolCmd::ChannelSet, + nlas: vec![ + EthtoolAttr::Channel(EthtoolChannelAttr::Header(vec![ + EthtoolHeader::DevName("eth0".to_string()), + ])), + EthtoolAttr::Channel(EthtoolChannelAttr::OtherCount(2)), + ], + }; + + let mut raw = vec![0; msg.buffer_len()]; + msg.emit(&mut raw); + + assert_eq!(expected, raw,); +} + +#[test] +fn test_channels_set_combined() { + let expected: Vec = vec![ + 0x10, 0x00, 0x01, 0x80, 0x09, 0x00, 0x02, 0x00, 0x65, 0x74, 0x68, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x09, 0x00, 0x02, 0x00, 0x00, 0x00, + ]; + + let msg = EthtoolMessage { + cmd: EthtoolCmd::ChannelSet, + nlas: vec![ + EthtoolAttr::Channel(EthtoolChannelAttr::Header(vec![ + EthtoolHeader::DevName("eth0".to_string()), + ])), + EthtoolAttr::Channel(EthtoolChannelAttr::CombinedCount(2)), + ], + }; + + let mut raw = vec![0; msg.buffer_len()]; + msg.emit(&mut raw); + + assert_eq!(expected, raw,); +}