diff --git a/src/lib.rs b/src/lib.rs index 16a808bf..f1d53fa4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ pub mod address; pub mod link; pub mod neighbour; pub mod neighbour_table; +pub mod nexthop; pub mod nsid; pub mod prefix; pub mod route; diff --git a/src/message.rs b/src/message.rs index d1b20809..5aff0c53 100644 --- a/src/message.rs +++ b/src/message.rs @@ -8,6 +8,7 @@ use netlink_packet_utils::{ DecodeError, Emitable, Parseable, ParseableParametrized, }; +use crate::nexthop::{NexthopMessage, NexthopMessageBuffer}; use crate::tc::{TcActionMessage, TcActionMessageBuffer}; use crate::{ address::{AddressHeader, AddressMessage, AddressMessageBuffer}, @@ -76,6 +77,9 @@ const RTM_GETNSID: u16 = 90; const RTM_NEWCHAIN: u16 = 100; const RTM_DELCHAIN: u16 = 101; const RTM_GETCHAIN: u16 = 102; +const RTM_NEWNEXTHOP: u16 = 104; +const RTM_DELNEXTHOP: u16 = 105; +const RTM_GETNEXTHOP: u16 = 106; const RTM_NEWLINKPROP: u16 = 108; const RTM_DELLINKPROP: u16 = 109; @@ -322,6 +326,22 @@ impl<'a, T: AsRef<[u8]> + ?Sized> } } + // Nexthop Messages + RTM_NEWNEXTHOP | RTM_DELNEXTHOP | RTM_GETNEXTHOP => { + 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() @@ -369,6 +389,9 @@ pub enum RouteNetlinkMessage { NewTrafficChain(TcMessage), DelTrafficChain(TcMessage), GetTrafficChain(TcMessage), + NewNexthop(NexthopMessage), + DelNexthop(NexthopMessage), + GetNexthop(NexthopMessage), NewNsId(NsidMessage), DelNsId(NsidMessage), GetNsId(NsidMessage), @@ -526,6 +549,18 @@ impl RouteNetlinkMessage { matches!(self, RouteNetlinkMessage::DelRule(_)) } + pub fn is_get_nexthop(&self) -> bool { + matches!(self, RouteNetlinkMessage::GetNexthop(_)) + } + + pub fn is_new_nexthop(&self) -> bool { + matches!(self, RouteNetlinkMessage::NewNexthop(_)) + } + + pub fn is_del_nexthop(&self) -> bool { + matches!(self, RouteNetlinkMessage::DelNexthop(_)) + } + pub fn message_type(&self) -> u16 { use self::RouteNetlinkMessage::*; @@ -570,6 +605,9 @@ impl RouteNetlinkMessage { GetRule(_) => RTM_GETRULE, NewRule(_) => RTM_NEWRULE, DelRule(_) => RTM_DELRULE, + NewNexthop(_) => RTM_NEWNEXTHOP, + DelNexthop(_) => RTM_DELNEXTHOP, + GetNexthop(_) => RTM_GETNEXTHOP, } } } @@ -637,7 +675,12 @@ impl Emitable for RouteNetlinkMessage { | DelTrafficAction(ref msg) | GetTrafficAction(ref msg) => msg.buffer_len(), - } + + | NewNexthop(ref msg) + | DelNexthop(ref msg) + | GetNexthop(ref msg) + => msg.buffer_len(), +} } #[rustfmt::skip] @@ -702,7 +745,12 @@ impl Emitable for RouteNetlinkMessage { | DelTrafficAction(ref msg) | GetTrafficAction(ref msg) => msg.emit(buffer), - } + + | NewNexthop(ref msg) + | DelNexthop(ref msg) + | GetNexthop(ref msg) + => msg.emit(buffer), +} } } diff --git a/src/nexthop/attribute.rs b/src/nexthop/attribute.rs new file mode 100644 index 00000000..8d8d790f --- /dev/null +++ b/src/nexthop/attribute.rs @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: MIT + +use anyhow::Context; +use byteorder::{ByteOrder, NativeEndian}; +use netlink_packet_utils::{ + nla::{DefaultNla, Nla, NlaBuffer}, + parsers::{parse_u16, parse_u32}, + DecodeError, Emitable, Parseable, ParseableParametrized, +}; + +use crate::{ + route::{RouteAddress, RouteLwTunnelEncap}, + AddressFamily, +}; + +use super::NexthopGroup; + +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; +const NHA_MASTER: u16 = 10; +const NHA_FDB: u16 = 11; +// const NHA_RES_GROUP: u16 = 12; +// const NHA_RES_BUCKET: u16 = 13; + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub enum NexthopAttribute { + Id(u32), + Group(Vec), + GroupType(u16), + Blackhole, + Oif(u32), + Gateway(RouteAddress), + EncapType(u16), + Encap(Vec), + Groups, + Master(u32), + Fdb, + Other(DefaultNla), +} + +impl Nla for NexthopAttribute { + fn value_len(&self) -> usize { + match self { + Self::Id(_) | Self::Oif(_) | Self::Master(_) => 4, + Self::Group(groups) => { + groups.iter().map(|grp| grp.buffer_len()).sum() + } + Self::GroupType(_) | Self::EncapType(_) => 2, + Self::Blackhole | Self::Groups | Self::Fdb => 0, + Self::Encap(v) => v.as_slice().buffer_len(), + Self::Gateway(addr) => addr.buffer_len(), + Self::Other(attr) => attr.value_len(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + Self::Id(value) | Self::Oif(value) | Self::Master(value) => { + NativeEndian::write_u32(buffer, *value) + } + Self::Group(groups) => { + let mut offset = 0; + for grp in groups { + let len = grp.buffer_len(); + grp.emit(&mut buffer[offset..offset + len]); + offset += len + } + } + Self::GroupType(value) | Self::EncapType(value) => { + NativeEndian::write_u16(buffer, *value); + } + Self::Blackhole | Self::Groups | Self::Fdb => {} + Self::Encap(nlas) => nlas.as_slice().emit(buffer), + Self::Gateway(addr) => addr.emit(buffer), + Self::Other(attr) => attr.emit_value(buffer), + } + } + + fn kind(&self) -> u16 { + match self { + Self::Id(_) => NHA_ID, + Self::Group(_) => NHA_GROUP, + Self::GroupType(_) => NHA_GROUP_TYPE, + Self::Blackhole => NHA_BLACKHOLE, + Self::Oif(_) => NHA_OIF, + Self::Gateway(_) => NHA_GATEWAY, + Self::EncapType(_) => NHA_ENCAP_TYPE, + Self::Encap(_) => NHA_ENCAP, + Self::Groups => NHA_GROUPS, + Self::Master(_) => NHA_MASTER, + Self::Fdb => NHA_FDB, + Self::Other(nla) => nla.kind(), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> + ParseableParametrized, AddressFamily> + for NexthopAttribute +{ + fn parse_with_param( + buf: &NlaBuffer<&'a T>, + address_family: AddressFamily, + ) -> Result { + let payload = buf.value(); + Ok(match buf.kind() { + NHA_ID => Self::Id( + parse_u32(payload).context(format!("invalid NHA_ID value"))?, + ), + NHA_GROUP => { + let mut groups = vec![]; + let mut i: usize = 0; + while i + 8 <= payload.len() { + groups.push(NexthopGroup::parse(&payload[i..i + 8])?); + i += 8; + } + Self::Group(groups) + } + NHA_GROUP_TYPE => Self::GroupType( + parse_u16(payload) + .context(format!("invalid NHA_GROUP_TYPE value"))?, + ), + NHA_BLACKHOLE => Self::Blackhole, + NHA_OIF => Self::Oif( + parse_u32(payload).context(format!("invalid NHA_OIF value"))?, + ), + NHA_GATEWAY => { + Self::Gateway(RouteAddress::parse(address_family, payload)?) + } + NHA_ENCAP_TYPE => Self::EncapType( + parse_u16(payload) + .context(format!("invalid NHA_ENCAP_TYPE value"))?, + ), + NHA_GROUPS => Self::Groups, + NHA_MASTER => Self::Master( + parse_u32(payload) + .context(format!("invalid NHA_MASTER value"))?, + ), + NHA_FDB => Self::Fdb, + _ => Self::Other( + DefaultNla::parse(buf) + .context("invalid link NLA value (unknown type)")?, + ), + }) + } +} diff --git a/src/nexthop/flags.rs b/src/nexthop/flags.rs new file mode 100644 index 00000000..79b8c513 --- /dev/null +++ b/src/nexthop/flags.rs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT + +const RTNH_F_DEAD: u32 = 1 << 0; +const RTNH_F_PERVASIVE: u32 = 1 << 1; +const RTNH_F_ONLINK: u32 = 1 << 2; +const RTNH_F_OFFLOAD: u32 = 1 << 3; +const RTNH_F_LINKDOWN: u32 = 1 << 4; +const RTNH_F_UNRESOLVED: u32 = 1 << 5; +const RTNH_F_TRAP: u32 = 1 << 6; +// const RTNH_COMPARE_MASK: u32 = +// RTNH_F_DEAD | RTNH_F_LINKDOWN | RTNH_F_OFFLOAD | RTNH_F_TRAP; + +bitflags! { + #[derive(Clone, Eq, PartialEq, Debug, Copy, Default)] + #[non_exhaustive] + pub struct NexthopFlags: u32 { + const Dead = RTNH_F_DEAD; + const Pervasive = RTNH_F_PERVASIVE; + const Onlink =RTNH_F_ONLINK; + const Offload = RTNH_F_OFFLOAD; + const Linkdown = RTNH_F_LINKDOWN; + const Unresolved = RTNH_F_UNRESOLVED; + const Trap= RTNH_F_TRAP; + const _ = !0; + } +} diff --git a/src/nexthop/group.rs b/src/nexthop/group.rs new file mode 100644 index 00000000..dbad6a16 --- /dev/null +++ b/src/nexthop/group.rs @@ -0,0 +1,43 @@ +use byteorder::{ByteOrder, NativeEndian}; +use netlink_packet_utils::{ + parsers::{parse_u16, parse_u32}, + DecodeError, Emitable, +}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +/// Nexthop Group +pub struct NexthopGroup { + /// Nexthop id + pub id: u32, + /// Weight of this nexthop + pub weight: u8, + /// Reserved + pub resvd1: u8, + /// Reserved + pub resvd2: u16, +} + +impl Emitable for NexthopGroup { + fn buffer_len(&self) -> usize { + 8 + } + + fn emit(&self, buffer: &mut [u8]) { + NativeEndian::write_u32(buffer, self.id); + buffer[4] = self.weight; + buffer[5] = self.resvd1; + NativeEndian::write_u16(&mut buffer[6..8], self.resvd2); + } +} + +impl NexthopGroup { + pub fn parse(payload: &[u8]) -> Result { + let grp = Self { + id: parse_u32(payload)?, + weight: payload[4], + resvd1: payload[5], + resvd2: parse_u16(&payload[6..8])?, + }; + Ok(grp) + } +} diff --git a/src/nexthop/header.rs b/src/nexthop/header.rs new file mode 100644 index 00000000..0036aafb --- /dev/null +++ b/src/nexthop/header.rs @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_utils::{ + nla::{NlaBuffer, NlasIterator}, + traits::{Emitable, Parseable}, + DecodeError, +}; + +use super::NexthopFlags; +use crate::{ + route::{RouteProtocol, RouteScope}, + AddressFamily, +}; + +const NEXTHOP_HEADER_LEN: usize = 8; + +buffer!(NexthopMessageBuffer(NEXTHOP_HEADER_LEN) { + address_family: (u8, 0), + scope: (u8, 1), + protocol: (u8, 2), + resvd: (u8, 3), + flags: (u32, 4..NEXTHOP_HEADER_LEN), + payload: (slice, NEXTHOP_HEADER_LEN..), +}); + +impl<'a, T: AsRef<[u8]> + ?Sized> NexthopMessageBuffer<&'a T> { + pub fn attributes( + &self, + ) -> impl Iterator, DecodeError>> { + NlasIterator::new(self.payload()) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Default)] +pub struct NexthopHeader { + pub address_family: AddressFamily, + // Nexthop scope. + pub scope: RouteScope, + // Protocol. + pub protocol: RouteProtocol, + // Reserved + pub resvd: u8, + // Nexthop flags. + pub flags: NexthopFlags, +} + +impl> Parseable> for NexthopHeader { + fn parse(buf: &NexthopMessageBuffer) -> Result { + Ok(Self { + address_family: buf.address_family().into(), + protocol: buf.protocol().into(), + scope: buf.scope().into(), + resvd: buf.resvd().into(), + flags: NexthopFlags::from_bits_retain(buf.flags()), + }) + } +} + +impl Emitable for NexthopHeader { + fn buffer_len(&self) -> usize { + NEXTHOP_HEADER_LEN + } + + fn emit(&self, buffer: &mut [u8]) { + let mut packet = NexthopMessageBuffer::new(buffer); + packet.set_address_family(self.address_family.into()); + packet.set_scope(self.scope.into()); + packet.set_protocol(self.protocol.into()); + packet.set_resvd(self.resvd.into()); + packet.set_flags(self.flags.bits()); + } +} diff --git a/src/nexthop/message.rs b/src/nexthop/message.rs new file mode 100644 index 00000000..fbe5370d --- /dev/null +++ b/src/nexthop/message.rs @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT + +use anyhow::Context; +use netlink_packet_utils::{ + traits::{Emitable, Parseable, ParseableParametrized}, + DecodeError, +}; + +use super::{ + super::AddressFamily, NexthopAttribute, NexthopHeader, NexthopMessageBuffer, +}; + +#[derive(Debug, PartialEq, Eq, Clone, Default)] +#[non_exhaustive] +pub struct NexthopMessage { + pub header: NexthopHeader, + pub attributes: Vec, +} + +impl Emitable for NexthopMessage { + fn buffer_len(&self) -> usize { + self.header.buffer_len() + self.attributes.as_slice().buffer_len() + } + + fn emit(&self, buffer: &mut [u8]) { + self.header.emit(buffer); + self.attributes + .as_slice() + .emit(&mut buffer[self.header.buffer_len()..]); + } +} + +impl<'a, T: AsRef<[u8]> + 'a> Parseable> + for NexthopMessage +{ + fn parse(buf: &NexthopMessageBuffer<&'a T>) -> Result { + let header = NexthopHeader::parse(buf) + .context("failed to parse nexthop message header")?; + let address_family = header.address_family; + Ok(NexthopMessage { + header, + attributes: Vec::::parse_with_param( + buf, + address_family, + ) + .context("failed to parse nexthop message NLAs")?, + }) + } +} + +impl<'a, T: AsRef<[u8]> + 'a> + ParseableParametrized, AddressFamily> + for Vec +{ + fn parse_with_param( + buf: &NexthopMessageBuffer<&'a T>, + address_family: AddressFamily, + ) -> Result { + let mut attributes = vec![]; + for nla_buf in buf.attributes() { + attributes.push(NexthopAttribute::parse_with_param( + &nla_buf?, + address_family, + )?); + } + Ok(attributes) + } +} diff --git a/src/nexthop/mod.rs b/src/nexthop/mod.rs new file mode 100644 index 00000000..e33fd0dc --- /dev/null +++ b/src/nexthop/mod.rs @@ -0,0 +1,11 @@ +mod attribute; +mod flags; +mod group; +mod header; +mod message; + +pub use self::attribute::NexthopAttribute; +pub use self::flags::NexthopFlags; +pub use self::group::NexthopGroup; +pub use self::header::{NexthopHeader, NexthopMessageBuffer}; +pub use self::message::NexthopMessage; diff --git a/src/route/attribute.rs b/src/route/attribute.rs index 344fc745..3d42eee2 100644 --- a/src/route/attribute.rs +++ b/src/route/attribute.rs @@ -43,11 +43,11 @@ const RTA_ENCAP: u16 = 22; const RTA_EXPIRES: u16 = 23; const RTA_UID: u16 = 25; const RTA_TTL_PROPAGATE: u16 = 26; +const RTA_NH_ID: u16 = 30; // TODO // const RTA_IP_PROTO:u16 = 27; // const RTA_SPORT:u16 = 28; // const RTA_DPORT:u16 = 29; -// const RTA_NH_ID:u16 = 30; /// Netlink attributes for `RTM_NEWROUTE`, `RTM_DELROUTE`, /// `RTM_GETROUTE` netlink messages. @@ -84,6 +84,7 @@ pub enum RouteAttribute { Realm(RouteRealm), Table(u32), Mark(u32), + Nhid(u32), Other(DefaultNla), } @@ -113,7 +114,8 @@ impl Nla for RouteAttribute { | Self::Oif(_) | Self::Priority(_) | Self::Table(_) - | Self::Mark(_) => 4, + | Self::Mark(_) + | Self::Nhid(_) => 4, Self::MulticastExpires(_) => 8, Self::Other(attr) => attr.value_len(), } @@ -149,7 +151,8 @@ impl Nla for RouteAttribute { | Self::Oif(value) | Self::Priority(value) | Self::Table(value) - | Self::Mark(value) => NativeEndian::write_u32(buffer, *value), + | Self::Mark(value) + | Self::Nhid(value) => NativeEndian::write_u32(buffer, *value), Self::Realm(v) => v.emit(buffer), Self::MulticastExpires(value) => { NativeEndian::write_u64(buffer, *value) @@ -183,6 +186,7 @@ impl Nla for RouteAttribute { Self::MulticastExpires(_) => RTA_EXPIRES, Self::Uid(_) => RTA_UID, Self::TtlPropagate(_) => RTA_TTL_PROPAGATE, + Self::Nhid(_) => RTA_NH_ID, Self::Other(ref attr) => attr.kind(), } }