Skip to content

Commit b50073f

Browse files
committed
bgp: add more role validation
Make sure the OTC is added before route is sent to Customer, peer or RS-Client. Additional check for if route is locally originated has been added. Move the RoleName to iana file. Signed-off-by: Paul Wekesa <paul1tw1@gmail.com>
1 parent 1934b0d commit b50073f

10 files changed

Lines changed: 197 additions & 108 deletions

File tree

holo-bgp/src/events.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::error::{Error, IoError, NbrRxError};
2222
use crate::instance::{InstanceUpView, PolicyApplyTasks};
2323
use crate::neighbor::{Neighbor, Neighbors, PeerType, fsm};
2424
use crate::packet::attribute::Attrs;
25-
use crate::packet::iana::{Afi, Safi};
25+
use crate::packet::iana::{Afi, RoleName, Safi};
2626
use crate::packet::message::{
2727
Capability, Message, MpReachNlri, MpUnreachNlri, RouteRefreshMsg, UpdateMsg,
2828
};
@@ -285,6 +285,18 @@ fn process_nbr_reach_prefixes<A>(
285285
return;
286286
}
287287

288+
// RFC 9234: If a route is received from a Provider, a Peer or an RS and
289+
// the OTC Attribute is not present, it MUST be added with AS number of
290+
// remote AS.
291+
if let Some(remote_role) = nbr.remote_role
292+
&& matches!(
293+
remote_role,
294+
RoleName::Provider | RoleName::Peer | RoleName::Rs
295+
)
296+
{
297+
let _ = attrs.base.otc.get_or_insert(nbr.config.peer_as);
298+
}
299+
288300
// Initialize route origin and type.
289301
let origin = RouteOrigin::Neighbor {
290302
identifier: nbr.identifier.unwrap(),
@@ -564,6 +576,20 @@ where
564576

565577
// Update route's attributes before transmission.
566578
let mut attrs = rpinfo.attrs;
579+
580+
// RFC 9234; If a route already contains the OTC Attribute,
581+
// it be propagated to Providers, Peers, or RSes.
582+
if let Some(remote_role) = nbr.remote_role
583+
&& matches!(
584+
remote_role,
585+
RoleName::Provider | RoleName::Peer | RoleName::Rs
586+
)
587+
&& attrs.base.otc.is_some()
588+
&& !rpinfo.origin.is_local()
589+
{
590+
continue;
591+
}
592+
567593
rib::attrs_tx_update::<A>(
568594
&mut attrs,
569595
nbr,
@@ -679,6 +705,7 @@ where
679705
let best_route = rib::best_path::<A>(
680706
dest,
681707
instance.config.asn,
708+
neighbors,
682709
&table.nht,
683710
selection_cfg,
684711
);

holo-bgp/src/neighbor.rs

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use std::time::Duration;
1313
use arbitrary::Arbitrary;
1414
use chrono::{DateTime, Utc};
1515
use holo_protocol::InstanceChannelsTx;
16-
use holo_utils::bgp::{AfiSafi, RoleName, RouteType, WellKnownCommunities};
16+
use holo_utils::bgp::{AfiSafi, RouteType, WellKnownCommunities};
1717
use holo_utils::ibus::IbusChannelsTx;
1818
use holo_utils::socket::{TTL_MAX, TcpConnInfo, TcpStream};
1919
use holo_utils::task::{IntervalTask, Task, TimeoutTask};
@@ -30,7 +30,7 @@ use crate::northbound::notification;
3030
use crate::northbound::rpc::ClearType;
3131
use crate::packet::attribute::{AS_TRANS, Attrs};
3232
use crate::packet::iana::{
33-
Afi, CeaseSubcode, ErrorCode, FsmErrorSubcode, Safi,
33+
Afi, CeaseSubcode, ErrorCode, FsmErrorSubcode, RoleName, Safi,
3434
};
3535
use crate::packet::message::{
3636
Capability, DecodeCxt, EncodeCxt, KeepaliveMsg, Message,
@@ -698,7 +698,7 @@ impl Neighbor {
698698
});
699699
}
700700

701-
if let Some(role) = self.config.role {
701+
if let Some(role) = self.config.local_role {
702702
capabilities.insert(Capability::Role { role });
703703
}
704704

@@ -814,40 +814,46 @@ impl Neighbor {
814814
));
815815
}
816816

817-
if let Some(local_role) = self.config.role
817+
// RFC 9234: Role correctness validation for OPEN messages.
818+
let mut role_correctness = true;
819+
if let Some(local_role) = self.config.local_role
818820
&& let Some(Capability::Role { role: remote_role }) = msg
819821
.capabilities
820822
.iter()
821823
.find(|cap| matches!(cap, Capability::Role { .. }))
822824
{
823-
let err = Err(Error::NbrRoleMismatch(
824-
self.remote_addr,
825-
local_role.to_u8().unwrap(),
826-
remote_role.to_u8().unwrap(),
827-
));
828-
829-
// Validate the incoming BGP Role.
830-
// ---
831-
// Validation 1:
832-
// Check if the neighbor had already sent a Role capability,
833-
// and if the role capability is same as incoming.
834-
if self.remote_role.is_some()
835-
&& self.remote_role != Some(*remote_role)
825+
// If remote role already exists, it should be same as incoming role.
826+
if let Some(r_role) = self.remote_role
827+
&& r_role != *remote_role
836828
{
837-
return err;
829+
// TODO: (RFC 9234 Section 4.2)
830+
// Use error below only when 'strict mode'
831+
// is enabled
832+
//
833+
// let err = Err(Error::NbrRoleMismatch(
834+
// self.remote_addr,
835+
// local_role.to_u8().unwrap(),
836+
// remote_role.to_u8().unwrap(),
837+
// ));
838+
// Should throw error on struct mode.
839+
role_correctness = false;
838840
}
839841

840-
// Validation 2:
841-
// Finds if:
842-
// 1. Role has been locally configured.
843-
// 2. role exists on the incoming message.
844-
// 3. If local role and remote role correctly match the RFC 9234.
845-
if !RoleName::validate_role_correctness(&local_role, remote_role) {
846-
return err;
842+
// Valid Role Mappings.
843+
let role_mappings = BTreeMap::from([
844+
(RoleName::Provider, RoleName::Customer),
845+
(RoleName::Customer, RoleName::Provider),
846+
(RoleName::Rs, RoleName::RsClient),
847+
(RoleName::RsClient, RoleName::Rs),
848+
(RoleName::Peer, RoleName::Peer),
849+
]);
850+
851+
if let Some(approved_role) = role_mappings.get(&local_role)
852+
&& approved_role == remote_role
853+
&& role_correctness
854+
{
855+
self.remote_role = Some(*remote_role);
847856
}
848-
849-
// If everything is okay, then we can set the remote role correctly.
850-
self.remote_role = Some(*remote_role);
851857
}
852858

853859
Ok(())

holo-bgp/src/northbound/configuration.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use std::sync::{Arc, LazyLock as Lazy};
1313
use arc_swap::ArcSwap;
1414
use enum_as_inner::EnumAsInner;
1515
use holo_northbound::configuration::{Callbacks, CallbacksBuilder, Provider, ValidationCallbacks, ValidationCallbacksBuilder};
16-
use holo_utils::bgp::{AfiSafi, RoleName};
16+
use holo_utils::bgp::AfiSafi;
1717
use holo_utils::ip::{AddressFamily, IpAddrKind};
1818
use holo_utils::policy::{ApplyPolicyCfg, DefaultPolicyType};
1919
use holo_utils::protocol::Protocol;
@@ -25,7 +25,7 @@ use crate::instance::{Instance, InstanceUpView};
2525
use crate::neighbor::{Neighbor, PeerType, fsm};
2626
use crate::network;
2727
use crate::northbound::yang_gen::bgp;
28-
use crate::packet::iana::{CeaseSubcode, ErrorCode};
28+
use crate::packet::iana::{CeaseSubcode, ErrorCode, RoleName};
2929
use crate::packet::message::{Message, NotificationMsg};
3030
use crate::rib::RouteOrigin;
3131

@@ -136,7 +136,7 @@ pub struct NeighborCfg {
136136
pub prefix_limit: PrefixLimitCfg,
137137
pub afi_safi: BTreeMap<AfiSafi, NeighborAfiSafiCfg>,
138138
pub trace_opts: NeighborTraceOptions,
139-
pub role: Option<RoleName>,
139+
pub local_role: Option<RoleName>,
140140
}
141141

142142
#[derive(Debug)]
@@ -253,6 +253,26 @@ fn load_callbacks() -> Callbacks<Instance> {
253253
.path(bgp::global::PATH)
254254
.create_apply(|_instance, _args| {})
255255
.delete_apply(|_instance, _args| {})
256+
.path(bgp::neighbors::neighbor::local_role::PATH)
257+
.modify_apply(|instance, args| {
258+
let nbr_addr = args.list_entry.into_neighbor().unwrap();
259+
let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap();
260+
261+
let local_role = args.dnode.get_string();
262+
nbr.config.local_role = match local_role.as_str() {
263+
"provider" => Some(RoleName::Provider),
264+
"customer" => Some(RoleName::Customer),
265+
"peer" => Some(RoleName::Peer),
266+
"rs-client" => Some(RoleName::RsClient),
267+
"rs" => Some(RoleName::Rs),
268+
_ => None,
269+
};
270+
})
271+
.delete_apply(|instance, args| {
272+
let nbr_addr = args.list_entry.into_neighbor().unwrap();
273+
let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap();
274+
nbr.config.local_role = None;
275+
})
256276
.path(bgp::global::r#as::PATH)
257277
.modify_apply(|instance, args| {
258278
let asn = args.dnode.get_u32();
@@ -1778,7 +1798,7 @@ impl Default for NeighborCfg {
17781798
prefix_limit: Default::default(),
17791799
afi_safi: Default::default(),
17801800
trace_opts: Default::default(),
1781-
role: Default::default(),
1801+
local_role: Default::default(),
17821802
}
17831803
}
17841804
}

holo-bgp/src/northbound/yang.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ impl ToYang for RouteIneligibleReason {
208208
RouteIneligibleReason::Originator => "iana-bgp-rib-types:ineligible-originator".into(),
209209
RouteIneligibleReason::Confed => "iana-bgp-rib-types:ineligible-confed".into(),
210210
RouteIneligibleReason::Unresolvable => "holo-bgp:ineligible-unresolvable".into(),
211+
RouteIneligibleReason::Role => "holo-bgp:ineligible-role".into(),
211212
}
212213
}
213214
}

holo-bgp/src/packet/attribute.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -908,7 +908,7 @@ mod otc {
908908
const LEN: u8 = 4;
909909

910910
pub(super) fn encode(otc: u32, buf: &mut BytesMut) {
911-
buf.put_u8(AttrFlags::OPTIONAL.bits());
911+
buf.put_u8(AttrFlags::OPTIONAL.bits() | AttrFlags::TRANSITIVE.bits());
912912
buf.put_u8(AttrType::Otc as u8);
913913
buf.put_u8(LEN);
914914
buf.put_u32(otc);

holo-bgp/src/packet/iana.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,18 @@ pub enum AttrType {
289289
//AttrSet = 128,
290290
}
291291

292+
// Roles as defined in RFC 9234.
293+
#[derive(Clone, Copy, Ord, Debug, Eq, Hash, PartialEq, PartialOrd)]
294+
#[derive(FromPrimitive, ToPrimitive)]
295+
#[derive(Deserialize, Serialize)]
296+
pub enum RoleName {
297+
Provider = 0,
298+
Rs = 1,
299+
RsClient = 2,
300+
Customer = 3,
301+
Peer = 4, // i.e Lateral Peer.
302+
}
303+
292304
// BGP Origin.
293305
pub type Origin = holo_utils::bgp::Origin;
294306

holo-bgp/src/packet/message.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ use std::net::{Ipv4Addr, Ipv6Addr};
1010
use arbitrary::Arbitrary;
1111
use bytes::{Buf, BufMut, Bytes, BytesMut};
1212
use enum_as_inner::EnumAsInner;
13-
use holo_utils::bgp::RoleName;
1413
use holo_utils::bytes::{BytesExt, BytesMutExt, TLS_BUF};
1514
use holo_utils::ip::{
1615
Ipv4AddrExt, Ipv4NetworkExt, Ipv6AddrExt, Ipv6NetworkExt,
@@ -28,7 +27,8 @@ use crate::packet::error::{
2827
};
2928
use crate::packet::iana::{
3029
Afi, CapabilityCode, ErrorCode, MessageHeaderErrorSubcode, MessageType,
31-
OpenMessageErrorSubcode, OpenParamType, Safi, UpdateMessageErrorSubcode,
30+
OpenMessageErrorSubcode, OpenParamType, RoleName, Safi,
31+
UpdateMessageErrorSubcode,
3232
};
3333

3434
//

holo-bgp/src/rib.rs

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ use serde::{Deserialize, Serialize};
1919
use crate::af::{AddressFamily, Ipv4Unicast, Ipv6Unicast};
2020
use crate::debug::Debug;
2121
use crate::ibus;
22-
use crate::neighbor::{Neighbor, PeerType};
22+
use crate::neighbor::{Neighbor, Neighbors, PeerType};
2323
use crate::northbound::configuration::{
2424
DistanceCfg, InstanceTraceOptions, MultipathCfg, RouteSelectionCfg,
2525
};
2626
use crate::packet::attribute::{
2727
Attrs, BaseAttrs, Comms, ExtComms, Extv6Comms, LargeComms, UnknownAttr,
2828
};
29+
use crate::packet::iana::RoleName;
2930
use crate::policy::RoutePolicyInfo;
3031

3132
// Default values.
@@ -145,6 +146,7 @@ pub enum RouteIneligibleReason {
145146
Originator,
146147
Confed,
147148
Unresolvable,
149+
Role,
148150
}
149151

150152
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -689,6 +691,7 @@ where
689691
pub(crate) fn best_path<A>(
690692
dest: &mut Destination,
691693
local_asn: u32,
694+
neighbors: &Neighbors,
692695
nht: &HashMap<IpAddr, NhtEntry<A>>,
693696
selection_cfg: &RouteSelectionCfg,
694697
) -> Option<Box<Route>>
@@ -698,7 +701,7 @@ where
698701
let mut best_route = None;
699702

700703
// Iterate over each Adj-RIB-In route for the destination.
701-
for route in dest
704+
'rib_loop: for route in dest
702705
.adj_rib
703706
.values_mut()
704707
// Pick the post-policy routes.
@@ -712,7 +715,39 @@ where
712715
// First, check if the route is eligible.
713716
if route.attrs.base.value.as_path.contains(local_asn) {
714717
route.ineligible_reason = Some(RouteIneligibleReason::AsLoop);
715-
continue;
718+
continue 'rib_loop;
719+
}
720+
721+
// Check for RoleName and OTC validity.
722+
// RFC 9234 Section 5.
723+
//
724+
// 1.
725+
// If a route with the OTC Attribute is received from a Customer or an
726+
// RS-Client, then it is a route leak and be considered ineligible.
727+
//
728+
// 2.
729+
// If a route with the OTC Attribute is received from a Peer
730+
// (i.e., remote AS with a Peer Role) and the Attribute has
731+
// a value that is not equal to the remote (i.e., Peer's) AS number,
732+
// then it is a route leak and be considered ineligible.
733+
if let RouteOrigin::Neighbor { remote_addr, .. } = route.origin
734+
&& let Some(nbr) = neighbors.get(&remote_addr)
735+
&& let Some(remote_role) = nbr.remote_role
736+
{
737+
let attrs = &route.attrs.get();
738+
739+
if let Some(otc) = attrs.base.otc {
740+
let is_leak = match remote_role {
741+
RoleName::Customer | RoleName::RsClient => true,
742+
RoleName::Peer if otc != nbr.config.peer_as => true,
743+
_ => false,
744+
};
745+
746+
if is_leak {
747+
route.ineligible_reason = Some(RouteIneligibleReason::Role);
748+
continue 'rib_loop;
749+
}
750+
}
716751
}
717752

718753
// Get interior cost to the route's nexthop.
@@ -722,7 +757,7 @@ where
722757
if route.igp_cost.is_none() {
723758
route.ineligible_reason =
724759
Some(RouteIneligibleReason::Unresolvable);
725-
continue;
760+
continue 'rib_loop;
726761
};
727762
}
728763

@@ -851,6 +886,22 @@ pub(crate) fn attrs_tx_update<A>(
851886

852887
// Remove the LOCAL_PREF attribute.
853888
attrs.base.local_pref = None;
889+
890+
if let Some(remote_role) = nbr.remote_role {
891+
// RFC 9234 - Section 5:
892+
// If a route is to be advertised to a Customer, a Peer, or an
893+
// RS-Client (when the sender is an RS) and the OTC Attribute is
894+
// not present, then when advertising the route, an OTC Attribute
895+
// be added with a value equal to the AS number of the local AS
896+
match (remote_role, nbr.config.local_role) {
897+
(RoleName::Customer, _)
898+
| (RoleName::Peer, _)
899+
| (RoleName::RsClient, Some(RoleName::Rs)) => {
900+
attrs.base.otc.get_or_insert(local_asn);
901+
}
902+
(_, _) => {}
903+
}
904+
}
854905
}
855906
}
856907

0 commit comments

Comments
 (0)