Skip to content

Commit

Permalink
routing, bgp: implement recursive nexthop resolution
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
rwestphal committed Mar 21, 2024
1 parent 85044aa commit 10535f3
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 54 deletions.
5 changes: 2 additions & 3 deletions holo-bgp/src/southbound/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<BTreeSet<_>>();

Expand Down
100 changes: 62 additions & 38 deletions holo-routing/src/netlink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -111,6 +77,64 @@ pub(crate) async fn ip_route_install(
}
}

fn add_nexthops_ipv4<'a>(
mut request: RouteAddRequest<Ipv4Addr>,
nexthops: impl Iterator<Item = &'a Nexthop>,
) -> RouteAddRequest<Ipv4Addr> {
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<Ipv6Addr>,
nexthops: impl Iterator<Item = &'a Nexthop>,
) -> RouteAddRequest<Ipv6Addr> {
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,
Expand Down
52 changes: 42 additions & 10 deletions holo-routing/src/northbound/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,15 @@ fn load_callbacks() -> Callbacks<Master> {

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);
}
}
Expand All @@ -240,7 +248,15 @@ fn load_callbacks() -> Callbacks<Master> {

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);
}
}
Expand Down Expand Up @@ -285,20 +301,36 @@ fn load_callbacks() -> Callbacks<Master> {
.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| {
Expand Down
34 changes: 31 additions & 3 deletions holo-routing/src/rib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 =
Expand All @@ -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<Nexthop>,
) -> BTreeSet<Nexthop> {
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);
Expand Down
5 changes: 5 additions & 0 deletions holo-utils/src/southbound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ pub enum Nexthop {
ifindex: u32,
},
Special(NexthopSpecial),
Recursive {
addr: IpAddr,
labels: Vec<Label>,
resolved: BTreeSet<Nexthop>,
},
}

#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
Expand Down

0 comments on commit 10535f3

Please sign in to comment.