Skip to content

Commit 859790d

Browse files
connorworleyarctic-alpaca
authored andcommitted
Add channel get and set functionality
1 parent 45df4a9 commit 859790d

File tree

11 files changed

+593
-3
lines changed

11 files changed

+593
-3
lines changed

examples/dump_channels.rs

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
use futures::stream::TryStreamExt;
4+
5+
// Once we find a way to load netsimdev kernel module in CI, we can convert this
6+
// to a test
7+
fn main() {
8+
let rt = tokio::runtime::Builder::new_current_thread()
9+
.enable_io()
10+
.build()
11+
.unwrap();
12+
rt.block_on(get_channels(None));
13+
}
14+
15+
async fn get_channels(iface_name: Option<&str>) {
16+
let (connection, mut handle, _) = ethtool::new_connection().unwrap();
17+
tokio::spawn(connection);
18+
19+
let mut channel_handle = handle.channel().get(iface_name).execute().await;
20+
21+
let mut msgs = Vec::new();
22+
while let Some(msg) = channel_handle.try_next().await.unwrap() {
23+
msgs.push(msg);
24+
}
25+
assert!(!msgs.is_empty());
26+
for msg in msgs {
27+
println!("{msg:?}");
28+
}
29+
}

examples/set_rx_count.rs

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
use std::env;
4+
5+
// Once we find a way to load netsimdev kernel module in CI, we can convert this
6+
// to a test
7+
fn main() {
8+
let args: Vec<String> = env::args().collect();
9+
if args.len() != 2 {
10+
usage();
11+
return;
12+
}
13+
let link_name = &args[1];
14+
let rt = tokio::runtime::Builder::new_current_thread()
15+
.enable_io()
16+
.build()
17+
.unwrap();
18+
rt.block_on(set_rx_count(link_name));
19+
}
20+
21+
async fn set_rx_count(iface_name: &str) {
22+
let (connection, mut handle, _) = ethtool::new_connection().unwrap();
23+
tokio::spawn(connection);
24+
25+
let result = handle.channel().set(iface_name).rx_count(4).execute().await;
26+
27+
if let Err(error) = result {
28+
panic!("{:?}", error);
29+
}
30+
}
31+
32+
fn usage() {
33+
eprintln!(
34+
"usage:
35+
cargo run --example set_rx_count -- <link name>
36+
37+
Note that you need to run this program as root. Instead of running cargo as root,
38+
build the example normally:
39+
40+
cd ethtool ; cargo build --example set_rx_count
41+
42+
Then find the binary in the target directory:
43+
44+
cd target/debug/example ; sudo ./set_rx_count <link_name>"
45+
);
46+
}

src/channel/attr.rs

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
use anyhow::Context;
4+
use byteorder::{ByteOrder, NativeEndian};
5+
use netlink_packet_utils::{
6+
nla::{DefaultNla, Nla, NlaBuffer, NlasIterator, NLA_F_NESTED},
7+
parsers::parse_u32,
8+
DecodeError, Emitable, Parseable,
9+
};
10+
11+
use crate::{EthtoolAttr, EthtoolHeader};
12+
13+
const ETHTOOL_A_CHANNELS_HEADER: u16 = 1;
14+
const ETHTOOL_A_CHANNELS_RX_MAX: u16 = 2;
15+
const ETHTOOL_A_CHANNELS_TX_MAX: u16 = 3;
16+
const ETHTOOL_A_CHANNELS_OTHER_MAX: u16 = 4;
17+
const ETHTOOL_A_CHANNELS_COMBINED_MAX: u16 = 5;
18+
const ETHTOOL_A_CHANNELS_RX_COUNT: u16 = 6;
19+
const ETHTOOL_A_CHANNELS_TX_COUNT: u16 = 7;
20+
const ETHTOOL_A_CHANNELS_OTHER_COUNT: u16 = 8;
21+
const ETHTOOL_A_CHANNELS_COMBINED_COUNT: u16 = 9;
22+
23+
#[derive(Debug, PartialEq, Eq, Clone)]
24+
pub enum EthtoolChannelAttr {
25+
Header(Vec<EthtoolHeader>),
26+
RxMax(u32),
27+
TxMax(u32),
28+
OtherMax(u32),
29+
CombinedMax(u32),
30+
RxCount(u32),
31+
TxCount(u32),
32+
OtherCount(u32),
33+
CombinedCount(u32),
34+
Other(DefaultNla),
35+
}
36+
37+
impl Nla for EthtoolChannelAttr {
38+
fn value_len(&self) -> usize {
39+
match self {
40+
Self::Header(hdrs) => hdrs.as_slice().buffer_len(),
41+
Self::RxMax(_)
42+
| Self::TxMax(_)
43+
| Self::OtherMax(_)
44+
| Self::CombinedMax(_)
45+
| Self::RxCount(_)
46+
| Self::TxCount(_)
47+
| Self::OtherCount(_)
48+
| Self::CombinedCount(_) => 4,
49+
Self::Other(attr) => attr.value_len(),
50+
}
51+
}
52+
53+
fn kind(&self) -> u16 {
54+
match self {
55+
Self::Header(_) => ETHTOOL_A_CHANNELS_HEADER | NLA_F_NESTED,
56+
Self::RxMax(_) => ETHTOOL_A_CHANNELS_RX_MAX,
57+
Self::TxMax(_) => ETHTOOL_A_CHANNELS_TX_MAX,
58+
Self::OtherMax(_) => ETHTOOL_A_CHANNELS_OTHER_MAX,
59+
Self::CombinedMax(_) => ETHTOOL_A_CHANNELS_COMBINED_MAX,
60+
Self::RxCount(_) => ETHTOOL_A_CHANNELS_RX_COUNT,
61+
Self::TxCount(_) => ETHTOOL_A_CHANNELS_TX_COUNT,
62+
Self::OtherCount(_) => ETHTOOL_A_CHANNELS_OTHER_COUNT,
63+
Self::CombinedCount(_) => ETHTOOL_A_CHANNELS_COMBINED_COUNT,
64+
Self::Other(attr) => attr.kind(),
65+
}
66+
}
67+
68+
fn emit_value(&self, buffer: &mut [u8]) {
69+
match self {
70+
Self::Header(ref nlas) => nlas.as_slice().emit(buffer),
71+
Self::RxMax(d)
72+
| Self::TxMax(d)
73+
| Self::OtherMax(d)
74+
| Self::CombinedMax(d)
75+
| Self::RxCount(d)
76+
| Self::TxCount(d)
77+
| Self::OtherCount(d)
78+
| Self::CombinedCount(d) => NativeEndian::write_u32(buffer, *d),
79+
Self::Other(ref attr) => attr.emit(buffer),
80+
}
81+
}
82+
}
83+
84+
impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<NlaBuffer<&'a T>>
85+
for EthtoolChannelAttr
86+
{
87+
fn parse(buf: &NlaBuffer<&'a T>) -> Result<Self, DecodeError> {
88+
let payload = buf.value();
89+
Ok(match buf.kind() {
90+
ETHTOOL_A_CHANNELS_HEADER => {
91+
let mut nlas = Vec::new();
92+
let error_msg = "failed to parse channel header attributes";
93+
for nla in NlasIterator::new(payload) {
94+
let nla = &nla.context(error_msg)?;
95+
let parsed =
96+
EthtoolHeader::parse(nla).context(error_msg)?;
97+
nlas.push(parsed);
98+
}
99+
Self::Header(nlas)
100+
}
101+
ETHTOOL_A_CHANNELS_RX_MAX => Self::RxMax(
102+
parse_u32(payload)
103+
.context("Invalid ETHTOOL_A_CHANNELS_RX_MAX value")?,
104+
),
105+
ETHTOOL_A_CHANNELS_TX_MAX => Self::TxMax(
106+
parse_u32(payload)
107+
.context("Invalid ETHTOOL_A_CHANNELS_RX_MAX value")?,
108+
),
109+
ETHTOOL_A_CHANNELS_OTHER_MAX => Self::OtherMax(
110+
parse_u32(payload)
111+
.context("Invalid ETHTOOL_A_CHANNELS_RX_MAX value")?,
112+
),
113+
ETHTOOL_A_CHANNELS_COMBINED_MAX => Self::CombinedMax(
114+
parse_u32(payload)
115+
.context("Invalid ETHTOOL_A_CHANNELS_RX_MAX value")?,
116+
),
117+
ETHTOOL_A_CHANNELS_RX_COUNT => Self::RxCount(
118+
parse_u32(payload)
119+
.context("Invalid ETHTOOL_A_CHANNELS_RX_COUNT value")?,
120+
),
121+
ETHTOOL_A_CHANNELS_TX_COUNT => Self::TxCount(
122+
parse_u32(payload)
123+
.context("Invalid ETHTOOL_A_CHANNELS_RX_COUNT value")?,
124+
),
125+
ETHTOOL_A_CHANNELS_OTHER_COUNT => Self::OtherCount(
126+
parse_u32(payload)
127+
.context("Invalid ETHTOOL_A_CHANNELS_RX_COUNT value")?,
128+
),
129+
ETHTOOL_A_CHANNELS_COMBINED_COUNT => Self::CombinedCount(
130+
parse_u32(payload)
131+
.context("Invalid ETHTOOL_A_CHANNELS_RX_COUNT value")?,
132+
),
133+
kind => {
134+
Self::Other(DefaultNla::parse(buf).context(format!(
135+
"invalid ethtool channel NLA kind {kind}"
136+
))?)
137+
}
138+
})
139+
}
140+
}
141+
142+
pub(crate) fn parse_channel_nlas(
143+
buffer: &[u8],
144+
) -> Result<Vec<EthtoolAttr>, DecodeError> {
145+
let mut nlas = Vec::new();
146+
for nla in NlasIterator::new(buffer) {
147+
let error_msg = format!(
148+
"Failed to parse ethtool channel message attribute {nla:?}"
149+
);
150+
let nla = &nla.context(error_msg.clone())?;
151+
let parsed = EthtoolChannelAttr::parse(nla).context(error_msg)?;
152+
nlas.push(EthtoolAttr::Channel(parsed));
153+
}
154+
Ok(nlas)
155+
}

src/channel/get.rs

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
use futures::TryStream;
4+
use netlink_packet_generic::GenlMessage;
5+
6+
use crate::{ethtool_execute, EthtoolError, EthtoolHandle, EthtoolMessage};
7+
8+
pub struct EthtoolChannelGetRequest {
9+
handle: EthtoolHandle,
10+
iface_name: Option<String>,
11+
}
12+
13+
impl EthtoolChannelGetRequest {
14+
pub(crate) fn new(handle: EthtoolHandle, iface_name: Option<&str>) -> Self {
15+
EthtoolChannelGetRequest {
16+
handle,
17+
iface_name: iface_name.map(|i| i.to_string()),
18+
}
19+
}
20+
21+
pub async fn execute(
22+
self,
23+
) -> impl TryStream<Ok = GenlMessage<EthtoolMessage>, Error = EthtoolError>
24+
{
25+
let EthtoolChannelGetRequest {
26+
mut handle,
27+
iface_name,
28+
} = self;
29+
30+
let ethtool_msg =
31+
EthtoolMessage::new_channel_get(iface_name.as_deref());
32+
ethtool_execute(&mut handle, iface_name.is_none(), ethtool_msg).await
33+
}
34+
}

src/channel/handle.rs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
use crate::{
4+
EthtoolChannelGetRequest, EthtoolChannelSetRequest, EthtoolHandle,
5+
};
6+
7+
pub struct EthtoolChannelHandle(EthtoolHandle);
8+
9+
impl EthtoolChannelHandle {
10+
pub fn new(handle: EthtoolHandle) -> Self {
11+
EthtoolChannelHandle(handle)
12+
}
13+
14+
/// Retrieve the ethtool Channels of a interface (equivalent to `ethtool -l
15+
/// eth1`)
16+
pub fn get(
17+
&mut self,
18+
iface_name: Option<&str>,
19+
) -> EthtoolChannelGetRequest {
20+
EthtoolChannelGetRequest::new(self.0.clone(), iface_name)
21+
}
22+
23+
/// Set the ethtool Channels of a interface (equivalent to `ethtool -L
24+
/// eth1`)
25+
pub fn set(&mut self, iface_name: &str) -> EthtoolChannelSetRequest {
26+
EthtoolChannelSetRequest::new(self.0.clone(), iface_name)
27+
}
28+
}

src/channel/mod.rs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
mod attr;
4+
mod get;
5+
mod handle;
6+
mod set;
7+
8+
pub(crate) use attr::parse_channel_nlas;
9+
10+
pub use attr::EthtoolChannelAttr;
11+
pub use get::EthtoolChannelGetRequest;
12+
pub use handle::EthtoolChannelHandle;
13+
pub use set::EthtoolChannelSetRequest;

0 commit comments

Comments
 (0)