Skip to content

Commit

Permalink
Add support for BGP neighbor reset
Browse files Browse the repository at this point in the history
Adds mgd API and mgadm CLI for BGP neighbor reset:
 - Hard (full reset of session)
 - SoftInbound (send Route Refresh to peer)
 - SoftOutbound (re-send RIB to peer)

Fixes: #331

Signed-off-by: Trey Aspelund <trey@oxidecomputer.com>
  • Loading branch information
taspelund committed Oct 26, 2024
1 parent 985a9fb commit a7b316b
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 10 deletions.
22 changes: 13 additions & 9 deletions bgp/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1992,14 +1992,7 @@ impl<Cnx: BgpConnection + 'static> SessionRunner<Cnx> {
// self.fanout_update(&update);
}

fn handle_refresh(
&self,
msg: RouteRefreshMessage,
pc: &PeerConnection<Cnx>,
) -> Result<(), Error> {
if msg.afi != Afi::Ipv4 as u16 {
return Ok(());
}
pub fn refresh_react(&self, conn: &Cnx) -> Result<(), Error> {
let originated = match self.db.get_origin4() {
Ok(value) => value,
Err(e) => {
Expand All @@ -2017,11 +2010,22 @@ impl<Cnx: BgpConnection + 'static> SessionRunner<Cnx> {
update.nlri.push(p.into());
}
read_lock!(self.fanout).send_all(&update);
self.send_update(update, &pc.conn, ShaperApplication::Current)?;
self.send_update(update, conn, ShaperApplication::Current)?;
}
Ok(())
}

fn handle_refresh(
&self,
msg: RouteRefreshMessage,
pc: &PeerConnection<Cnx>,
) -> Result<(), Error> {
if msg.afi != Afi::Ipv4 as u16 {
return Ok(());
}
self.refresh_react(&pc.conn)
}

/// Update this router's RIB based on an update message from a peer.
fn update_rib(&self, update: &UpdateMessage, id: u32, peer_as: u32) {
self.db.remove_bgp_prefixes(
Expand Down
43 changes: 42 additions & 1 deletion mgadm/src/bgp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
use anyhow::Result;
use clap::{Args, Subcommand};
use colored::*;
use mg_admin_client::types::{self, Path};
use mg_admin_client::types::{
self, NeighborResetOp, NeighborResetRequest, Path,
};
use mg_admin_client::types::{ImportExportPolicy, Rib};
use mg_admin_client::Client;
use rdb::types::{PolicyAction, Prefix4};
Expand All @@ -24,6 +26,9 @@ pub enum Commands {
/// View dynamic router state.
Status(StatusSubcommand),

/// Clear dynamic router state.
Clear(ClearSubcommand),

/// Omicron control plane commands.
Omicron(OmicronSubcommand),
}
Expand Down Expand Up @@ -82,6 +87,23 @@ pub enum StatusCmd {
},
}

#[derive(Debug, Args)]
pub struct ClearSubcommand {
#[command(subcommand)]
command: ClearCmd,
}

#[derive(Clone, Subcommand, Debug)]
pub enum ClearCmd {
Neighbor {
#[clap(env)]
asn: u32,
addr: IpAddr,
#[clap(value_enum)]
clear_type: NeighborResetOp,
},
}

#[derive(Debug, Args)]
pub struct OmicronSubcommand {
#[command(subcommand)]
Expand Down Expand Up @@ -546,6 +568,14 @@ pub async fn commands(command: Commands, c: Client) -> Result<()> {
StatusCmd::Selected { asn } => get_selected(c, asn).await,
},

Commands::Clear(cmd) => match cmd.command {
ClearCmd::Neighbor {
asn,
addr,
clear_type,
} => clear_nbr(asn, addr, clear_type, c).await,
},

Commands::Omicron(cmd) => match cmd.command {
OmicronCmd::Apply { filename } => apply(filename, c).await,
},
Expand Down Expand Up @@ -685,6 +715,17 @@ async fn delete_nbr(asn: u32, addr: IpAddr, c: Client) {
c.delete_neighbor(&addr, asn).await.unwrap();
}

async fn clear_nbr(
asn: u32,
addr: IpAddr,
op: types::NeighborResetOp,
c: Client,
) {
c.clear_neighbor(&NeighborResetRequest { asn, addr, op })
.await
.unwrap();
}

async fn create_origin4(originate: Originate4, c: Client) {
c.create_origin4(&types::Origin4 {
asn: originate.asn,
Expand Down
58 changes: 58 additions & 0 deletions mgd/src/bgp_admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use bgp::{
config::RouterConfig,
connection::BgpConnection,
connection_tcp::BgpConnectionTcp,
messages::{Afi, Message, RouteRefreshMessage, Safi},
router::Router,
session::{FsmEvent, SessionInfo},
BGP_PORT,
Expand Down Expand Up @@ -80,6 +81,7 @@ pub(crate) fn api_description(api: &mut ApiDescription<Arc<HandlerContext>>) {
register!(api, read_neighbor);
register!(api, update_neighbor);
register!(api, delete_neighbor);
register!(api, clear_neighbor);

// Origin configuration
register!(api, create_origin4);
Expand Down Expand Up @@ -281,6 +283,16 @@ pub async fn delete_neighbor(
Ok(helpers::remove_neighbor(ctx.clone(), rq.asn, rq.addr).await?)
}

#[endpoint { method = POST, path = "/bgp/clear/neighbor" }]
pub async fn clear_neighbor(
ctx: RequestContext<Arc<HandlerContext>>,
request: TypedBody<NeighborResetRequest>,
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
let rq = request.into_inner();
let ctx = ctx.context();
Ok(helpers::reset_neighbor(ctx.clone(), rq.asn, rq.addr, rq.op).await?)
}

#[endpoint { method = PUT, path = "/bgp/config/origin4" }]
pub async fn create_origin4(
ctx: RequestContext<Arc<HandlerContext>>,
Expand Down Expand Up @@ -874,6 +886,52 @@ pub(crate) mod helpers {
Ok(())
}

pub(crate) async fn reset_neighbor(
ctx: Arc<HandlerContext>,
asn: u32,
addr: IpAddr,
op: resource::NeighborResetOp,
) -> Result<HttpResponseUpdatedNoContent, Error> {
info!(ctx.log, "clear neighbor: {}", addr);

let session = get_router!(ctx, asn)?
.get_session(addr)
.ok_or(Error::NotFound("session for bgp peer not found".into()))?;

match op {
resource::NeighborResetOp::Hard => {
session.event_tx.send(FsmEvent::Reset).map_err(|e| {
Error::InternalCommunication(format!(
"failed to reset bgp session {e}",
))
})?
}
resource::NeighborResetOp::SoftInbound => session
.event_tx
.send(FsmEvent::Message(Message::RouteRefresh(
RouteRefreshMessage {
afi: Afi::Ipv4 as u16,
safi: Safi::NlriUnicast as u8,
},
)))
.map_err(|e| {
Error::InternalCommunication(format!(
"failed to trigger outbound update {e}"
))
})?,
resource::NeighborResetOp::SoftOutbound => session
.event_tx
.send(FsmEvent::RouteRefreshNeeded)
.map_err(|e| {
Error::InternalCommunication(format!(
"failed to generate route refresh {e}"
))
})?,
}

Ok(HttpResponseUpdatedNoContent())
}

pub(crate) fn add_router(
ctx: Arc<HandlerContext>,
rq: resource::Router,
Expand Down
14 changes: 14 additions & 0 deletions mgd/src/bgp_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ pub struct NeighborSelector {
pub addr: IpAddr,
}

#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
pub enum NeighborResetOp {
Hard,
SoftInbound,
SoftOutbound,
}

#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
pub struct NeighborResetRequest {
pub asn: u32,
pub addr: IpAddr,
pub op: NeighborResetOp,
}

#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq)]
pub struct Neighbor {
pub asn: u32,
Expand Down
56 changes: 56 additions & 0 deletions openapi/mg-admin.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,32 @@
}
}
},
"/bgp/clear/neighbor": {
"post": {
"operationId": "clear_neighbor",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NeighborResetRequest"
}
}
},
"required": true
},
"responses": {
"204": {
"description": "resource updated"
},
"4XX": {
"$ref": "#/components/responses/Error"
},
"5XX": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/bgp/config/checker": {
"get": {
"operationId": "read_checker",
Expand Down Expand Up @@ -2431,6 +2457,36 @@
"resolution"
]
},
"NeighborResetOp": {
"type": "string",
"enum": [
"Hard",
"SoftInbound",
"SoftOutbound"
]
},
"NeighborResetRequest": {
"type": "object",
"properties": {
"addr": {
"type": "string",
"format": "ip"
},
"asn": {
"type": "integer",
"format": "uint32",
"minimum": 0
},
"op": {
"$ref": "#/components/schemas/NeighborResetOp"
}
},
"required": [
"addr",
"asn",
"op"
]
},
"NotificationMessage": {
"description": "Notification messages are exchanged between BGP peers when an exceptional event has occurred.",
"type": "object",
Expand Down

0 comments on commit a7b316b

Please sign in to comment.