Skip to content

MPLS + multipath support #104

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 29, 2025
Merged
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
89 changes: 89 additions & 0 deletions examples/add_route_mpls.rs
Original file line number Diff line number Diff line change
@@ -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<String> = env::args().collect();
if args.len() != 4 {
usage();
return Ok(());
}

let input_label = args[1]
.parse::<u32>()
.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::<u32>()
.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::<MplsLabel>::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 -- <input_label> <gateway> <output_label>

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 -- <input_label> <gateway> \
<output_label>"
);
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
17 changes: 12 additions & 5 deletions src/link/bond.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};

Expand Down Expand Up @@ -125,7 +126,7 @@ impl LinkMessageBuilder<LinkBond> {
/// 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))
}

Expand All @@ -140,21 +141,27 @@ impl LinkMessageBuilder<LinkBond> {
/// 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))
}

Expand Down
151 changes: 149 additions & 2 deletions src/route/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand All @@ -19,6 +21,12 @@ pub struct RouteMessageBuilder<T = IpAddr> {
_phantom: PhantomData<T>,
}

#[derive(Debug, Clone)]
pub struct RouteNextHopBuilder {
address_family: AddressFamily,
nexthop: RouteNextHop,
}

impl<T> RouteMessageBuilder<T> {
/// Create default RouteMessage with header set to:
/// * route: [RouteHeader::RT_TABLE_MAIN]
Expand Down Expand Up @@ -52,6 +60,37 @@ impl<T> RouteMessageBuilder<T> {
self
}

/// Sets the output MPLS encapsulation labels.
pub fn output_mpls(mut self, labels: Vec<MplsLabel>) -> 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<RouteNextHop>) -> Self {
self.message
.attributes
.push(RouteAttribute::MultiPath(nexthops));
self
}

/// Sets the route priority (metric)
pub fn priority(mut self, priority: u32) -> Self {
self.message
Expand Down Expand Up @@ -402,3 +441,111 @@ impl Default for RouteMessageBuilder<IpAddr> {
Self::new()
}
}

impl RouteMessageBuilder<MplsLabel> {
/// 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<MplsLabel> {
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<Self, InvalidRouteMessage> {
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<MplsLabel>) -> 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
}
}
1 change: 1 addition & 0 deletions src/route/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;