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