Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[target.x86_64-unknown-linux-gnu]
runner = 'sudo'
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,9 @@ tokio = { version = "1.44.0", features = ["macros", "rt", "rt-multi-thread"] }
env_logger = "0.9.3"

[[example]]
name = "dump_pause"
name = "get_pause_conf"
required-features = ["tokio_socket"]

[[example]]
name = "apply_pause_conf"
required-features = ["tokio_socket"]
50 changes: 49 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,49 @@
# Rust crate for linux ethtool
# Rust crate for linux ethtool netlink management

This crate provides methods to query and configure the Linux network
interface settings via the ethtool generic netlink protocol.

The examples are built with the crate and need root (`CAP_NET_ADMIN`) to run:

```bash
cargo build --examples
sudo ./target/debug/examples/get_pause_conf
```

## Query

- [get or dump pause configuration](examples/get_pause_conf.rs)
- [query features](examples/query_features.rs)
- [query link modes](examples/query_link_mode.rs)
- [query rings](examples/query_rings.rs)
- [query coalesce](examples/query_coalesce.rs)
- [query timestamping information](examples/query_tsinfo.rs)
- [query FEC](examples/query_fec.rs)
- [query channels](examples/query_channels.rs)
- [query module EEPROM](examples/query_eeprom.rs)

## Set

- [apply pause configuration](examples/apply_pause_conf.rs)
- [set channel count](examples/set_channel_count.rs)

## Notes

- `EthtoolHandle::get_pause()` takes a mandatory interface name and returns
a single `EthtoolPauseParsed`, `EthtoolHandle::dump_pause()` takes no
interface name and returns a `Vec<EthtoolPauseParsed>`.
- `EthtoolHandle::apply_pause()` applies an `EthtoolPauseParsed`, usually
one returned by `get_pause()` or `dump_pause()` after modifying it. The
sub-handle request builders (`handle.pause().get(...)`,
`handle.pause().set(...)`) stay available as well.
- `EthtoolPauseParsed` is `#[non_exhaustive]`, so it can not be built with
a struct literal outside of this crate. Use `Default::default()` and
assign the properties instead.
- A parsed configuration holds only the properties the kernel reported, a
field holding `None` is not sent in a set request and the kernel keeps
its current value.
- The statistics properties of `EthtoolPauseParsed` are read-only, the
kernel rejects them in a set request, therefore they are not sent.
- `apply_pause_conf` shows how a queried configuration is modified and
applied back, `set_channel_count` uses the builder of the channel set
request.
22 changes: 20 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
virtio-net, veth and netdevsim do not report tx types/rx filters.
- Non-empty `EthtoolLinkModeAttr::Ours`/`Peer` bitsets are not covered,
none of the available virtual NICs reports supported link modes.
- `EthtoolPauseStatAttr::TxPauseStormEvents` parse path is not covered,
netdevsim does not report pause storm events.

## API review

Expand All @@ -44,15 +46,31 @@
`ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN_MAX` (16),
`ETHTOOL_A_RINGS_HDS_THRESH` (17),
`ETHTOOL_A_RINGS_HDS_THRESH_MAX` (18).
- `pause`: `ETHTOOL_A_PAUSE_STATS_SRC` (6) and
`ETHTOOL_A_PAUSE_STAT_TX_PAUSE_STORM_EVENTS` (4).
- `tsinfo`: `ETHTOOL_A_TSINFO_HWTSTAMP_PROVIDER` (7),
`ETHTOOL_A_TSINFO_HWTSTAMP_SOURCE` (8),
`ETHTOOL_A_TSINFO_HWTSTAMP_PHYINDEX` (9).
- `EthtoolModuleEEPROMAttr` only parses `HEADER` and `DATA`,
`OFFSET`/`LENGTH`/`PAGE`/`BANK`/`I2C_ADDRESS` fall through to
`DefaultNla` (they are only used in requests today).

## apply support

`pause` is the pilot for the parsed/apply design:

- A get request executes into parsed types (`EthtoolPauseParsed`): the flat
`EthtoolHandle::get_pause()` takes a mandatory interface name and returns
one parsed configuration, `EthtoolHandle::dump_pause()` returns all of
them. `EthtoolHandle::apply_pause()` applies a parsed type back with a
set request. The old sub-handle request builders stay available.
- `build()` of the parsed type emits only fields holding `Some` (the
kernel keeps the value of the others) and validates the input, failing
with `EthtoolError::InvalidInput`.
- Roll out to the commands with kernel `*_SET` support not covered yet:
`link_mode` (`ADVERTISING`), `feature` (`WANTED`), `ring`, `coalesce`,
`fec` (`MODES`), `tsinfo`, EEE.
- `EthtoolError` could hold `{kind, msg, netlink_msg}` like
`WireguardError` instead of the current enum.

## missing support

- `EthtoolMessage` parses a subset of ethtool commands only: `STATS_GET`,
Expand Down
52 changes: 52 additions & 0 deletions examples/apply_pause_conf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT

use std::env;

// Once we find a way to load netsimdev kernel module in CI, we can convert this
// to a test
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() != 2 {
usage();
return;
}
let link_name = &args[1];
let rt = tokio::runtime::Builder::new_current_thread()
.enable_io()
.build()
.unwrap();
rt.block_on(apply_pause_conf(link_name));
}

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

// Fetch the current configuration, change what is wanted and apply it
// back. Fields left as None are not touched by the kernel.
let mut config = handle.get_pause(iface_name).await.unwrap();
config.rx = Some(true);
config.tx = Some(true);

let result = handle.apply_pause(config).await;

if let Err(error) = result {
panic!("{:?}", error);
}
}

fn usage() {
eprintln!(
"usage:
cargo run --example apply_pause_conf -- <link name>

Note that you need to run this program as root. Instead of running cargo as root,
build the example normally:

cd ethtool ; cargo build --example apply_pause_conf

Then find the binary in the target directory:

cd target/debug/examples ; sudo ./apply_pause_conf <link_name>"
);
}
24 changes: 13 additions & 11 deletions examples/dump_pause.rs → examples/get_pause_conf.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT

use futures_util::stream::StreamExt;
use std::env;

// Once we find a way to load netsimdev kernel module in CI, we can convert this
// to a test
Expand All @@ -10,20 +10,22 @@ fn main() {
.enable_io()
.build()
.unwrap();
rt.block_on(get_pause(None));
rt.block_on(run());
}

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

let mut pause_handle =
handle.pause().get(iface_name).execute().await.unwrap();

let mut msgs = Vec::new();
while let Some(Ok(msg)) = pause_handle.next().await {
msgs.push(msg);
match env::args().nth(1) {
Some(iface_name) => {
let config = handle.get_pause(&iface_name).await.unwrap();
println!("{config:?}");
}
None => {
for config in handle.dump_pause().await.unwrap() {
println!("{config:?}");
}
}
}
assert!(!msgs.is_empty());
println!("{msgs:?}");
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
10 changes: 5 additions & 5 deletions examples/set_rx_count.rs → examples/set_channel_count.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ fn main() {
.enable_io()
.build()
.unwrap();
rt.block_on(set_rx_count(link_name));
rt.block_on(set_channel_count(link_name));
}

async fn set_rx_count(iface_name: &str) {
async fn set_channel_count(iface_name: &str) {
let (connection, mut handle, _) = ethtool::new_connection().unwrap();
tokio::spawn(connection);

Expand All @@ -32,15 +32,15 @@ async fn set_rx_count(iface_name: &str) {
fn usage() {
eprintln!(
"usage:
cargo run --example set_rx_count -- <link name>
cargo run --example set_channel_count -- <link name>

Note that you need to run this program as root. Instead of running cargo as root,
build the example normally:

cd ethtool ; cargo build --example set_rx_count
cd ethtool ; cargo build --example set_channel_count

Then find the binary in the target directory:

cd target/debug/example ; sudo ./set_rx_count <link_name>"
cd target/debug/examples ; sudo ./set_channel_count <link_name>"
);
}
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ pub enum EthtoolError {
#[error("A netlink request failed")]
RequestFailed(String),

#[error("Invalid input: {0}")]
InvalidInput(String),

#[error("A bug in this crate")]
Bug(String),
}
2 changes: 1 addition & 1 deletion src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use netlink_packet_core::{
Nla, NlaBuffer, Parseable,
};

const ALTIFNAMSIZ: usize = 128;
pub(crate) 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;
Expand Down
7 changes: 6 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,16 @@ pub use link_mode::{
pub use message::{EthtoolAttr, EthtoolCmd, EthtoolMessage};
pub use pause::{
EthtoolPauseAttr, EthtoolPauseGetRequest, EthtoolPauseHandle,
EthtoolPauseStatAttr,
EthtoolPauseParsed, EthtoolPauseSetRequest, EthtoolPauseStatAttr,
};
pub use ring::{EthtoolRingAttr, EthtoolRingGetRequest, EthtoolRingHandle};
pub use tsinfo::{
EthtoolTsInfoAttr, EthtoolTsInfoGetRequest, EthtoolTsInfoHandle,
};

pub(crate) use handle::ethtool_execute;

// Compile the examples of the README as doc tests.
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct ReadmeDoctests;
5 changes: 5 additions & 0 deletions src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ use crate::{
};

const ETHTOOL_MSG_PAUSE_GET: u8 = 21;
/// The kernel uses the same numeric value for the `GET_REPLY` (kernel
/// namespace) and the `SET` (user namespace) command.
const ETHTOOL_MSG_PAUSE_SET: u8 = 22;
const ETHTOOL_MSG_PAUSE_GET_REPLY: u8 = 22;
const ETHTOOL_MSG_FEATURES_GET: u8 = 11;
const ETHTOOL_MSG_FEATURES_GET_REPLY: u8 = 11;
Expand All @@ -40,6 +43,7 @@ const ETHTOOL_MSG_MODULE_EEPROM_GET_REPLY: u8 = 32;
pub enum EthtoolCmd {
PauseGet,
PauseGetReply,
PauseSet,
FeatureGet,
FeatureGetReply,
LinkModeGet,
Expand All @@ -64,6 +68,7 @@ impl From<EthtoolCmd> for u8 {
match cmd {
EthtoolCmd::PauseGet => ETHTOOL_MSG_PAUSE_GET,
EthtoolCmd::PauseGetReply => ETHTOOL_MSG_PAUSE_GET_REPLY,
EthtoolCmd::PauseSet => ETHTOOL_MSG_PAUSE_SET,
EthtoolCmd::FeatureGet => ETHTOOL_MSG_FEATURES_GET,
EthtoolCmd::FeatureGetReply => ETHTOOL_MSG_FEATURES_GET_REPLY,
EthtoolCmd::LinkModeGet => ETHTOOL_MSG_LINKMODES_GET,
Expand Down
Loading
Loading