Skip to content

Commit

Permalink
bgp: implement route redistribution
Browse files Browse the repository at this point in the history
What's the use of a BGP implementation if you can't advertise your
own routes? That problem is solved now :)

The YANG augmentations were necessary because the IETF BGP YANG
model doesn't have nodes for configuring route redistribution, for
whatever reason.

Support for applying policies on the redistributed routes should be
implemented separately later.

Signed-off-by: Renato Westphal <[email protected]>
  • Loading branch information
rwestphal committed Mar 28, 2024
1 parent 09824fb commit 88980e6
Show file tree
Hide file tree
Showing 15 changed files with 394 additions and 78 deletions.
86 changes: 50 additions & 36 deletions holo-bgp/src/af.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub trait AddressFamily: Sized {
fn nexthop_rx_extract(attrs: &BaseAttrs) -> IpAddr;

// Modify the next hop(s) for transmission.
fn nexthop_tx_change(nbr: &Neighbor, attrs: &mut BaseAttrs);
fn nexthop_tx_change(nbr: &Neighbor, local: bool, attrs: &mut BaseAttrs);

// Build BGP UPDATE messages based on the provided update queue.
fn build_updates(queue: &mut NeighborUpdateQueue<Self>) -> Vec<Message>;
Expand Down Expand Up @@ -85,29 +85,36 @@ impl AddressFamily for Ipv4Unicast {
attrs.nexthop.unwrap()
}

fn nexthop_tx_change(nbr: &Neighbor, attrs: &mut BaseAttrs) {
fn nexthop_tx_change(nbr: &Neighbor, local: bool, attrs: &mut BaseAttrs) {
// Get source address of the BGP session.
let session_src = match nbr.conn_info.as_ref().unwrap().local_addr {
IpAddr::V4(addr) => {
// BGP over IPv4.
addr
}
IpAddr::V6(_addr) => {
// BGP over IPv6.
//
// TODO: use IPv4 address of the corresponding system interface.
Ipv4Addr::UNSPECIFIED
}
};

// Handle locally originated routes.
if local {
attrs.nexthop = Some(session_src.into());
return;
}

match nbr.peer_type {
PeerType::Internal => {
// Next hop isn't modified.
}
PeerType::External => {
if !nbr.shared_subnet {
// Update next hop.
match nbr.conn_info.as_ref().unwrap().local_addr {
IpAddr::V4(src_addr) => {
// BGP over IPv4.
//
// Use source address of the eBGP session.
attrs.nexthop = Some(src_addr.into())
}
IpAddr::V6(_src_addr) => {
// BGP over IPv6.
//
// TODO: use IPv4 address of the corresponding
// system interface.
attrs.nexthop = None;
}
}
// Update next hop using the source address of the eBGP
// session.
attrs.nexthop = Some(session_src.into());
} else {
// Next hop isn't modified (eBGP next hop optimization).
}
Expand Down Expand Up @@ -202,7 +209,28 @@ impl AddressFamily for Ipv6Unicast {
.unwrap_or(attrs.nexthop.unwrap())
}

fn nexthop_tx_change(nbr: &Neighbor, attrs: &mut BaseAttrs) {
fn nexthop_tx_change(nbr: &Neighbor, local: bool, attrs: &mut BaseAttrs) {
// Get source address of the BGP session.
let session_src = match nbr.conn_info.as_ref().unwrap().local_addr {
IpAddr::V4(addr) => {
// BGP over IPv4 (IPv4-mapped IPv6 address).
addr.to_ipv6_mapped()
}
IpAddr::V6(addr) => {
// BGP over IPv6.
addr
}
};

// Handle locally originated routes.
if local {
attrs.nexthop = Some(session_src.into());
if nbr.shared_subnet {
// TODO: update link-local next hop.
}
return;
}

match nbr.peer_type {
PeerType::Internal => {
// Global next hop isn't modified.
Expand All @@ -211,23 +239,9 @@ impl AddressFamily for Ipv6Unicast {
}
PeerType::External => {
if !nbr.shared_subnet {
// Update global next hop.
match nbr.conn_info.as_ref().unwrap().local_addr {
IpAddr::V4(src_addr) => {
// BGP over IPv4.
//
// Use source address of the eBGP session
// (IPv4-mapped IPv6 address).
attrs.nexthop =
Some(src_addr.to_ipv6_mapped().into())
}
IpAddr::V6(src_addr) => {
// BGP over IPv6.
//
// Use source address of the eBGP session.
attrs.nexthop = Some(src_addr.into())
}
}
// Update global next hop using the source address of the
// eBGP session.
attrs.nexthop = Some(session_src.into());

// Unset link-local next hop.
attrs.ll_nexthop = None;
Expand Down
17 changes: 13 additions & 4 deletions holo-bgp/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,12 @@ where
if update {
// Update route's attributes before transmission.
let mut attrs = rpinfo.attrs;
attrs_tx_update::<A>(nbr, instance.config.asn, &mut attrs);
attrs_tx_update::<A>(
nbr,
instance.config.asn,
rpinfo.origin.is_local(),
&mut attrs,
);

// Update neighbor's Tx queue.
let update_queue = A::update_queue(&mut nbr.update_queues);
Expand All @@ -601,8 +606,12 @@ where
Ok(())
}

fn attrs_tx_update<A>(nbr: &Neighbor, local_asn: u32, attrs: &mut Attrs)
where
fn attrs_tx_update<A>(
nbr: &Neighbor,
local_asn: u32,
local: bool,
attrs: &mut Attrs,
) where
A: AddressFamily,
{
match nbr.peer_type {
Expand All @@ -625,7 +634,7 @@ where
}

// Update the next-hop attribute based on the address family if necessary.
A::nexthop_tx_change(nbr, &mut attrs.base);
A::nexthop_tx_change(nbr, local, &mut attrs.base);
}

// ===== BGP decision process =====
Expand Down
8 changes: 8 additions & 0 deletions holo-bgp/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,14 @@ async fn process_ibus_msg(
// Remove the local copy of the policy definition.
instance.shared.policies.remove(&policy_name);
}
IbusMsg::RouteRedistributeAdd(msg) => {
// Route redistribute update notification.
southbound::rx::process_route_add(instance, msg);
}
IbusMsg::RouteRedistributeDel(msg) => {
// Route redistribute delete notification.
southbound::rx::process_route_del(instance, msg);
}
// Ignore other events.
_ => {}
}
Expand Down
67 changes: 65 additions & 2 deletions holo-bgp/src/northbound/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

#![allow(clippy::derivable_impls)]

use std::collections::BTreeMap;
use std::collections::{BTreeMap, HashMap};
use std::net::{IpAddr, Ipv4Addr};
use std::sync::LazyLock as Lazy;

Expand All @@ -18,8 +18,10 @@ use holo_northbound::configuration::{
};
use holo_northbound::paths::control_plane_protocol::bgp;
use holo_utils::bgp::AfiSafi;
use holo_utils::ip::IpAddrKind;
use holo_utils::ibus::IbusMsg;
use holo_utils::ip::{AddressFamily, IpAddrKind};
use holo_utils::policy::{ApplyPolicyCfg, DefaultPolicyType};
use holo_utils::protocol::Protocol;
use holo_utils::yang::DataNodeRefExt;
use holo_yang::TryFromYang;

Expand All @@ -34,6 +36,7 @@ pub enum ListEntry {
#[default]
None,
AfiSafi(AfiSafi),
Redistribution(AfiSafi, Protocol),
Neighbor(IpAddr),
NeighborAfiSafi(IpAddr, AfiSafi),
}
Expand All @@ -48,6 +51,7 @@ pub enum Event {
NeighborDelete(IpAddr),
NeighborReset(IpAddr, NotificationMsg),
NeighborUpdateAuth(IpAddr),
RedistributeRequest(Protocol, AddressFamily),
}

pub static VALIDATION_CALLBACKS: Lazy<ValidationCallbacks> =
Expand Down Expand Up @@ -89,6 +93,7 @@ pub struct InstanceAfiSafiCfg {
pub prefix_limit: PrefixLimitCfg,
pub send_default_route: bool,
pub apply_policy: ApplyPolicyCfg,
pub redistribution: HashMap<Protocol, RedistributionCfg>,
}

#[derive(Debug)]
Expand Down Expand Up @@ -153,6 +158,9 @@ pub struct PrefixLimitCfg {
pub idle_time: Option<u32>,
}

#[derive(Debug, Default)]
pub struct RedistributionCfg {}

#[derive(Debug)]
pub struct AsPathOptions {
pub allow_own_as: u8,
Expand Down Expand Up @@ -455,6 +463,30 @@ fn load_callbacks() -> Callbacks<Instance> {
let send = args.dnode.get_bool();
afi_safi.send_default_route = send;
})
.path(bgp::global::afi_safis::afi_safi::ipv4_unicast::redistribution::PATH)
.create_apply(|instance, args| {
let afi_safi = args.list_entry.into_afi_safi().unwrap();
let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap();

let protocol = args.dnode.get_string_relative("./type").unwrap();
let protocol = Protocol::try_from_yang(&protocol).unwrap();
afi_safi.redistribution.insert(protocol, Default::default());

let event_queue = args.event_queue;
event_queue.insert(Event::RedistributeRequest(protocol, AddressFamily::Ipv4));
})
.delete_apply(|instance, args| {
let (afi_safi, protocol) = args.list_entry.into_redistribution().unwrap();
let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap();

afi_safi.redistribution.remove(&protocol);
})
.lookup(|_instance, list_entry, dnode| {
let afi_safi = list_entry.into_afi_safi().unwrap();
let protocol = dnode.get_string_relative("./type").unwrap();
let protocol = Protocol::try_from_yang(&protocol).unwrap();
ListEntry::Redistribution(afi_safi, protocol)
})
.path(bgp::global::afi_safis::afi_safi::ipv6_unicast::prefix_limit::max_prefixes::PATH)
.modify_apply(|instance, args| {
let afi_safi = args.list_entry.into_afi_safi().unwrap();
Expand Down Expand Up @@ -514,6 +546,30 @@ fn load_callbacks() -> Callbacks<Instance> {
let send = args.dnode.get_bool();
afi_safi.send_default_route = send;
})
.path(bgp::global::afi_safis::afi_safi::ipv6_unicast::redistribution::PATH)
.create_apply(|instance, args| {
let afi_safi = args.list_entry.into_afi_safi().unwrap();
let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap();

let protocol = args.dnode.get_string_relative("./type").unwrap();
let protocol = Protocol::try_from_yang(&protocol).unwrap();
afi_safi.redistribution.insert(protocol, Default::default());

let event_queue = args.event_queue;
event_queue.insert(Event::RedistributeRequest(protocol, AddressFamily::Ipv6));
})
.delete_apply(|instance, args| {
let (afi_safi, protocol) = args.list_entry.into_redistribution().unwrap();
let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap();

afi_safi.redistribution.remove(&protocol);
})
.lookup(|_instance, list_entry, dnode| {
let afi_safi = list_entry.into_afi_safi().unwrap();
let protocol = dnode.get_string_relative("./type").unwrap();
let protocol = Protocol::try_from_yang(&protocol).unwrap();
ListEntry::Redistribution(afi_safi, protocol)
})
.path(bgp::global::apply_policy::import_policy::PATH)
.create_apply(|instance, args| {
let policy = args.dnode.get_string();
Expand Down Expand Up @@ -1297,6 +1353,12 @@ impl Provider for Instance {
);
}
}
Event::RedistributeRequest(protocol, af) => {
let _ = self.tx.ibus.send(IbusMsg::RouteRedistributeDump {
protocol,
af: Some(af),
});
}
}
}
}
Expand Down Expand Up @@ -1355,6 +1417,7 @@ impl Default for InstanceAfiSafiCfg {
prefix_limit: Default::default(),
send_default_route: false,
apply_policy: Default::default(),
redistribution: Default::default(),
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions holo-bgp/src/northbound/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,21 @@ fn load_callbacks() -> Callbacks<Instance> {
// TODO: implement me!
None
})
.path(bgp::global::afi_safis::afi_safi::ipv4_unicast::redistribution::PATH)
.get_iterate(|_instance, _args| {
// No operational data under this list.
None
})
.path(bgp::global::afi_safis::afi_safi::ipv6_unicast::prefix_limit::prefix_limit_exceeded::PATH)
.get_element_bool(|_instance, _args| {
// TODO: implement me!
None
})
.path(bgp::global::afi_safis::afi_safi::ipv6_unicast::redistribution::PATH)
.get_iterate(|_instance, _args| {
// No operational data under this list.
None
})
.path(bgp::global::apply_policy::import_policy::PATH)
.get_iterate(|_instance, _args| {
// No operational data under this list.
Expand Down
4 changes: 2 additions & 2 deletions holo-bgp/src/packet/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use crate::packet::message::{
pub const ATTR_MIN_LEN: u16 = 3;
pub const ATTR_MIN_LEN_EXT: u16 = 4;

#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
#[derive(Deserialize, Serialize)]
pub struct Attrs {
pub base: BaseAttrs,
Expand All @@ -40,7 +40,7 @@ pub struct Attrs {
pub unknown: Box<[UnknownAttr]>,
}

#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
#[derive(Deserialize, Serialize)]
pub struct BaseAttrs {
pub origin: Origin,
Expand Down
Loading

0 comments on commit 88980e6

Please sign in to comment.