Skip to content

Commit

Permalink
[API break] neighbour_table: Reorganize the code base
Browse files Browse the repository at this point in the history
 * This patch is first preparation on stable release 1.0 for submodule
   neighbour_table. The detailed checklist for developer is stated in
   README.md file.
 * Stored constants into where it been used. Will remove `constants.rs`
   once all components finished.
 * Replace `pub use self::*` to explicit expose.
 * Renamed `neighbour_table::Nla` to `NeighbourTableAttribute` and all its entry
   changed to detailed data structure instead of `Vec<u8>`
 * Unit test case included. There is no `ip` command for
   `neighbour_table`. Hence this patch introduce example
   `dump_neighbour_tables.rs`, and use it to generate netlink query to
   kernel. The integers are verified by `sysctl -a|grep neigh`.

Signed-off-by: Gris Ge <[email protected]>
  • Loading branch information
cathay4t committed Nov 29, 2023
1 parent e94293b commit 3f61520
Show file tree
Hide file tree
Showing 17 changed files with 747 additions and 218 deletions.
69 changes: 69 additions & 0 deletions examples/dump_neighbour_tables.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: MIT

use netlink_packet_core::{
NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST,
};
use netlink_packet_route::{
neighbour_table::NeighbourTableMessage, RtnlMessage,
};
use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr};

fn main() {
let mut socket = Socket::new(NETLINK_ROUTE).unwrap();
let _port_number = socket.bind_auto().unwrap().port_number();
socket.connect(&SocketAddr::new(0, 0)).unwrap();

let mut nl_hdr = NetlinkHeader::default();
nl_hdr.flags = NLM_F_DUMP | NLM_F_REQUEST;

let mut req = NetlinkMessage::new(
nl_hdr,
NetlinkPayload::from(RtnlMessage::GetNeighbourTable(
NeighbourTableMessage::default(),
)),
);
// IMPORTANT: call `finalize()` to automatically set the
// `message_type` and `length` fields to the appropriate values in
// the netlink header.
req.finalize();

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

println!(">>> {req:?}");
socket.send(&buf[..], 0).unwrap();

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

'outer: loop {
let size = socket.recv(&mut &mut receive_buffer[..], 0).unwrap();

loop {
let bytes = &receive_buffer[offset..];
// Parse the message
let msg: NetlinkMessage<RtnlMessage> =
NetlinkMessage::deserialize(bytes).unwrap();

match msg.payload {
NetlinkPayload::Done(_) => break 'outer,
NetlinkPayload::InnerMessage(
RtnlMessage::NewNeighbourTable(entry),
) => {
println!("HAHA {:?}", entry);
}
NetlinkPayload::Error(err) => {
eprintln!("Received a netlink error message: {err:?}");
return;
}
_ => {}
}

offset += msg.header.length as usize;
if offset == size || msg.header.length == 0 {
offset = 0;
break;
}
}
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub use self::rtnl::*;
pub mod address;
pub mod link;
pub mod neighbour;
pub mod neighbour_table;
pub mod route;
pub mod rule;
pub mod tc;
Expand Down
136 changes: 136 additions & 0 deletions src/neighbour_table/attribute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: MIT

use anyhow::Context;
use byteorder::{ByteOrder, NativeEndian};
use netlink_packet_utils::{
nla::{DefaultNla, Nla, NlaBuffer},
parsers::{parse_string, parse_u32, parse_u64},
DecodeError, Emitable, Parseable,
};

use super::{
param::VecNeighbourTableParameter, NeighbourTableConfig,
NeighbourTableConfigBuffer, NeighbourTableParameter, NeighbourTableStats,
NeighbourTableStatsBuffer,
};

const NDTA_NAME: u16 = 1;
const NDTA_THRESH1: u16 = 2;
const NDTA_THRESH2: u16 = 3;
const NDTA_THRESH3: u16 = 4;
const NDTA_CONFIG: u16 = 5;
const NDTA_PARMS: u16 = 6;
const NDTA_STATS: u16 = 7;
const NDTA_GC_INTERVAL: u16 = 8;

#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub enum NeighbourTableAttribute {
Parms(Vec<NeighbourTableParameter>),
Name(String),
Threshold1(u32),
Threshold2(u32),
Threshold3(u32),
Config(NeighbourTableConfig),
Stats(NeighbourTableStats),
GcInterval(u64),
Other(DefaultNla),
}

impl Nla for NeighbourTableAttribute {
fn value_len(&self) -> usize {
match self {
Self::Parms(v) => v.as_slice().buffer_len(),
Self::Stats(v) => v.buffer_len(),
Self::Config(v) => v.buffer_len(),
// strings: +1 because we need to append a nul byte
Self::Name(s) => s.len() + 1,
Self::Threshold1(_) | Self::Threshold2(_) | Self::Threshold3(_) => {
4
}
Self::GcInterval(_) => 8,
Self::Other(attr) => attr.value_len(),

Check warning on line 52 in src/neighbour_table/attribute.rs

View check run for this annotation

Codecov / codecov/patch

src/neighbour_table/attribute.rs#L52

Added line #L52 was not covered by tests
}
}

fn emit_value(&self, buffer: &mut [u8]) {
match self {
Self::Parms(v) => v.as_slice().emit(buffer),
Self::Stats(v) => v.emit(buffer),
Self::Config(v) => v.emit(buffer),
Self::Name(string) => {
buffer[..string.len()].copy_from_slice(string.as_bytes());
buffer[string.len()] = 0;
}
Self::GcInterval(value) => NativeEndian::write_u64(buffer, *value),
Self::Threshold1(value)
| Self::Threshold2(value)
| Self::Threshold3(value) => {
NativeEndian::write_u32(buffer, *value)
}
Self::Other(attr) => attr.emit_value(buffer),

Check warning on line 71 in src/neighbour_table/attribute.rs

View check run for this annotation

Codecov / codecov/patch

src/neighbour_table/attribute.rs#L71

Added line #L71 was not covered by tests
}
}

fn kind(&self) -> u16 {
match self {
Self::Name(_) => NDTA_NAME,
Self::Config(_) => NDTA_CONFIG,
Self::Stats(_) => NDTA_STATS,
Self::Parms(_) => NDTA_PARMS,
Self::GcInterval(_) => NDTA_GC_INTERVAL,
Self::Threshold1(_) => NDTA_THRESH1,
Self::Threshold2(_) => NDTA_THRESH2,
Self::Threshold3(_) => NDTA_THRESH3,
Self::Other(attr) => attr.kind(),

Check warning on line 85 in src/neighbour_table/attribute.rs

View check run for this annotation

Codecov / codecov/patch

src/neighbour_table/attribute.rs#L85

Added line #L85 was not covered by tests
}
}
}

impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<NlaBuffer<&'a T>>
for NeighbourTableAttribute
{
fn parse(buf: &NlaBuffer<&'a T>) -> Result<Self, DecodeError> {
let payload = buf.value();
Ok(match buf.kind() {
NDTA_NAME => Self::Name(
parse_string(payload).context("invalid NDTA_NAME value")?,
),
NDTA_CONFIG => Self::Config(
NeighbourTableConfig::parse(
&NeighbourTableConfigBuffer::new_checked(payload)
.context(format!("invalid NDTA_CONFIG {payload:?}"))?,
)
.context(format!("invalid NDTA_CONFIG {payload:?}"))?,
),
NDTA_STATS => Self::Stats(
NeighbourTableStats::parse(
&NeighbourTableStatsBuffer::new_checked(payload)
.context(format!("invalid NDTA_STATS {payload:?}"))?,
)
.context(format!("invalid NDTA_STATS {payload:?}"))?,
),
NDTA_PARMS => Self::Parms(
VecNeighbourTableParameter::parse(&NlaBuffer::new(payload))
.context(format!("invalid NDTA_PARMS {payload:?}"))?
.0,
),
NDTA_GC_INTERVAL => Self::GcInterval(
parse_u64(payload).context("invalid NDTA_GC_INTERVAL value")?,
),
NDTA_THRESH1 => Self::Threshold1(
parse_u32(payload).context("invalid NDTA_THRESH1 value")?,
),
NDTA_THRESH2 => Self::Threshold2(
parse_u32(payload).context("invalid NDTA_THRESH2 value")?,
),
NDTA_THRESH3 => Self::Threshold3(
parse_u32(payload).context("invalid NDTA_THRESH3 value")?,
),
kind => Self::Other(
DefaultNla::parse(buf)
.context(format!("unknown NLA type {kind}"))?,

Check warning on line 132 in src/neighbour_table/attribute.rs

View check run for this annotation

Codecov / codecov/patch

src/neighbour_table/attribute.rs#L130-L132

Added lines #L130 - L132 were not covered by tests
),
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use netlink_packet_utils::{

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[non_exhaustive]
pub struct Config {
pub struct NeighbourTableConfig {
pub key_len: u16,
pub entry_size: u16,
pub entries: u32,
Expand All @@ -21,7 +21,7 @@ pub struct Config {

pub const CONFIG_LEN: usize = 32;

buffer!(ConfigBuffer(CONFIG_LEN) {
buffer!(NeighbourTableConfigBuffer(CONFIG_LEN) {
key_len: (u16, 0..2),
entry_size: (u16, 2..4),
entries: (u32, 4..8),
Expand All @@ -33,8 +33,10 @@ buffer!(ConfigBuffer(CONFIG_LEN) {
proxy_qlen: (u32, 28..32),
});

impl<T: AsRef<[u8]>> Parseable<ConfigBuffer<T>> for Config {
fn parse(buf: &ConfigBuffer<T>) -> Result<Self, DecodeError> {
impl<T: AsRef<[u8]>> Parseable<NeighbourTableConfigBuffer<T>>
for NeighbourTableConfig
{
fn parse(buf: &NeighbourTableConfigBuffer<T>) -> Result<Self, DecodeError> {
Ok(Self {
key_len: buf.key_len(),
entry_size: buf.entry_size(),
Expand All @@ -49,13 +51,13 @@ impl<T: AsRef<[u8]>> Parseable<ConfigBuffer<T>> for Config {
}
}

impl Emitable for Config {
impl Emitable for NeighbourTableConfig {
fn buffer_len(&self) -> usize {
CONFIG_LEN
}

fn emit(&self, buffer: &mut [u8]) {
let mut buffer = ConfigBuffer::new(buffer);
let mut buffer = NeighbourTableConfigBuffer::new(buffer);
buffer.set_key_len(self.key_len);
buffer.set_entry_size(self.entry_size);
buffer.set_entries(self.entries);
Expand Down
53 changes: 53 additions & 0 deletions src/neighbour_table/header.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT

use netlink_packet_utils::{
nla::{NlaBuffer, NlasIterator},
traits::{Emitable, Parseable},
DecodeError,
};

use crate::AddressFamily;

const NEIGHBOUR_TABLE_HEADER_LEN: usize = 4;

buffer!(NeighbourTableMessageBuffer(NEIGHBOUR_TABLE_HEADER_LEN) {
family: (u8, 0),
payload: (slice, NEIGHBOUR_TABLE_HEADER_LEN..),
});

impl<'a, T: AsRef<[u8]> + ?Sized> NeighbourTableMessageBuffer<&'a T> {
pub fn attributes(
&self,
) -> impl Iterator<Item = Result<NlaBuffer<&'a [u8]>, DecodeError>> {
NlasIterator::new(self.payload())
}
}

// kernel code is `struct rtgenmsg`
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub struct NeighbourTableHeader {
pub family: AddressFamily,
}

impl<T: AsRef<[u8]>> Parseable<NeighbourTableMessageBuffer<T>>
for NeighbourTableHeader
{
fn parse(
buf: &NeighbourTableMessageBuffer<T>,
) -> Result<Self, DecodeError> {
Ok(Self {
family: buf.family().into(),
})
}
}

impl Emitable for NeighbourTableHeader {
fn buffer_len(&self) -> usize {
NEIGHBOUR_TABLE_HEADER_LEN
}

fn emit(&self, buffer: &mut [u8]) {
let mut packet = NeighbourTableMessageBuffer::new(buffer);
packet.set_family(self.family.into());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,27 @@ use netlink_packet_utils::{
DecodeError,
};

use crate::{
nlas::neighbour_table::Nla, NeighbourTableHeader,
NeighbourTableMessageBuffer,
use super::{
NeighbourTableAttribute, NeighbourTableHeader, NeighbourTableMessageBuffer,
};

#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone, Default)]
#[non_exhaustive]
pub struct NeighbourTableMessage {
pub header: NeighbourTableHeader,
pub nlas: Vec<Nla>,
pub attributes: Vec<NeighbourTableAttribute>,
}

impl Emitable for NeighbourTableMessage {
fn buffer_len(&self) -> usize {
self.header.buffer_len() + self.nlas.as_slice().buffer_len()
self.header.buffer_len() + self.attributes.as_slice().buffer_len()
}

fn emit(&self, buffer: &mut [u8]) {
self.header.emit(buffer);
self.nlas.as_slice().emit(buffer);
self.attributes
.as_slice()
.emit(&mut buffer[self.header.buffer_len()..]);
}
}

Expand All @@ -38,22 +39,22 @@ impl<'a, T: AsRef<[u8]> + 'a> Parseable<NeighbourTableMessageBuffer<&'a T>>
Ok(NeighbourTableMessage {
header: NeighbourTableHeader::parse(buf)
.context("failed to parse neighbour table message header")?,
nlas: Vec::<Nla>::parse(buf)
attributes: Vec::<NeighbourTableAttribute>::parse(buf)
.context("failed to parse neighbour table message NLAs")?,
})
}
}

impl<'a, T: AsRef<[u8]> + 'a> Parseable<NeighbourTableMessageBuffer<&'a T>>
for Vec<Nla>
for Vec<NeighbourTableAttribute>
{
fn parse(
buf: &NeighbourTableMessageBuffer<&'a T>,
) -> Result<Self, DecodeError> {
let mut nlas = vec![];
for nla_buf in buf.nlas() {
nlas.push(Nla::parse(&nla_buf?)?);
let mut attributes = vec![];
for nla_buf in buf.attributes() {
attributes.push(NeighbourTableAttribute::parse(&nla_buf?)?);
}
Ok(nlas)
Ok(attributes)
}
}
17 changes: 17 additions & 0 deletions src/neighbour_table/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT

mod attribute;
mod config;
mod header;
mod message;
pub(crate) mod param;
mod stats;
#[cfg(test)]
mod tests;

pub use self::attribute::NeighbourTableAttribute;
pub use self::config::{NeighbourTableConfig, NeighbourTableConfigBuffer};
pub use self::header::{NeighbourTableHeader, NeighbourTableMessageBuffer};
pub use self::message::NeighbourTableMessage;
pub use self::param::NeighbourTableParameter;
pub use self::stats::{NeighbourTableStats, NeighbourTableStatsBuffer};
Loading

0 comments on commit 3f61520

Please sign in to comment.