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/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/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/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)) } diff --git a/src/route/builder.rs b/src/route/builder.rs index a67be72..42a5301 100644 --- a/src/route/builder.rs +++ b/src/route/builder.rs @@ -7,8 +7,10 @@ use std::{ use netlink_packet_route::{ route::{ - RouteAddress, RouteAttribute, RouteFlags, RouteHeader, RouteMessage, - RouteProtocol, RouteScope, RouteType, + MplsLabel, RouteAddress, RouteAttribute, RouteFlags, RouteHeader, + RouteLwEnCapType, RouteLwTunnelEncap, RouteMessage, RouteMplsIpTunnel, + RouteNextHop, RouteNextHopFlags, RouteProtocol, RouteScope, RouteType, + RouteVia, }, AddressFamily, }; @@ -19,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] @@ -52,6 +60,37 @@ 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 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 @@ -402,3 +441,111 @@ 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() + } +} + +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;