Skip to content
Open
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
33 changes: 33 additions & 0 deletions examples/dump_link_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT

use futures_util::stream::StreamExt;

fn main() {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_io()
.build()
.unwrap();
let iface_name = std::env::args().nth(1);
rt.block_on(get_link_state(iface_name.as_deref()));
}

async fn get_link_state(iface_name: Option<&str>) {
let (connection, mut handle, _) = ethtool::new_connection().unwrap();
tokio::spawn(connection);

let mut link_state_handle = handle
.link_state()
.get(iface_name, Some(0))
.execute()
.await
.unwrap();

let mut msgs = Vec::new();
while let Some(Ok(msg)) = link_state_handle.next().await {
msgs.push(msg);
}
assert!(!msgs.is_empty());
for msg in msgs {
println!("{:?}", msg);
}
}
8 changes: 6 additions & 2 deletions src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use netlink_packet_generic::GenlMessage;
use crate::{
try_ethtool, EthtoolChannelHandle, EthtoolCoalesceHandle, EthtoolError,
EthtoolFeatureHandle, EthtoolFecHandle, EthtoolLinkModeHandle,
EthtoolMessage, EthtoolModuleEEPROMHandle, EthtoolPauseHandle,
EthtoolRingHandle, EthtoolTsInfoHandle,
EthtoolLinkStateHandle, EthtoolMessage, EthtoolModuleEEPROMHandle,
EthtoolPauseHandle, EthtoolRingHandle, EthtoolTsInfoHandle,
};

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -61,6 +61,10 @@ impl EthtoolHandle {
EthtoolModuleEEPROMHandle::new(self.clone())
}

pub fn link_state(&mut self) -> EthtoolLinkStateHandle {
EthtoolLinkStateHandle::new(self.clone())
}

pub async fn request(
&mut self,
message: NetlinkMessage<GenlMessage<EthtoolMessage>>,
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod fec;
mod handle;
mod header;
mod link_mode;
mod link_state;
mod macros;
mod message;
mod pause;
Expand Down Expand Up @@ -46,6 +47,9 @@ pub use link_mode::{
EthtoolLinkModeAttr, EthtoolLinkModeDuplex, EthtoolLinkModeGetRequest,
EthtoolLinkModeHandle,
};
pub use link_state::{
EthtoolLinkStateAttr, EthtoolLinkStateGetRequest, EthtoolLinkStateHandle,
};
pub use message::{EthtoolAttr, EthtoolCmd, EthtoolMessage};
pub use pause::{
EthtoolPauseAttr, EthtoolPauseGetRequest, EthtoolPauseHandle,
Expand Down
126 changes: 126 additions & 0 deletions src/link_state/attr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// SPDX-License-Identifier: MIT

use netlink_packet_core::{
parse_u32, parse_u8, DecodeError, DefaultNla, Emitable, ErrorContext, Nla,
NlaBuffer, NlasIterator, Parseable, NLA_F_NESTED,
};

use crate::{EthtoolAttr, EthtoolHeader};

const ETHTOOL_A_LINKSTATE_HEADER: u16 = 1;
const ETHTOOL_A_LINKSTATE_LINK: u16 = 2;
const ETHTOOL_A_LINKSTATE_SQI: u16 = 3;
const ETHTOOL_A_LINKSTATE_SQI_MAX: u16 = 4;
const ETHTOOL_A_LINKSTATE_EXT_STATE: u16 = 5;
const ETHTOOL_A_LINKSTATE_EXT_SUBSTATE: u16 = 6;
const ETHTOOL_A_LINKSTATE_EXT_DOWN_CNT: u16 = 7;

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum EthtoolLinkStateAttr {
Header(Vec<EthtoolHeader>),
Link(bool),
Sqi(u32),
SqiMax(u32),
ExtState(u8),
ExtSubstate(u8),
ExtDownCnt(u32),
Other(DefaultNla),
}

impl Nla for EthtoolLinkStateAttr {
fn value_len(&self) -> usize {
match self {
Self::Header(hdrs) => hdrs.as_slice().buffer_len(),
Self::Link(_) | Self::ExtState(_) | Self::ExtSubstate(_) => 1,
Self::Sqi(_) | Self::SqiMax(_) | Self::ExtDownCnt(_) => 4,
Self::Other(attr) => attr.value_len(),
}
}

fn kind(&self) -> u16 {
match self {
Self::Header(_) => ETHTOOL_A_LINKSTATE_HEADER | NLA_F_NESTED,
Self::Link(_) => ETHTOOL_A_LINKSTATE_LINK,
Self::Sqi(_) => ETHTOOL_A_LINKSTATE_SQI,
Self::SqiMax(_) => ETHTOOL_A_LINKSTATE_SQI_MAX,
Self::ExtState(_) => ETHTOOL_A_LINKSTATE_EXT_STATE,
Self::ExtSubstate(_) => ETHTOOL_A_LINKSTATE_EXT_SUBSTATE,
Self::ExtDownCnt(_) => ETHTOOL_A_LINKSTATE_EXT_DOWN_CNT,
Self::Other(attr) => attr.kind(),
}
}

fn emit_value(&self, buffer: &mut [u8]) {
match self {
Self::Header(ref nlas) => nlas.as_slice().emit(buffer),
Self::Other(ref attr) => attr.emit(buffer),
_ => todo!("Does not support changing link state"),
}
}
Comment on lines +53 to +59

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using todo! here will cause a panic if any code attempts to serialize EthtoolLinkStateAttr variants other than Header or Other. While this is not the case for get_link_state, it could be an unexpected trap for future developers using this crate, especially if set functionality is added. It would be better to use unimplemented! which is more idiomatic for this situation. This makes the intent clearer that this path is not yet supported for emission.

Suggested change
fn emit_value(&self, buffer: &mut [u8]) {
match self {
Self::Header(ref nlas) => nlas.as_slice().emit(buffer),
Self::Other(ref attr) => attr.emit(buffer),
_ => todo!("Does not support changing link state"),
}
}
fn emit_value(&self, buffer: &mut [u8]) {
match self {
Self::Header(ref nlas) => nlas.as_slice().emit(buffer),
Self::Other(ref attr) => attr.emit(buffer),
_ => unimplemented!("Does not support changing link state"),
}
}

}

impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<NlaBuffer<&'a T>>
for EthtoolLinkStateAttr
{
fn parse(buf: &NlaBuffer<&'a T>) -> Result<Self, DecodeError> {
let payload = buf.value();
Ok(match buf.kind() {
ETHTOOL_A_LINKSTATE_HEADER => {
let mut nlas = Vec::new();
let error_msg = "failed to parse link state 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_LINKSTATE_LINK => Self::Link(
parse_u8(payload)
.context("invalid ETHTOOL_A_LINKSTATE_LINK value")?
== 1,
),
ETHTOOL_A_LINKSTATE_SQI => Self::Sqi(
parse_u32(payload)
.context("invalid ETHTOOL_A_LINKSTATE_SQI value")?,
),
ETHTOOL_A_LINKSTATE_SQI_MAX => Self::SqiMax(
parse_u32(payload)
.context("invalid ETHTOOL_A_LINKSTATE_SQI_MAX value")?,
),
ETHTOOL_A_LINKSTATE_EXT_STATE => Self::ExtState(
parse_u8(payload)
.context("invalid ETHTOOL_A_LINKSTATE_EXT_STATE value")?,
),
ETHTOOL_A_LINKSTATE_EXT_SUBSTATE => {
Self::ExtSubstate(parse_u8(payload).context(
"invalid ETHTOOL_A_LINKSTATE_EXT_SUBSTATE value",
)?)
}
ETHTOOL_A_LINKSTATE_EXT_DOWN_CNT => {
Self::ExtDownCnt(parse_u32(payload).context(
"invalid ETHTOOL_A_LINKSTATE_EXT_DOWN_CNT value",
)?)
}
_ => Self::Other(
DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?,
),
})
}
}

pub(crate) fn parse_link_state_nlas(
buffer: &[u8],
) -> Result<Vec<EthtoolAttr>, DecodeError> {
let mut nlas = Vec::new();
for nla in NlasIterator::new(buffer) {
let error_msg = format!(
"Failed to parse ethtool link state message attribute {nla:?}"
);
let nla = &nla.context(error_msg.clone())?;
let parsed = EthtoolLinkStateAttr::parse(nla).context(error_msg)?;
nlas.push(EthtoolAttr::LinkState(parsed));
}
Comment on lines +117 to +124

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation for parsing NLAs is a bit inefficient and the error message can be confusing:

  • It calls format! and clone() on each iteration, which is not performant.
  • The nla variable in format! is a Result, so the error message will contain Ok(...) which is not ideal.

A cleaner and more performant approach would be to handle the Result from the iterator first, and then parse the NlaBuffer. This avoids unnecessary allocations and provides clearer error messages.

    for nla in NlasIterator::new(buffer) {
        let nla = nla.context("Failed to parse link state NLA header")?;
        let parsed = EthtoolLinkStateAttr::parse(&nla)
            .context(format!("Failed to parse link state NLA: {nla:?}"))?;
        nlas.push(EthtoolAttr::LinkState(parsed));
    }

Ok(nlas)
}
43 changes: 43 additions & 0 deletions src/link_state/get.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT

use futures_util::Stream;
use netlink_packet_generic::GenlMessage;

use crate::{ethtool_execute, EthtoolError, EthtoolHandle, EthtoolMessage};

pub struct EthtoolLinkStateGetRequest {
handle: EthtoolHandle,
iface_name: Option<String>,
flags: Option<u32>,
}

impl EthtoolLinkStateGetRequest {
pub(crate) fn new(
handle: EthtoolHandle,
iface_name: Option<&str>,
flags: Option<u32>,
) -> Self {
EthtoolLinkStateGetRequest {
handle,
iface_name: iface_name.map(|i| i.to_string()),
flags,
}
}

pub async fn execute(
self,
) -> Result<
impl Stream<Item = Result<GenlMessage<EthtoolMessage>, EthtoolError>>,
EthtoolError,
> {
let EthtoolLinkStateGetRequest {
mut handle,
iface_name,
flags,
} = self;

let ethtool_msg =
EthtoolMessage::new_link_state_get(iface_name.as_deref(), flags);
ethtool_execute(&mut handle, iface_name.is_none(), ethtool_msg).await
}
}
20 changes: 20 additions & 0 deletions src/link_state/handle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT

use crate::{EthtoolHandle, EthtoolLinkStateGetRequest};

pub struct EthtoolLinkStateHandle(EthtoolHandle);

impl EthtoolLinkStateHandle {
pub fn new(handle: EthtoolHandle) -> Self {
EthtoolLinkStateHandle(handle)
}

/// Retrieve the current link state of an interface
pub fn get(
&mut self,
iface_name: Option<&str>,
flags: Option<u32>,
) -> EthtoolLinkStateGetRequest {
EthtoolLinkStateGetRequest::new(self.0.clone(), iface_name, flags)
}
}
11 changes: 11 additions & 0 deletions src/link_state/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT

mod attr;
mod get;
mod handle;

pub(crate) use attr::parse_link_state_nlas;

pub use attr::EthtoolLinkStateAttr;
pub use get::EthtoolLinkStateGetRequest;
pub use handle::EthtoolLinkStateHandle;
34 changes: 34 additions & 0 deletions src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
feature::{parse_feature_nlas, EthtoolFeatureAttr},
fec::{parse_fec_nlas, EthtoolFecAttr},
link_mode::{parse_link_mode_nlas, EthtoolLinkModeAttr},
link_state::{parse_link_state_nlas, EthtoolLinkStateAttr},
pause::{parse_pause_nlas, EthtoolPauseAttr},
ring::{parse_ring_nlas, EthtoolRingAttr},
tsinfo::{parse_tsinfo_nlas, EthtoolTsInfoAttr},
Expand All @@ -33,6 +34,8 @@ const ETHTOOL_MSG_FEC_GET_REPLY: u8 = 30;
const ETHTOOL_MSG_CHANNELS_GET: u8 = 17;
const ETHTOOL_MSG_CHANNELS_GET_REPLY: u8 = 18;
const ETHTOOL_MSG_CHANNELS_SET: u8 = 18;
const ETHTOOL_MSG_LINKSTATE_GET: u8 = 6;
const ETHTOOL_MSG_LINKSTATE_GET_REPLY: u8 = 6;
const ETHTOOL_MSG_MODULE_EEPROM_GET: u8 = 31;
const ETHTOOL_MSG_MODULE_EEPROM_GET_REPLY: u8 = 32;

Expand All @@ -55,6 +58,8 @@ pub enum EthtoolCmd {
ChannelGet,
ChannelGetReply,
ChannelSet,
LinkStateGet,
LinkStateGetReply,
ModuleEEPROMGet,
ModuleEEPROMGetReply,
}
Expand Down Expand Up @@ -83,6 +88,8 @@ impl From<EthtoolCmd> for u8 {
EthtoolCmd::ModuleEEPROMGetReply => {
ETHTOOL_MSG_MODULE_EEPROM_GET_REPLY
}
EthtoolCmd::LinkStateGet => ETHTOOL_MSG_LINKSTATE_GET,
EthtoolCmd::LinkStateGetReply => ETHTOOL_MSG_LINKSTATE_GET_REPLY,
}
}
}
Expand All @@ -98,6 +105,7 @@ pub enum EthtoolAttr {
Fec(EthtoolFecAttr),
Channel(EthtoolChannelAttr),
ModuleEEPROM(EthtoolModuleEEPROMAttr),
LinkState(EthtoolLinkStateAttr),
}

impl Nla for EthtoolAttr {
Expand All @@ -112,6 +120,7 @@ impl Nla for EthtoolAttr {
Self::Fec(attr) => attr.value_len(),
Self::Channel(attr) => attr.value_len(),
Self::ModuleEEPROM(attr) => attr.value_len(),
Self::LinkState(attr) => attr.value_len(),
}
}

Expand All @@ -126,6 +135,7 @@ impl Nla for EthtoolAttr {
Self::Fec(attr) => attr.kind(),
Self::Channel(attr) => attr.kind(),
Self::ModuleEEPROM(attr) => attr.kind(),
Self::LinkState(attr) => attr.kind(),
}
}

Expand All @@ -140,6 +150,7 @@ impl Nla for EthtoolAttr {
Self::Fec(attr) => attr.emit_value(buffer),
Self::Channel(attr) => attr.emit_value(buffer),
Self::ModuleEEPROM(attr) => attr.emit_value(buffer),
Self::LinkState(attr) => attr.emit_value(buffer),
}
}
}
Expand Down Expand Up @@ -349,6 +360,25 @@ impl EthtoolMessage {
nlas,
}
}

pub fn new_link_state_get(
iface_name: Option<&str>,
flags: Option<u32>,
) -> Self {
let mut header = vec![];
if let Some(s) = iface_name {
header.push(EthtoolHeader::DevName(s.to_string()))
}
if let Some(f) = flags {
header.push(EthtoolHeader::Flags(f))
}
let nlas =
vec![EthtoolAttr::LinkState(EthtoolLinkStateAttr::Header(header))];
EthtoolMessage {
cmd: EthtoolCmd::LinkStateGet,
nlas,
}
}
}

impl Emitable for EthtoolMessage {
Expand Down Expand Up @@ -403,6 +433,10 @@ impl ParseableParametrized<[u8], GenlHeader> for EthtoolMessage {
cmd: EthtoolCmd::ModuleEEPROMGetReply,
nlas: parse_module_eeprom_nlas(buffer)?,
},
ETHTOOL_MSG_LINKSTATE_GET_REPLY => Self {
cmd: EthtoolCmd::LinkStateGetReply,
nlas: parse_link_state_nlas(buffer)?,
},
cmd => {
return Err(DecodeError::from(format!(
"Unsupported ethtool reply command: {cmd}"
Expand Down
Loading