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!(),
Copy link
Member

Choose a reason for hiding this comment

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

It is not unreachable. In the future, kernel might add new type or hacker can craft invalid netlink packet to crash the whole project.

Please check other struct on how to handle this properly.

}
}

_ => {
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),
Copy link
Member

Choose a reason for hiding this comment

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

enum instead of plain integer

Blackhole,
Oif(u32),
Gateway(Vec<u8>), // Can be IPv4 or IPv6
Copy link
Member

Choose a reason for hiding this comment

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

Gateway(IpAddr)

EncapType(u16),
Copy link
Member

Choose a reason for hiding this comment

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

Enum instead of plain integer.

Encap(Vec<u8>), // TODO: Parse encap attributes properly
Copy link
Member

Choose a reason for hiding this comment

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

Finish the TODO or remove it.

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::*;
Copy link
Member

Choose a reason for hiding this comment

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

Please do not use use xxx::* anymore.

match self {
Id(_) => 4,
Group(entries) => entries.len() * 8, // Each entry is 8 bytes
Copy link
Member

Choose a reason for hiding this comment

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

No magic number please. Use entries.len() * NexthopGroupEntry::LENGTH

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::*;
Copy link
Member

Choose a reason for hiding this comment

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

Please do not use use xxx::* anymore.

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;
Copy link
Member

Choose a reason for hiding this comment

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

Do not expose constant, use enum.

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 {
Copy link
Member

Choose a reason for hiding this comment

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

#[non_exhaustive]

pub id: u32,
pub weight: u8,
pub resvd1: u8,
Copy link
Member

Choose a reason for hiding this comment

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

If reserved, then don't expose as public. When kernel change its names, we have to break our API.

Marking this struct as non_exhaustive and hide this property as internal property.

Use pub fn new() allowing create this struct use id and weight.

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 {
Copy link
Member

Choose a reason for hiding this comment

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

buf.len() < NexthopGroupEntry::LENGTH

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())
Copy link
Member

Choose a reason for hiding this comment

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

from_bits_retain(). We should not discard data at this level.

}
}

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