Skip to content
Open
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
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mod address_family;
pub mod link;
pub mod neighbour;
pub mod neighbour_table;
pub mod nexthop;
pub mod nsid;
pub mod prefix;
pub mod route;
Expand Down
48 changes: 48 additions & 0 deletions src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
link::{LinkMessage, LinkMessageBuffer},
neighbour::{NeighbourMessage, NeighbourMessageBuffer},
neighbour_table::{NeighbourTableMessage, NeighbourTableMessageBuffer},
nexthop::{NexthopMessage, NexthopMessageBuffer},
nsid::{NsidMessage, NsidMessageBuffer},
prefix::{PrefixMessage, PrefixMessageBuffer},
route::{RouteHeader, RouteMessage, RouteMessageBuffer},
Expand Down Expand Up @@ -74,6 +75,9 @@ const RTM_DELCHAIN: u16 = 101;
const RTM_GETCHAIN: u16 = 102;
const RTM_NEWLINKPROP: u16 = 108;
const RTM_DELLINKPROP: u16 = 109;
const RTM_NEWNEXTHOP: u16 = 104;
const RTM_DELNEXTHOP: u16 = 105;
const RTM_GETNEXTHOP: u16 = 106;

buffer!(RouteNetlinkMessageBuffer);

Expand Down Expand Up @@ -318,6 +322,22 @@ impl<'a, T: AsRef<[u8]> + ?Sized>
}
}

// Nexthop Messages
RTM_NEWNEXTHOP | RTM_GETNEXTHOP | RTM_DELNEXTHOP => {
let err = "invalid nexthop message";
let msg = NexthopMessage::parse(
&NexthopMessageBuffer::new_checked(&buf.inner())
.context(err)?,
)
.context(err)?;
match message_type {
RTM_NEWNEXTHOP => RouteNetlinkMessage::NewNexthop(msg),
RTM_DELNEXTHOP => RouteNetlinkMessage::DelNexthop(msg),
RTM_GETNEXTHOP => RouteNetlinkMessage::GetNexthop(msg),
_ => unreachable!(),
}
}

_ => {
return Err(
format!("Unknown message type: {message_type}").into()
Expand Down Expand Up @@ -371,6 +391,9 @@ pub enum RouteNetlinkMessage {
NewRule(RuleMessage),
DelRule(RuleMessage),
GetRule(RuleMessage),
NewNexthop(NexthopMessage),
DelNexthop(NexthopMessage),
GetNexthop(NexthopMessage),
}

impl RouteNetlinkMessage {
Expand Down Expand Up @@ -522,6 +545,18 @@ impl RouteNetlinkMessage {
matches!(self, RouteNetlinkMessage::DelRule(_))
}

pub fn is_new_nexthop(&self) -> bool {
matches!(self, RouteNetlinkMessage::NewNexthop(_))
}

pub fn is_del_nexthop(&self) -> bool {
matches!(self, RouteNetlinkMessage::DelNexthop(_))
}

pub fn is_get_nexthop(&self) -> bool {
matches!(self, RouteNetlinkMessage::GetNexthop(_))
}

pub fn message_type(&self) -> u16 {
use self::RouteNetlinkMessage::*;

Expand Down Expand Up @@ -566,6 +601,9 @@ impl RouteNetlinkMessage {
GetRule(_) => RTM_GETRULE,
NewRule(_) => RTM_NEWRULE,
DelRule(_) => RTM_DELRULE,
NewNexthop(_) => RTM_NEWNEXTHOP,
DelNexthop(_) => RTM_DELNEXTHOP,
GetNexthop(_) => RTM_GETNEXTHOP,
}
}
}
Expand Down Expand Up @@ -629,6 +667,11 @@ impl Emitable for RouteNetlinkMessage {
| GetRule(ref msg)
=> msg.buffer_len(),

| NewNexthop(ref msg)
| DelNexthop(ref msg)
| GetNexthop(ref msg)
=> msg.buffer_len(),

| NewTrafficAction(ref msg)
| DelTrafficAction(ref msg)
| GetTrafficAction(ref msg)
Expand Down Expand Up @@ -694,6 +737,11 @@ impl Emitable for RouteNetlinkMessage {
| GetRule(ref msg)
=> msg.emit(buffer),

| NewNexthop(ref msg)
| DelNexthop(ref msg)
| GetNexthop(ref msg)
=> msg.emit(buffer),

| NewTrafficAction(ref msg)
| DelTrafficAction(ref msg)
| GetTrafficAction(ref msg)
Expand Down
230 changes: 230 additions & 0 deletions src/nexthop/attribute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
// SPDX-License-Identifier: MIT

use netlink_packet_core::{
DecodeError, Emitable, Nla, Parseable, ParseableParametrized,
};

#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub enum NexthopAttribute {
Id(u32),
Group(Vec<NexthopGroupEntry>),
GroupType(u16),
Blackhole,
Oif(u32),
Gateway(Vec<u8>), // Can be IPv4 or IPv6
EncapType(u16),
Encap(Vec<u8>), // TODO: Parse encap attributes properly
Fdb(Vec<u8>), // TODO: Parse FDB
ResGroup(Vec<u8>), // TODO: Parse ResGroup
Other(u16, Vec<u8>),
}

impl Nla for NexthopAttribute {
fn value_len(&self) -> usize {
use self::NexthopAttribute::*;
match self {
Id(_) => 4,
Group(entries) => entries.len() * 8, // Each entry is 8 bytes
GroupType(_) => 2,
Blackhole => 0,
Oif(_) => 4,
Gateway(bytes) => bytes.len(),
EncapType(_) => 2,
Encap(bytes) => bytes.len(),
Fdb(bytes) => bytes.len(),
ResGroup(bytes) => bytes.len(),
Other(_, bytes) => bytes.len(),
}
}

#[rustfmt::skip]
fn emit_value(&self, buffer: &mut [u8]) {
use self::NexthopAttribute::*;
match self {
| Id(value)
| Oif(value)
=> buffer[0..4].copy_from_slice(&value.to_ne_bytes()),

| GroupType(value)
| EncapType(value)
=> buffer[0..2].copy_from_slice(&value.to_ne_bytes()),

Group(entries) => {
for (i, entry) in entries.iter().enumerate() {
entry.emit(&mut buffer[i * 8..]);
}
}
Blackhole => {},
Gateway(bytes)
| Encap(bytes)
| Fdb(bytes)
| ResGroup(bytes)
| Other(_, bytes)
=> buffer.copy_from_slice(bytes),
}
}

fn kind(&self) -> u16 {
use self::NexthopAttribute::*;
match self {
Id(_) => NHA_ID,
Group(_) => NHA_GROUP,
GroupType(_) => NHA_GROUP_TYPE,
Blackhole => NHA_BLACKHOLE,
Oif(_) => NHA_OIF,
Gateway(_) => NHA_GATEWAY,
EncapType(_) => NHA_ENCAP_TYPE,
Encap(_) => NHA_ENCAP,
Fdb(_) => NHA_FDB,
ResGroup(_) => NHA_RES_GROUP,
Other(kind, _) => *kind,
}
}
}

pub struct NexthopAttributeType;

impl NexthopAttributeType {
pub const ID: u16 = NHA_ID;
pub const GROUP: u16 = NHA_GROUP;
pub const GROUP_TYPE: u16 = NHA_GROUP_TYPE;
pub const BLACKHOLE: u16 = NHA_BLACKHOLE;
pub const OIF: u16 = NHA_OIF;
pub const GATEWAY: u16 = NHA_GATEWAY;
pub const ENCAP_TYPE: u16 = NHA_ENCAP_TYPE;
pub const ENCAP: u16 = NHA_ENCAP;
pub const FDB: u16 = NHA_FDB;
pub const RES_GROUP: u16 = NHA_RES_GROUP;
}

impl<'a, T: AsRef<[u8]> + ?Sized> ParseableParametrized<(&'a T, u16), ()>
for NexthopAttribute
{
fn parse_with_param(
input: &(&'a T, u16),
_params: (),
) -> Result<Self, DecodeError> {
let (payload, kind) = input;
let payload = payload.as_ref();

Ok(match *kind {
NHA_ID => {
if payload.len() != 4 {
return Err(DecodeError::from("Invalid NHA_ID length"));
}
NexthopAttribute::Id(u32::from_ne_bytes(
payload.try_into().map_err(|_| {
DecodeError::from("Invalid NHA_ID length")
})?,
))
Comment on lines +113 to +120

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The parsing logic for fixed-size attributes like NHA_ID can be simplified. Instead of an explicit length check followed by try_into(), you can rely on try_into() alone to handle the length validation. This makes the code more concise and idiomatic Rust. This same feedback applies to the parsing of NHA_GROUP_TYPE, NHA_OIF, and NHA_ENCAP_TYPE in this file.

                NexthopAttribute::Id(u32::from_ne_bytes(
                    payload.try_into().map_err(|_| {
                        DecodeError::from("Invalid NHA_ID length")
                    })?,
                ))

}
NHA_GROUP => {
if payload.len() % 8 != 0 {
return Err(DecodeError::from("Invalid NHA_GROUP length"));
}
let mut entries = Vec::new();
for chunk in payload.chunks(8) {
if let Ok(entry) = NexthopGroupEntry::parse(&chunk) {
entries.push(entry);
} else {
return Err(DecodeError::from(
"Failed to parse group entry",
));
}
}
NexthopAttribute::Group(entries)
Comment on lines +123 to +136

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation for parsing NHA_GROUP contains an unreachable else block. The length check payload.len() % 8 != 0 ensures that payload.chunks(8) only yields 8-byte slices, for which NexthopGroupEntry::parse will not fail. This can be refactored to be more concise and idiomatic using chunks_exact and iterator collect, which also makes the code's intent clearer and removes the dead code.

                if payload.len() % 8 != 0 {
                    return Err(DecodeError::from("Invalid NHA_GROUP length"));
                }
                NexthopAttribute::Group(
                    payload
                        .chunks_exact(8)
                        .map(NexthopGroupEntry::parse)
                        .collect::<Result<Vec<_, _>>>()
                        .map_err(|_|
                            // This should be unreachable given the length check above.
                            DecodeError::from("Failed to parse group entry"))?,
                )

}
NHA_GROUP_TYPE => {
if payload.len() != 2 {
return Err(DecodeError::from(
"Invalid NHA_GROUP_TYPE length",
));
}
NexthopAttribute::GroupType(u16::from_ne_bytes(
payload.try_into().map_err(|_| {
DecodeError::from("Invalid NHA_GROUP_TYPE length")
})?,
))
Comment on lines +139 to +148

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The parsing logic for NHA_GROUP_TYPE can be simplified. By relying on try_into() to validate the payload length, the code becomes more concise and idiomatic.

                NexthopAttribute::GroupType(u16::from_ne_bytes(
                    payload.try_into().map_err(|_| {
                        DecodeError::from("Invalid NHA_GROUP_TYPE length")
                    })?,
                ))

}
NHA_BLACKHOLE => NexthopAttribute::Blackhole,
NHA_OIF => {
if payload.len() != 4 {
return Err(DecodeError::from("Invalid NHA_OIF length"));
}
NexthopAttribute::Oif(u32::from_ne_bytes(
payload.try_into().map_err(|_| {
DecodeError::from("Invalid NHA_OIF length")
})?,
))
Comment on lines +152 to +159

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The parsing logic for NHA_OIF can be simplified. By relying on try_into() to validate the payload length, the code becomes more concise and idiomatic.

                NexthopAttribute::Oif(u32::from_ne_bytes(
                    payload.try_into().map_err(|_| {
                        DecodeError::from("Invalid NHA_OIF length")
                    })?,
                ))

}
NHA_GATEWAY => NexthopAttribute::Gateway(payload.to_vec()),
NHA_ENCAP_TYPE => {
if payload.len() != 2 {
return Err(DecodeError::from(
"Invalid NHA_ENCAP_TYPE length",
));
}
NexthopAttribute::EncapType(u16::from_ne_bytes(
payload.try_into().map_err(|_| {
DecodeError::from("Invalid NHA_ENCAP_TYPE length")
})?,
))
Comment on lines +163 to +172

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The parsing logic for NHA_ENCAP_TYPE can be simplified. By relying on try_into() to validate the payload length, the code becomes more concise and idiomatic.

                NexthopAttribute::EncapType(u16::from_ne_bytes(
                    payload.try_into().map_err(|_| {
                        DecodeError::from("Invalid NHA_ENCAP_TYPE length")
                    })?,
                ))

}
NHA_ENCAP => NexthopAttribute::Encap(payload.to_vec()),
NHA_FDB => NexthopAttribute::Fdb(payload.to_vec()),
NHA_RES_GROUP => NexthopAttribute::ResGroup(payload.to_vec()),
_ => NexthopAttribute::Other(*kind, payload.to_vec()),
})
}
}

// Constants
const NHA_ID: u16 = 1;
const NHA_GROUP: u16 = 2;
const NHA_GROUP_TYPE: u16 = 3;
const NHA_BLACKHOLE: u16 = 4;
const NHA_OIF: u16 = 5;
const NHA_GATEWAY: u16 = 6;
const NHA_ENCAP_TYPE: u16 = 7;
const NHA_ENCAP: u16 = 8;
// const NHA_GROUPS: u16 = 9; // Not implementing NHA_GROUPS as it seems deprecated or complex
// const NHA_MASTER: u16 = 10;
const NHA_FDB: u16 = 11;
const NHA_RES_GROUP: u16 = 12;

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct NexthopGroupEntry {
pub id: u32,
pub weight: u8,
pub resvd1: u8,
pub resvd2: u16,
}

impl Emitable for NexthopGroupEntry {
fn buffer_len(&self) -> usize {
8
}

fn emit(&self, buffer: &mut [u8]) {
buffer[0..4].copy_from_slice(&self.id.to_ne_bytes());
buffer[4] = self.weight;
buffer[5] = self.resvd1;
buffer[6..8].copy_from_slice(&self.resvd2.to_ne_bytes());
}
}

impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<T> for NexthopGroupEntry {
fn parse(buf: &T) -> Result<Self, DecodeError> {
let buf = buf.as_ref();
if buf.len() < 8 {
return Err(DecodeError::from("Invalid NexthopGroupEntry length"));
}
Ok(NexthopGroupEntry {
id: u32::from_ne_bytes(buf[0..4].try_into().unwrap()),
weight: buf[4],
resvd1: buf[5],
resvd2: u16::from_ne_bytes(buf[6..8].try_into().unwrap()),
})
}
}
34 changes: 34 additions & 0 deletions src/nexthop/buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use netlink_packet_core::{NlaBuffer, NlasIterator};

use super::NexthopFlags;

buffer!(NexthopMessageBuffer(8) {
family: (u8, 0),
scope: (u8, 1),
protocol: (u8, 2),
resvd: (u8, 3),
flags_raw: (u32, 4..8),
payload: (slice, 8..),
});

impl<T: AsRef<[u8]>> NexthopMessageBuffer<T> {
pub fn flags(&self) -> NexthopFlags {
NexthopFlags::from_bits_truncate(self.flags_raw())
}
}

impl<T: AsRef<[u8]> + AsMut<[u8]>> NexthopMessageBuffer<T> {
pub fn set_flags(&mut self, flags: NexthopFlags) {
self.set_flags_raw(flags.bits());
}
}

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