Skip to content

Commit

Permalink
Inversion of control (#57)
Browse files Browse the repository at this point in the history
* Add more inversion of control
The library now depends on the caller to retrieve netlink messages and pass it to `netlink-tc`.
`netlink-tc` accepts `Vec<NetlinkMessage<RtnlMessage>>` which can be fetched using `netlink-packet-route`.
The message is then parsed and processed as earlier.

* Remove dependency to make netlink calls to retrieve `TcMsg`
Pass a new `RtNetlinkMessage` which encapsulates `TcMsg` and `LinkMsg`.
The netlink is already parsed and passed as a parameter rather than making a netlink call here and then parsing.

* Add examples for fetching qdiscs, classes, links

* Scope down errors; refactor imports

* Add `ParseOptions` builder
This enables caller to decide how to handle unknown or unimplemented attributes and options.
Default behavior is to ignore such branches.
  • Loading branch information
mmynk committed Nov 21, 2023
1 parent e6d8612 commit a965cc6
Show file tree
Hide file tree
Showing 13 changed files with 856 additions and 1,209 deletions.
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@ This library is very much in progress. It only supports a small subset of `class
## Usage

```rust
use netlink_packet_core::NetlinkMessage;
use netlink_packet_route::RtnlMessage;
use netlink_tc as tc;

fn main() {
// Get list of qdiscs
let qdiscs = tc::qdiscs::<tc::Netlink>().unwrap();

// Get list of classes
let classes = tc::classes::<tc::Netlink>().unwrap();

// Get class for given interface
let class = tc::class::<tc::Netlink>("eth0").unwrap();
// Retrive netlink messages using `netlink-packet-route`.
// See `examples` for more details.
let messages: Vec<NetlinkMessage<RtnlMessage>> = vec![]; // init with netlink messages

// Get list of tc qdiscs or classes
let qdiscs = OpenOptions::new()
.fail_on_unknown_netlink_message(true)
.tc(messages.clone()).unwrap();

// Get list of links
let links = OpenOptions::new()
.links(messages.clone()).unwrap();
}
```

Expand Down
108 changes: 108 additions & 0 deletions examples/get.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use netlink_packet_core::{
NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST,
};
use netlink_packet_route::{LinkMessage, RtnlMessage, TcHeader, TcMessage};
use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr};
use netlink_tc::ParseOptions;

fn socket() -> Socket {
let socket = Socket::new(NETLINK_ROUTE).unwrap();
socket.connect(&SocketAddr::new(0, 0)).unwrap();
socket
}

fn receive_netlink_messages(message: RtnlMessage) -> Vec<NetlinkMessage<RtnlMessage>> {
let socket = socket();
send_request(&socket, message);

let mut receive_buffer = vec![0; 4096];
let mut offset = 0;

let mut messages = Vec::new();
while let Ok(size) = socket.recv(&mut &mut receive_buffer[..], 0) {
loop {
let bytes = &receive_buffer[offset..];
let rx_packet = <NetlinkMessage<RtnlMessage>>::deserialize(bytes).unwrap();
messages.push(rx_packet.clone());
let payload = rx_packet.payload;
if let NetlinkPayload::Error(err) = payload {
panic!("Netlink error: {:?}", err);
}
if let NetlinkPayload::Done(_) = payload {
return messages;
}

offset += rx_packet.header.length as usize;
if offset == size || rx_packet.header.length == 0 {
offset = 0;
break;
}
}
}

messages
}

fn get_qdiscs() -> Vec<NetlinkMessage<RtnlMessage>> {
receive_netlink_messages(RtnlMessage::GetQueueDiscipline(TcMessage::default()))
}

fn get_classes(index: i32) -> Vec<NetlinkMessage<RtnlMessage>> {
let header = TcHeader {
index,
..Default::default()
};
let mut message = TcMessage::default();
message.header = header;
receive_netlink_messages(RtnlMessage::GetTrafficClass(message))
}

fn get_links() -> Vec<NetlinkMessage<RtnlMessage>> {
receive_netlink_messages(RtnlMessage::GetLink(LinkMessage::default()))
}

fn send_request(socket: &Socket, message: RtnlMessage) {
let mut nl_hdr = NetlinkHeader::default();
nl_hdr.flags = NLM_F_REQUEST | NLM_F_DUMP;

let mut packet = NetlinkMessage::new(nl_hdr, NetlinkPayload::from(message));
packet.finalize();

let mut buf = vec![0; packet.header.length as usize];
packet.serialize(&mut buf[..]);

socket.send(&buf[..], 0).unwrap();
}

fn main() {
let messages = get_qdiscs();
let qdiscs = ParseOptions::new()
.fail_on_unknown_netlink_message(false)
.fail_on_unknown_attribute(false)
.fail_on_unknown_option(false)
.tc(messages)
.unwrap();
println!("length: {}, qdiscs: {:#?}", qdiscs.len(), qdiscs);

let messages = get_links();
let links = ParseOptions::new()
.fail_on_unknown_netlink_message(false)
.fail_on_unknown_attribute(false)
.fail_on_unknown_option(false)
.links(messages)
.unwrap();
println!("length: {}, links: {:#?}", links.len(), links);

let mut messages = Vec::new();
for link in links {
let classes = get_classes(link.index as i32);
messages.extend(classes);
}
let classes = ParseOptions::new()
.fail_on_unknown_netlink_message(false)
.fail_on_unknown_attribute(false)
.fail_on_unknown_option(false)
.tc(messages)
.unwrap();
println!("length: {}, classes: {:#?}", classes.len(), classes);
}
16 changes: 8 additions & 8 deletions src/class/htb.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};

use crate::{errors::TcError, types::*};
use crate::{errors::Error, types::*};

/// Defined in `include/uapi/linux/pkt_sched.h`.
#[derive(Default, Debug, PartialEq)]
Expand Down Expand Up @@ -83,7 +83,7 @@ impl Htb {
}

impl HtbXstats {
pub fn new(bytes: &[u8]) -> Result<Self, TcError> {
pub fn new(bytes: &[u8]) -> Result<Self, Error> {
unmarshal_htb_xstats(bytes)
}
}
Expand Down Expand Up @@ -135,14 +135,14 @@ fn unmarshal_htb(opts: Vec<TcOption>) -> Htb {
htb
}

fn unmarshal_htb_opt(bytes: &[u8]) -> Result<HtbOpt, TcError> {
bincode::deserialize(bytes).map_err(TcError::UnmarshalStruct)
fn unmarshal_htb_opt(bytes: &[u8]) -> Result<HtbOpt, Error> {
bincode::deserialize(bytes).map_err(|e| Error::Parse(e.to_string()))
}

fn unmarshal_htb_glob(bytes: &[u8]) -> Result<HtbGlob, TcError> {
bincode::deserialize(bytes).map_err(TcError::UnmarshalStruct)
fn unmarshal_htb_glob(bytes: &[u8]) -> Result<HtbGlob, Error> {
bincode::deserialize(bytes).map_err(|e| Error::Parse(e.to_string()))
}

fn unmarshal_htb_xstats(bytes: &[u8]) -> Result<HtbXstats, TcError> {
bincode::deserialize(bytes).map_err(TcError::UnmarshalStruct)
fn unmarshal_htb_xstats(bytes: &[u8]) -> Result<HtbXstats, Error> {
bincode::deserialize(bytes).map_err(|e| Error::Parse(e.to_string()))
}
51 changes: 3 additions & 48 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,52 +1,7 @@
use std::error;

use bincode::ErrorKind;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum NetlinkError {
#[error("Failed to create socket: {0}")]
Socket(#[from] Box<dyn error::Error>),

#[error("Failed to send message: {0}")]
Send(String),

#[error("Netlink error: {0}")]
Netlink(String),

#[error("Failed to decode netlink message: {0}")]
NetlinkDecode(String),
}

#[derive(Debug, Error)]
pub enum LinkError {
#[error("rust-netlink error: {0}")]
Netlink(#[from] NetlinkError),

#[error("Missing attribute: {0}")]
MissingAttribute(String),
}

#[derive(Debug, Error)]
pub enum TcError {
#[error("rust-netlink error: {0}")]
Netlink(#[from] NetlinkError),

#[error("Failed to retrieve links: {0}")]
Link(#[from] LinkError),

#[error("Failed to decode field: {0}")]
Decode(String),

#[error("Failed to unmarshal struct: {0}")]
UnmarshalStruct(#[from] Box<ErrorKind>),

#[error("Failed to unmarshal structs: {0}")]
UnmarshalStructs(String),

#[error("Inavalid attribute: {0}")]
InvalidAttribute(String),

#[error("Attribute not implemented: {0}")]
UnimplementedAttribute(String),
pub enum Error {
#[error("Failed to parse: {0}")]
Parse(String),
}
Loading

0 comments on commit a965cc6

Please sign in to comment.