Skip to content

Commit

Permalink
add enforce-first-as option
Browse files Browse the repository at this point in the history
- fixes #208
  • Loading branch information
rcgoodfellow committed Apr 26, 2024
1 parent ea4fe41 commit b0af04c
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 1 deletion.
9 changes: 9 additions & 0 deletions bgp/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u32>),

#[error("Invalid address")]
InvalidAddress(String),

Expand Down
36 changes: 35 additions & 1 deletion bgp/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,10 @@ pub struct SessionInfo {

/// Capabilities sent to the peer.
pub capabilities_sent: Vec<Capability>,

/// 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 {
Expand All @@ -387,6 +391,7 @@ impl Default for SessionInfo {
local_pref: None,
capabilities_received: Vec::new(),
capabilities_sent: Vec::new(),
enforce_first_as: false,
}
}
}
Expand Down Expand Up @@ -1628,7 +1633,14 @@ impl<Cnx: BgpConnection + 'static> SessionRunner<Cnx> {

/// 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) {
Expand All @@ -1655,13 +1667,35 @@ impl<Cnx: BgpConnection + 'static> SessionRunner<Cnx> {
};
for segment in path {
if segment.value.contains(&asn) {
wrn!(self; "self in AS path: {:?}", update);
return Err(Error::SelfLoopDetected);
}
}
}
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<u32> = 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.
Expand Down
6 changes: 6 additions & 0 deletions mgadm/src/bgp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ pub struct Neighbor {
/// Local preference to send to iBGP peers.
#[arg(long)]
pub local_pref: Option<u32>,

/// 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<Neighbor> for types::AddNeighborRequest {
Expand All @@ -209,6 +214,7 @@ impl From<Neighbor> for types::AddNeighborRequest {
multi_exit_discriminator: n.med,
communities: n.communities,
local_pref: n.local_pref,
enforce_first_as: n.enforce_first_as,
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions mgd/src/bgp_admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
};

Expand Down Expand Up @@ -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)?;
Expand Down
3 changes: 3 additions & 0 deletions mgd/src/bgp_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pub struct AddNeighborRequest {
pub multi_exit_discriminator: Option<u32>,
pub communities: Vec<u32>,
pub local_pref: Option<u32>,
pub enforce_first_as: bool,
}

impl From<AddNeighborRequest> for PeerConfig {
Expand Down Expand Up @@ -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,
}
}
}
Expand Down Expand Up @@ -247,6 +249,7 @@ pub struct BgpPeerConfig {
pub multi_exit_discriminator: Option<u32>,
pub communities: Vec<u32>,
pub local_pref: Option<u32>,
pub enforce_first_as: bool,
}

#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
Expand Down
1 change: 1 addition & 0 deletions mgd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down
13 changes: 13 additions & 0 deletions openapi/mg-admin.json
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,9 @@
"format": "uint64",
"minimum": 0
},
"enforce_first_as": {
"type": "boolean"
},
"group": {
"type": "string"
},
Expand Down Expand Up @@ -712,6 +715,7 @@
"communities",
"connect_retry",
"delay_open",
"enforce_first_as",
"group",
"hold_time",
"host",
Expand Down Expand Up @@ -994,6 +998,9 @@
"format": "uint64",
"minimum": 0
},
"enforce_first_as": {
"type": "boolean"
},
"hold_time": {
"type": "integer",
"format": "uint64",
Expand Down Expand Up @@ -1060,6 +1067,7 @@
"communities",
"connect_retry",
"delay_open",
"enforce_first_as",
"hold_time",
"host",
"idle_hold_time",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions rdb/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ pub struct BgpNeighborInfo {
pub multi_exit_discriminator: Option<u32>,
pub communities: Vec<u32>,
pub local_pref: Option<u32>,
pub enforce_first_as: bool,
}

#[derive(Debug, Copy, Clone, Deserialize, Serialize, JsonSchema)]
Expand Down

0 comments on commit b0af04c

Please sign in to comment.