-
Notifications
You must be signed in to change notification settings - Fork 13
Add support for get_link_state #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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); | ||
| } | ||
| } |
| 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"), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current implementation for parsing NLAs is a bit inefficient and the error message can be confusing:
A cleaner and more performant approach would be to handle the 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) | ||
| } | ||
| 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 | ||
| } | ||
| } |
| 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) | ||
| } | ||
| } |
| 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using
todo!here will cause a panic if any code attempts to serializeEthtoolLinkStateAttrvariants other thanHeaderorOther. While this is not the case forget_link_state, it could be an unexpected trap for future developers using this crate, especially ifsetfunctionality is added. It would be better to useunimplemented!which is more idiomatic for this situation. This makes the intent clearer that this path is not yet supported for emission.