From 47ea04c5dde1ad1344deafbaae4aa39b1762b908 Mon Sep 17 00:00:00 2001 From: Renato Westphal Date: Sat, 10 May 2025 17:25:19 -0300 Subject: [PATCH 1/3] route: use netlink-packet-route 0.24.0 Signed-off-by: Renato Westphal --- Cargo.toml | 2 +- src/link/bond.rs | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c49675e..aaa008c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ log = "0.4.8" thiserror = "1" netlink-sys = { version = "0.8" } netlink-packet-utils = { version = "0.5" } -netlink-packet-route = { version = "0.22" } +netlink-packet-route = { version = "0.24" } netlink-packet-core = { version = "0.7" } netlink-proto = { default-features = false, version = "0.11" } nix = { version = "0.29.0", default-features = false, features = ["fs", "mount", "sched", "signal"] } diff --git a/src/link/bond.rs b/src/link/bond.rs index 1c08d80..780f900 100644 --- a/src/link/bond.rs +++ b/src/link/bond.rs @@ -5,7 +5,8 @@ use std::net::{Ipv4Addr, Ipv6Addr}; use crate::{ link::LinkMessageBuilder, packet_route::link::{ - BondArpValidate, BondMode, InfoBond, InfoData, InfoKind, + BondArpAllTargets, BondArpValidate, BondFailOverMac, BondMode, + BondPrimaryReselect, BondXmitHashPolicy, InfoBond, InfoData, InfoKind, }, }; @@ -125,7 +126,7 @@ impl LinkMessageBuilder { /// Adds the `arp_all_targets` attribute to the bond /// This is equivalent to `ip link add name NAME type bond arp_all_targets /// ARP_ALL_TARGETS` - pub fn arp_all_targets(self, arp_all_targets: u32) -> Self { + pub fn arp_all_targets(self, arp_all_targets: BondArpAllTargets) -> Self { self.append_info_data(InfoBond::ArpAllTargets(arp_all_targets)) } @@ -140,21 +141,27 @@ impl LinkMessageBuilder { /// Adds the `primary_reselect` attribute to the bond /// This is equivalent to `ip link add name NAME type bond primary_reselect /// PRIMARY_RESELECT`. - pub fn primary_reselect(self, primary_reselect: u8) -> Self { + pub fn primary_reselect( + self, + primary_reselect: BondPrimaryReselect, + ) -> Self { self.append_info_data(InfoBond::PrimaryReselect(primary_reselect)) } /// Adds the `fail_over_mac` attribute to the bond /// This is equivalent to `ip link add name NAME type bond fail_over_mac /// FAIL_OVER_MAC`. - pub fn fail_over_mac(self, fail_over_mac: u8) -> Self { + pub fn fail_over_mac(self, fail_over_mac: BondFailOverMac) -> Self { self.append_info_data(InfoBond::FailOverMac(fail_over_mac)) } /// Adds the `xmit_hash_policy` attribute to the bond /// This is equivalent to `ip link add name NAME type bond xmit_hash_policy /// XMIT_HASH_POLICY`. - pub fn xmit_hash_policy(self, xmit_hash_policy: u8) -> Self { + pub fn xmit_hash_policy( + self, + xmit_hash_policy: BondXmitHashPolicy, + ) -> Self { self.append_info_data(InfoBond::XmitHashPolicy(xmit_hash_policy)) } From 23fddd5f7cdce70751f108f021151b96c65929fd Mon Sep 17 00:00:00 2001 From: Renato Westphal Date: Sat, 10 May 2025 18:13:33 -0300 Subject: [PATCH 2/3] route: add support for MPLS routes and nexthop label stacks Extend `RouteMessageBuilder` to support the creation of MPLS routes and nexthops with MPLS label stacks. This enables configuration of IP-to-MPLS, MPLS-to-MPLS, and MPLS-to-IP routes. Signed-off-by: Renato Westphal --- examples/add_route_mpls.rs | 89 ++++++++++++++++++++++++++++++++++++++ src/route/builder.rs | 67 +++++++++++++++++++++++++++- 2 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 examples/add_route_mpls.rs diff --git a/examples/add_route_mpls.rs b/examples/add_route_mpls.rs new file mode 100644 index 0000000..7136f0b --- /dev/null +++ b/examples/add_route_mpls.rs @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MIT + +use std::env; + +use ipnetwork::IpNetwork; +use netlink_packet_route::route::MplsLabel; +use rtnetlink::{new_connection, Error, Handle, RouteMessageBuilder}; + +#[tokio::main] +async fn main() -> Result<(), ()> { + let args: Vec = env::args().collect(); + if args.len() != 4 { + usage(); + return Ok(()); + } + + let input_label = args[1] + .parse::() + .map(|label| MplsLabel { + label, + traffic_class: 0, + bottom_of_stack: true, + ttl: 0, + }) + .unwrap_or_else(|_| { + eprintln!("invalid MPLS input label"); + std::process::exit(1); + }); + + let gateway: IpNetwork = args[2].parse().unwrap_or_else(|_| { + eprintln!("invalid gateway"); + std::process::exit(1); + }); + + let output_label = args[3] + .parse::() + .map(|label| MplsLabel { + label, + traffic_class: 0, + bottom_of_stack: true, + ttl: 0, + }) + .unwrap_or_else(|_| { + eprintln!("invalid MPLS output label"); + std::process::exit(1); + }); + + let (connection, handle, _) = new_connection().unwrap(); + tokio::spawn(connection); + + if let Err(e) = + add_route_mpls(input_label, &gateway, output_label, handle.clone()) + .await + { + eprintln!("{e}"); + } else { + println!("Route has been added"); + } + Ok(()) +} + +async fn add_route_mpls( + input_label: MplsLabel, + gateway: &IpNetwork, + output_label: MplsLabel, + handle: Handle, +) -> Result<(), Error> { + let route = RouteMessageBuilder::::new() + .label(input_label) + .via(gateway.ip().into()) + .output_mpls(vec![output_label]) + .build(); + handle.route().add(route).execute().await?; + Ok(()) +} + +fn usage() { + eprintln!( + "\ +usage: + cargo run --example add_route_mpls -- + +Note that you need to run this program as root: + + env CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER='sudo -E' \\ + cargo run --example add_route_mpls -- \ + " + ); +} diff --git a/src/route/builder.rs b/src/route/builder.rs index a67be72..b844824 100644 --- a/src/route/builder.rs +++ b/src/route/builder.rs @@ -7,7 +7,8 @@ use std::{ use netlink_packet_route::{ route::{ - RouteAddress, RouteAttribute, RouteFlags, RouteHeader, RouteMessage, + MplsLabel, RouteAddress, RouteAttribute, RouteFlags, RouteHeader, + RouteLwEnCapType, RouteLwTunnelEncap, RouteMessage, RouteMplsIpTunnel, RouteProtocol, RouteScope, RouteType, }, AddressFamily, @@ -52,6 +53,29 @@ impl RouteMessageBuilder { self } + /// Sets the output MPLS encapsulation labels. + pub fn output_mpls(mut self, labels: Vec) -> Self { + if labels.is_empty() { + return self; + } + if self.message.header.address_family == AddressFamily::Mpls { + self.message + .attributes + .push(RouteAttribute::NewDestination(labels)); + } else { + self.message + .attributes + .push(RouteAttribute::EncapType(RouteLwEnCapType::Mpls)); + let encap = RouteLwTunnelEncap::Mpls( + RouteMplsIpTunnel::Destination(labels), + ); + self.message + .attributes + .push(RouteAttribute::Encap(vec![encap])); + } + self + } + /// Sets the route priority (metric) pub fn priority(mut self, priority: u32) -> Self { self.message @@ -402,3 +426,44 @@ impl Default for RouteMessageBuilder { Self::new() } } + +impl RouteMessageBuilder { + /// Create default RouteMessage with header set to: + /// * route: [RouteHeader::RT_TABLE_MAIN] + /// * protocol: [RouteProtocol::Static] + /// * scope: [RouteScope::Universe] + /// * kind: [RouteType::Unicast] + /// * address_family: [AddressFamily::Mpls] + /// + /// For using this message in querying routes, these settings + /// are ignored unless `NETLINK_GET_STRICT_CHK` been enabled. + pub fn new() -> Self { + let mut builder = Self::new_no_address_family(); + builder.get_mut().header.address_family = AddressFamily::Mpls; + builder + } + + /// Sets the destination MPLS label. + pub fn label(mut self, label: MplsLabel) -> Self { + self.message.header.address_family = AddressFamily::Mpls; + self.message.header.destination_prefix_length = 20; + self.message + .attributes + .push(RouteAttribute::Destination(RouteAddress::Mpls(label))); + self + } + + /// Sets the gateway (via) address. + pub fn via(mut self, addr: IpAddr) -> Self { + self.message + .attributes + .push(RouteAttribute::Via(addr.into())); + self + } +} + +impl Default for RouteMessageBuilder { + fn default() -> Self { + Self::new() + } +} From 8e0e9ce45776ae2ae05fa63be965d4b54d1a415e Mon Sep 17 00:00:00 2001 From: Renato Westphal Date: Sat, 10 May 2025 20:21:48 -0300 Subject: [PATCH 3/3] route: add support for multipath routes Add support for configuring multipath (ECMP) routes. This includes a new `RouteNextHopBuilder` for creating nexthop entries with various attributes. `RouteMessageBuilder` now has a `multipath()` method to attach multiple nexthops using the `RTA_MULTIPATH` attribute. Signed-off-by: Renato Westphal --- src/lib.rs | 2 +- src/route/builder.rs | 84 +++++++++++++++++++++++++++++++++++++++++++- src/route/mod.rs | 1 + 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5d830dc..c195862 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,7 @@ pub use crate::neighbour::{ pub use crate::ns::{NetworkNamespace, NETNS_PATH, NONE_FS, SELF_NS_PATH}; pub use crate::route::{ IpVersion, RouteAddRequest, RouteDelRequest, RouteGetRequest, RouteHandle, - RouteMessageBuilder, + RouteMessageBuilder, RouteNextHopBuilder, }; pub use crate::rule::{ RuleAddRequest, RuleDelRequest, RuleGetRequest, RuleHandle, diff --git a/src/route/builder.rs b/src/route/builder.rs index b844824..42a5301 100644 --- a/src/route/builder.rs +++ b/src/route/builder.rs @@ -9,7 +9,8 @@ use netlink_packet_route::{ route::{ MplsLabel, RouteAddress, RouteAttribute, RouteFlags, RouteHeader, RouteLwEnCapType, RouteLwTunnelEncap, RouteMessage, RouteMplsIpTunnel, - RouteProtocol, RouteScope, RouteType, + RouteNextHop, RouteNextHopFlags, RouteProtocol, RouteScope, RouteType, + RouteVia, }, AddressFamily, }; @@ -20,6 +21,12 @@ pub struct RouteMessageBuilder { _phantom: PhantomData, } +#[derive(Debug, Clone)] +pub struct RouteNextHopBuilder { + address_family: AddressFamily, + nexthop: RouteNextHop, +} + impl RouteMessageBuilder { /// Create default RouteMessage with header set to: /// * route: [RouteHeader::RT_TABLE_MAIN] @@ -76,6 +83,14 @@ impl RouteMessageBuilder { self } + /// Sets multiple nexthop entries for the route. + pub fn multipath(mut self, nexthops: Vec) -> Self { + self.message + .attributes + .push(RouteAttribute::MultiPath(nexthops)); + self + } + /// Sets the route priority (metric) pub fn priority(mut self, priority: u32) -> Self { self.message @@ -467,3 +482,70 @@ impl Default for RouteMessageBuilder { Self::new() } } + +impl RouteNextHopBuilder { + /// Create default RouteNexthop for a route with the given address family. + pub fn new(address_family: AddressFamily) -> Self { + Self { + address_family, + nexthop: Default::default(), + } + } + + /// Sets the nexthop interface index. + pub fn interface(mut self, index: u32) -> Self { + self.nexthop.interface_index = index; + self + } + + /// Sets the nexthop (via) address. + pub fn via(mut self, addr: IpAddr) -> Result { + use AddressFamily::*; + let attr = match (self.address_family, addr) { + (Inet, addr @ IpAddr::V4(_)) | (Inet6, addr @ IpAddr::V6(_)) => { + RouteAttribute::Gateway(addr.into()) + } + (Inet, IpAddr::V6(v6)) => RouteAttribute::Via(RouteVia::Inet6(v6)), + (Mpls, _) => RouteAttribute::Via(addr.into()), + (af, _) => return Err(InvalidRouteMessage::AddressFamily(af)), + }; + self.nexthop.attributes.push(attr); + Ok(self) + } + + /// Marks the nexthop as directly reachable (on-link). + /// + /// Indicates that the nexthop is reachable without passing through a + /// connected subnet. + pub fn onlink(mut self) -> Self { + self.nexthop.flags.insert(RouteNextHopFlags::Onlink); + self + } + + /// Sets the nexthop MPLS encapsulation labels. + pub fn mpls(mut self, labels: Vec) -> Self { + if labels.is_empty() { + return self; + } + if self.address_family == AddressFamily::Mpls { + self.nexthop + .attributes + .push(RouteAttribute::NewDestination(labels)); + } else { + self.nexthop + .attributes + .push(RouteAttribute::EncapType(RouteLwEnCapType::Mpls)); + let encap = RouteLwTunnelEncap::Mpls( + RouteMplsIpTunnel::Destination(labels), + ); + self.nexthop + .attributes + .push(RouteAttribute::Encap(vec![encap])); + } + self + } + + pub fn build(self) -> RouteNextHop { + self.nexthop + } +} diff --git a/src/route/mod.rs b/src/route/mod.rs index 39fa681..17b24b0 100644 --- a/src/route/mod.rs +++ b/src/route/mod.rs @@ -8,6 +8,7 @@ mod handle; pub use self::add::RouteAddRequest; pub use self::builder::RouteMessageBuilder; +pub use self::builder::RouteNextHopBuilder; pub use self::del::RouteDelRequest; pub use self::get::{IpVersion, RouteGetRequest}; pub use self::handle::RouteHandle;