From b0af04c182e494cbbc9e6347166751ffff00052a Mon Sep 17 00:00:00 2001 From: Ryan Goodfellow Date: Sun, 14 Apr 2024 02:49:31 +0000 Subject: [PATCH] add enforce-first-as option - fixes #208 --- bgp/src/error.rs | 9 +++++++++ bgp/src/session.rs | 36 +++++++++++++++++++++++++++++++++++- mgadm/src/bgp.rs | 6 ++++++ mgd/src/bgp_admin.rs | 2 ++ mgd/src/bgp_param.rs | 3 +++ mgd/src/main.rs | 1 + openapi/mg-admin.json | 13 +++++++++++++ rdb/src/types.rs | 1 + 8 files changed, 70 insertions(+), 1 deletion(-) diff --git a/bgp/src/error.rs b/bgp/src/error.rs index 6f662e53..e5049366 100644 --- a/bgp/src/error.rs +++ b/bgp/src/error.rs @@ -145,6 +145,15 @@ pub enum Error { #[error("Self loop detected")] SelfLoopDetected, + #[error("AS path missing")] + MissingAsPath, + + #[error("AS path is empty")] + EmptyAsPath, + + #[error("Enforce-first-AS check failed: expected: {0}, found: {1:?}")] + EnforceAsFirst(u32, Vec), + #[error("Invalid address")] InvalidAddress(String), diff --git a/bgp/src/session.rs b/bgp/src/session.rs index b960155c..4d603689 100644 --- a/bgp/src/session.rs +++ b/bgp/src/session.rs @@ -364,6 +364,10 @@ pub struct SessionInfo { /// Capabilities sent to the peer. pub capabilities_sent: Vec, + + /// Ensure that routes received from eBGP peers have the peer's ASN as the + /// first element in the AS path. + pub enforce_first_as: bool, } impl Default for SessionInfo { @@ -387,6 +391,7 @@ impl Default for SessionInfo { local_pref: None, capabilities_received: Vec::new(), capabilities_sent: Vec::new(), + enforce_first_as: false, } } } @@ -1628,7 +1633,14 @@ impl SessionRunner { /// Perform a set of checks on an update to see if we can accept it. fn check_update(&self, update: &UpdateMessage) -> Result<(), Error> { - self.check_for_self_in_path(update) + self.check_for_self_in_path(update)?; + let info = lock!(self.session); + if info.enforce_first_as { + if let Some(peer_as) = info.remote_asn { + self.enforce_first_as(update, peer_as)?; + } + } + Ok(()) } fn apply_update_policy(&self, update: &mut UpdateMessage) { @@ -1655,6 +1667,7 @@ impl SessionRunner { }; for segment in path { if segment.value.contains(&asn) { + wrn!(self; "self in AS path: {:?}", update); return Err(Error::SelfLoopDetected); } } @@ -1662,6 +1675,27 @@ impl SessionRunner { Ok(()) } + fn enforce_first_as( + &self, + update: &UpdateMessage, + peer_as: u32, + ) -> Result<(), Error> { + let path = match update.as_path() { + Some(path) => path, + None => return Err(Error::MissingAsPath), + }; + let path: Vec = path.into_iter().flat_map(|x| x.value).collect(); + if path.is_empty() { + return Err(Error::EmptyAsPath); + } + + if path[0] != peer_as { + return Err(Error::EnforceAsFirst(peer_as, path)); + } + + Ok(()) + } + // NOTE: for now we are only acting as an edge router. This means we // do not redistribute announcements. So for now this function // is unused. However, this may change in the future. diff --git a/mgadm/src/bgp.rs b/mgadm/src/bgp.rs index 6db9965e..68389220 100644 --- a/mgadm/src/bgp.rs +++ b/mgadm/src/bgp.rs @@ -185,6 +185,11 @@ pub struct Neighbor { /// Local preference to send to iBGP peers. #[arg(long)] pub local_pref: Option, + + /// Ensure that routes received from eBGP peers have the peer's ASN as the + /// first element in the AS path. + #[arg(long)] + pub enforce_first_as: bool, } impl From for types::AddNeighborRequest { @@ -209,6 +214,7 @@ impl From for types::AddNeighborRequest { multi_exit_discriminator: n.med, communities: n.communities, local_pref: n.local_pref, + enforce_first_as: n.enforce_first_as, } } } diff --git a/mgd/src/bgp_admin.rs b/mgd/src/bgp_admin.rs index d199c59c..1730560f 100644 --- a/mgd/src/bgp_admin.rs +++ b/mgd/src/bgp_admin.rs @@ -540,6 +540,7 @@ pub(crate) mod helpers { multi_exit_discriminator: rq.multi_exit_discriminator, communities: rq.communities.clone(), local_pref: rq.local_pref, + enforce_first_as: rq.enforce_first_as, ..Default::default() }; @@ -583,6 +584,7 @@ pub(crate) mod helpers { multi_exit_discriminator: rq.multi_exit_discriminator, communities: rq.communities, local_pref: rq.local_pref, + enforce_first_as: rq.enforce_first_as, })?; start_bgp_session(&event_tx)?; diff --git a/mgd/src/bgp_param.rs b/mgd/src/bgp_param.rs index 481e4933..ff5ac27b 100644 --- a/mgd/src/bgp_param.rs +++ b/mgd/src/bgp_param.rs @@ -57,6 +57,7 @@ pub struct AddNeighborRequest { pub multi_exit_discriminator: Option, pub communities: Vec, pub local_pref: Option, + pub enforce_first_as: bool, } impl From for PeerConfig { @@ -98,6 +99,7 @@ impl AddNeighborRequest { multi_exit_discriminator: rq.multi_exit_discriminator, communities: rq.communities, local_pref: rq.local_pref, + enforce_first_as: rq.enforce_first_as, } } } @@ -247,6 +249,7 @@ pub struct BgpPeerConfig { pub multi_exit_discriminator: Option, pub communities: Vec, pub local_pref: Option, + pub enforce_first_as: bool, } #[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)] diff --git a/mgd/src/main.rs b/mgd/src/main.rs index 4f856446..bb3d64fe 100644 --- a/mgd/src/main.rs +++ b/mgd/src/main.rs @@ -252,6 +252,7 @@ fn start_bgp_routers( multi_exit_discriminator: nbr.multi_exit_discriminator, communities: nbr.communities.clone(), local_pref: nbr.local_pref, + enforce_first_as: nbr.enforce_first_as, }, true, ) diff --git a/openapi/mg-admin.json b/openapi/mg-admin.json index 90d4c6f4..512ae6f9 100644 --- a/openapi/mg-admin.json +++ b/openapi/mg-admin.json @@ -642,6 +642,9 @@ "format": "uint64", "minimum": 0 }, + "enforce_first_as": { + "type": "boolean" + }, "group": { "type": "string" }, @@ -712,6 +715,7 @@ "communities", "connect_retry", "delay_open", + "enforce_first_as", "group", "hold_time", "host", @@ -994,6 +998,9 @@ "format": "uint64", "minimum": 0 }, + "enforce_first_as": { + "type": "boolean" + }, "hold_time": { "type": "integer", "format": "uint64", @@ -1060,6 +1067,7 @@ "communities", "connect_retry", "delay_open", + "enforce_first_as", "hold_time", "host", "idle_hold_time", @@ -2702,6 +2710,10 @@ "description": "Delay sending out the initial open message.", "type": "boolean" }, + "enforce_first_as": { + "description": "Ensure that routes received from eBGP peers have the peer's ASN as the first element in the AS path.", + "type": "boolean" + }, "local_pref": { "nullable": true, "description": "Local preference attribute added to updates if this is an iBGP session", @@ -2763,6 +2775,7 @@ "connect_retry_counter", "damp_peer_oscillations", "delay_open", + "enforce_first_as", "passive_tcp_establishment", "send_notification_without_open", "track_tcp_state" diff --git a/rdb/src/types.rs b/rdb/src/types.rs index f11a1322..c8eec4a0 100644 --- a/rdb/src/types.rs +++ b/rdb/src/types.rs @@ -394,6 +394,7 @@ pub struct BgpNeighborInfo { pub multi_exit_discriminator: Option, pub communities: Vec, pub local_pref: Option, + pub enforce_first_as: bool, } #[derive(Debug, Copy, Clone, Deserialize, Serialize, JsonSchema)]