From 10535f3fa8a9403a50d311b34248301c5cb7882c Mon Sep 17 00:00:00 2001 From: Renato Westphal Date: Thu, 21 Mar 2024 12:39:14 -0300 Subject: [PATCH] routing, bgp: implement recursive nexthop resolution This commit introduces an initial implementation of recursive nexthop resolution. Recursive nexthops are resolved upon the addition or update of their corresponding routes in the RIB. Currently, only one level of recursion is supported. Routing protocols installing routes with recursive nexthops should implement next-hop tracking to reinstall/uninstall the affected routes whenever the resolving route for the recursive nexthop changes. When displaying the YANG-modeled RIB operational data, only the recursive next-hop is currently shown, and the resolved next-hops are not. Note: Some code duplication exists for IPv4 and IPv6 in the netlink.rs file. This duplication is expected to be resolved in the near future following an update to the rtnetlink crate. Signed-off-by: Renato Westphal --- holo-bgp/src/southbound/tx.rs | 5 +- holo-routing/src/netlink.rs | 100 +++++++++++++++++---------- holo-routing/src/northbound/state.rs | 52 +++++++++++--- holo-routing/src/rib.rs | 34 ++++++++- holo-utils/src/southbound.rs | 5 ++ 5 files changed, 142 insertions(+), 54 deletions(-) diff --git a/holo-bgp/src/southbound/tx.rs b/holo-bgp/src/southbound/tx.rs index 128a5a8f..ae015f3a 100644 --- a/holo-bgp/src/southbound/tx.rs +++ b/holo-bgp/src/southbound/tx.rs @@ -32,11 +32,10 @@ pub(crate) fn route_install( let nexthops = route .nexthops .iter() - .map(|nexthop| Nexthop::Address { - // TODO - ifindex: 0, + .map(|nexthop| Nexthop::Recursive { addr: *nexthop, labels: vec![], + resolved: Default::default(), }) .collect::>(); diff --git a/holo-routing/src/netlink.rs b/holo-routing/src/netlink.rs index 5542a1ce..4efcbd66 100644 --- a/holo-routing/src/netlink.rs +++ b/holo-routing/src/netlink.rs @@ -4,14 +4,14 @@ // SPDX-License-Identifier: MIT // -use std::net::IpAddr; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use capctl::caps::CapState; use holo_utils::mpls::Label; use holo_utils::protocol::Protocol; use holo_utils::southbound::Nexthop; use ipnetwork::IpNetwork; -use rtnetlink::{new_connection, Handle}; +use rtnetlink::{new_connection, Handle, RouteAddRequest}; use tracing::error; use crate::rib::Route; @@ -52,24 +52,7 @@ pub(crate) async fn ip_route_install( .destination_prefix(prefix.ip(), prefix.prefix()); // Add nexthops. - for nexthop in route.nexthops.iter() { - request = match nexthop { - Nexthop::Address { addr, ifindex, .. } => { - if let IpAddr::V4(addr) = addr { - request.gateway(*addr).output_interface(*ifindex) - } else { - request - } - } - Nexthop::Interface { ifindex } => { - request.output_interface(*ifindex) - } - Nexthop::Special(_) => { - // TODO: not supported by the `rtnetlink` crate yet. - request - } - }; - } + request = add_nexthops_ipv4(request, route.nexthops.iter()); // Execute request. if let Err(error) = request.execute().await { @@ -84,24 +67,7 @@ pub(crate) async fn ip_route_install( .destination_prefix(prefix.ip(), prefix.prefix()); // Add nexthops. - for nexthop in route.nexthops.iter() { - request = match nexthop { - Nexthop::Address { addr, ifindex, .. } => { - if let IpAddr::V6(addr) = addr { - request.gateway(*addr).output_interface(*ifindex) - } else { - request - } - } - Nexthop::Interface { ifindex } => { - request.output_interface(*ifindex) - } - Nexthop::Special(_) => { - // TODO: not supported by the `rtnetlink` crate yet. - request - } - }; - } + request = add_nexthops_ipv6(request, route.nexthops.iter()); // Execute request. if let Err(error) = request.execute().await { @@ -111,6 +77,64 @@ pub(crate) async fn ip_route_install( } } +fn add_nexthops_ipv4<'a>( + mut request: RouteAddRequest, + nexthops: impl Iterator, +) -> RouteAddRequest { + for nexthop in nexthops { + request = match nexthop { + Nexthop::Address { addr, ifindex, .. } => { + if let IpAddr::V4(addr) = addr { + request.gateway(*addr).output_interface(*ifindex) + } else { + request + } + } + Nexthop::Interface { ifindex } => { + request.output_interface(*ifindex) + } + Nexthop::Special(_) => { + // TODO: not supported by the `rtnetlink` crate yet. + request + } + Nexthop::Recursive { resolved, .. } => { + add_nexthops_ipv4(request, resolved.iter()) + } + }; + } + + request +} + +fn add_nexthops_ipv6<'a>( + mut request: RouteAddRequest, + nexthops: impl Iterator, +) -> RouteAddRequest { + for nexthop in nexthops { + request = match nexthop { + Nexthop::Address { addr, ifindex, .. } => { + if let IpAddr::V6(addr) = addr { + request.gateway(*addr).output_interface(*ifindex) + } else { + request + } + } + Nexthop::Interface { ifindex } => { + request.output_interface(*ifindex) + } + Nexthop::Special(_) => { + // TODO: not supported by the `rtnetlink` crate yet. + request + } + Nexthop::Recursive { resolved, .. } => { + add_nexthops_ipv6(request, resolved.iter()) + } + }; + } + + request +} + pub(crate) async fn ip_route_uninstall( handle: &Handle, prefix: &IpNetwork, diff --git a/holo-routing/src/northbound/state.rs b/holo-routing/src/northbound/state.rs index fa6ee04c..bd99d905 100644 --- a/holo-routing/src/northbound/state.rs +++ b/holo-routing/src/northbound/state.rs @@ -227,7 +227,15 @@ fn load_callbacks() -> Callbacks { if route.nexthops.len() == 1 { let nexthop = route.nexthops.first().unwrap(); - if let Nexthop::Address { addr: IpAddr::V4(addr), .. } = nexthop { + if let Nexthop::Address { + addr: IpAddr::V4(addr), + .. + } + | Nexthop::Recursive { + addr: IpAddr::V4(addr), + .. + } = nexthop + { return Some(*addr); } } @@ -240,7 +248,15 @@ fn load_callbacks() -> Callbacks { if route.nexthops.len() == 1 { let nexthop = route.nexthops.first().unwrap(); - if let Nexthop::Address { addr: IpAddr::V6(addr), .. } = nexthop { + if let Nexthop::Address { + addr: IpAddr::V6(addr), + .. + } + | Nexthop::Recursive { + addr: IpAddr::V6(addr), + .. + } = nexthop + { return Some(*addr); } } @@ -285,20 +301,36 @@ fn load_callbacks() -> Callbacks { .path(ribs::rib::routes::route::next_hop::next_hop_list::next_hop::ipv4_address::PATH) .get_element_ipv4(|_master, args| { let nexthop = args.list_entry.as_nexthop().unwrap(); - if let Nexthop::Address { addr: IpAddr::V4(addr), .. } = nexthop { - Some(*addr) - } else { - None + if let Nexthop::Address { + addr: IpAddr::V4(addr), + .. + } + | Nexthop::Recursive { + addr: IpAddr::V4(addr), + .. + } = nexthop + { + return Some(*addr); } + + None }) .path(ribs::rib::routes::route::next_hop::next_hop_list::next_hop::ipv6_address::PATH) .get_element_ipv6(|_master, args| { let nexthop = args.list_entry.as_nexthop().unwrap(); - if let Nexthop::Address { addr: IpAddr::V6(addr), .. } = nexthop { - Some(*addr) - } else { - None + if let Nexthop::Address { + addr: IpAddr::V6(addr), + .. } + | Nexthop::Recursive { + addr: IpAddr::V6(addr), + .. + } = nexthop + { + return Some(*addr); + } + + None }) .path(ribs::rib::routes::route::next_hop::next_hop_list::next_hop::mpls_label_stack::entry::PATH) .get_iterate(|_master, args| { diff --git a/holo-routing/src/rib.rs b/holo-routing/src/rib.rs index 88762023..a4f53dd9 100644 --- a/holo-routing/src/rib.rs +++ b/holo-routing/src/rib.rs @@ -121,7 +121,8 @@ impl Rib { } // Adds IP route to the RIB. - pub(crate) async fn ip_route_add(&mut self, msg: RouteMsg) { + pub(crate) async fn ip_route_add(&mut self, mut msg: RouteMsg) { + msg.nexthops = self.resolve_nexthops(msg.nexthops); let rib_prefix = self.prefix_entry(msg.prefix); match rib_prefix.entry(msg.distance) { btree_map::Entry::Vacant(v) => { @@ -173,7 +174,8 @@ impl Rib { } // Adds MPLS route to the RIB. - pub(crate) async fn mpls_route_add(&mut self, msg: LabelInstallMsg) { + pub(crate) async fn mpls_route_add(&mut self, mut msg: LabelInstallMsg) { + msg.nexthops = self.resolve_nexthops(msg.nexthops); match self.mpls.entry(msg.label) { btree_map::Entry::Vacant(v) => { // If the MPLS route does not exist, create a new entry. @@ -443,7 +445,8 @@ impl Rib { } } - pub(crate) fn prefix_longest_match(&self, addr: &IpAddr) -> Option<&Route> { + // Returns the longest matching route for the given IP address. + fn prefix_longest_match(&self, addr: &IpAddr) -> Option<&Route> { let lpm = match addr { IpAddr::V4(addr) => { let prefix = @@ -466,6 +469,31 @@ impl Rib { .filter(|route| !route.flags.contains(RouteFlags::REMOVED)) } + // Resolves the recursive next-hops in the provided set of next-hops. + // + // Note that only one level of recursion is resolved. If the resolved + // next-hops contain recursive next-hops themselves, those will not be + // resolved further. + fn resolve_nexthops( + &self, + nexthops: BTreeSet, + ) -> BTreeSet { + nexthops + .into_iter() + .map(|mut nexthop| { + if let Nexthop::Recursive { addr, resolved, .. } = &mut nexthop + { + if let Some(route) = self.prefix_longest_match(addr) { + resolved.clone_from(&route.nexthops); + } else { + debug!(%addr, "failed to resolve recursive nexthop"); + } + } + nexthop + }) + .collect() + } + // Adds IP route to the update queue. fn ip_update_queue_add(&mut self, prefix: IpNetwork) { self.ip_update_queue.insert(prefix); diff --git a/holo-utils/src/southbound.rs b/holo-utils/src/southbound.rs index 126d41b5..509a0b2c 100644 --- a/holo-utils/src/southbound.rs +++ b/holo-utils/src/southbound.rs @@ -47,6 +47,11 @@ pub enum Nexthop { ifindex: u32, }, Special(NexthopSpecial), + Recursive { + addr: IpAddr, + labels: Vec