Skip to content

Commit 4a5a0d4

Browse files
committed
bgp unnumbered plumbing
1 parent 409e7b4 commit 4a5a0d4

File tree

32 files changed

+30951
-439
lines changed

32 files changed

+30951
-439
lines changed

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -566,8 +566,8 @@ ntp-admin-api = { path = "ntp-admin/api" }
566566
ntp-admin-client = { path = "clients/ntp-admin-client" }
567567
ntp-admin-types = { path = "ntp-admin/types" }
568568
ntp-admin-types-versions = { path = "ntp-admin/types/versions" }
569-
mg-admin-client = { git = "https://github.com/oxidecomputer/maghemite", rev = "7f78e2b9ab37981e9edcf2e076a3257a032bbb06" }
570-
ddm-admin-client = { git = "https://github.com/oxidecomputer/maghemite", rev = "7f78e2b9ab37981e9edcf2e076a3257a032bbb06" }
569+
mg-admin-client = { git = "https://github.com/oxidecomputer/maghemite", rev = "f1ea4650fc95a95bbcd1a64d19a45c40e98286f7" }
570+
ddm-admin-client = { git = "https://github.com/oxidecomputer/maghemite", rev = "f1ea4650fc95a95bbcd1a64d19a45c40e98286f7" }
571571
multimap = "0.10.1"
572572
nexus-auth = { path = "nexus/auth" }
573573
nexus-background-task-interface = { path = "nexus/background-task-interface" }
@@ -697,7 +697,7 @@ rats-corim = { git = "https://github.com/oxidecomputer/rats-corim.git", rev = "f
697697
raw-cpuid = { git = "https://github.com/oxidecomputer/rust-cpuid.git", rev = "a4cf01df76f35430ff5d39dc2fe470bcb953503b" }
698698
rayon = "1.10"
699699
rcgen = "0.12.1"
700-
rdb-types = { git = "https://github.com/oxidecomputer/maghemite", rev = "7f78e2b9ab37981e9edcf2e076a3257a032bbb06" }
700+
rdb-types = { git = "https://github.com/oxidecomputer/maghemite", rev = "f1ea4650fc95a95bbcd1a64d19a45c40e98286f7" }
701701
reconfigurator-cli = { path = "dev-tools/reconfigurator-cli" }
702702
reedline = "0.40.0"
703703
ref-cast = "1.0"

common/src/api/external/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3244,8 +3244,10 @@ pub struct BgpPeer {
32443244
/// could be vlan47 to refer to a VLAN interface.
32453245
pub interface_name: Name,
32463246

3247-
/// The address of the host to peer with.
3248-
pub addr: IpAddr,
3247+
/// The address of the host to peer with. If not provided, this is an
3248+
/// unnumbered BGP session that will be established over the interface
3249+
/// specified by `interface_name`.
3250+
pub addr: Option<IpAddr>,
32493251

32503252
/// How long to hold peer connections between keepalives (seconds).
32513253
pub hold_time: u32,

common/src/api/internal/shared/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ pub struct BgpPeerConfig {
9191
pub asn: u32,
9292
/// Switch port the peer is reachable on.
9393
pub port: String,
94-
/// Address of the peer.
94+
/// Address of the peer. Use `Ipv4Addr::UNSPECIFIED` to indicate an
95+
/// unnumbered BGP session established over the interface specified by
96+
/// `port`.
9597
pub addr: Ipv4Addr,
9698
/// How long to keep a session alive without a keepalive in seconds.
9799
/// Defaults to 6.

dev-tools/ls-apis/tests/api_dependencies.out

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Downstairs Controller (debugging only) (client: dsc-client)
4646

4747
Management Gateway Service (client: gateway-client)
4848
consumed by: dpd (dendrite/dpd) via 1 path
49+
consumed by: mgd (maghemite/mgd) via 1 path
4950
consumed by: omicron-nexus (omicron/nexus) via 4 paths
5051
consumed by: omicron-sled-agent (omicron/sled-agent) via 1 path
5152
consumed by: wicketd (omicron/wicketd) via 3 paths

nexus/db-model/src/bgp.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ impl Into<external::BgpAnnouncement> for BgpAnnouncement {
128128
pub struct BgpPeerView {
129129
pub switch_location: String,
130130
pub port_name: String,
131-
pub addr: IpNetwork,
131+
pub addr: Option<IpNetwork>,
132132
pub asn: SqlU32,
133133
pub connect_retry: SqlU32,
134134
pub delay_open: SqlU32,

nexus/db-model/src/schema_versions.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use std::{collections::BTreeMap, sync::LazyLock};
1616
///
1717
/// This must be updated when you change the database schema. Refer to
1818
/// schema/crdb/README.adoc in the root of this repository for details.
19-
pub const SCHEMA_VERSION: Version = Version::new(217, 0, 0);
19+
pub const SCHEMA_VERSION: Version = Version::new(218, 0, 0);
2020

2121
/// List of all past database schema versions, in *reverse* order
2222
///
@@ -28,6 +28,7 @@ static KNOWN_VERSIONS: LazyLock<Vec<KnownVersion>> = LazyLock::new(|| {
2828
// | leaving the first copy as an example for the next person.
2929
// v
3030
// KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"),
31+
KnownVersion::new(218, "bgp-unnumbered-peers"),
3132
KnownVersion::new(217, "multiple-default-ip-pools-per-silo"),
3233
KnownVersion::new(216, "add-trust-quorum"),
3334
KnownVersion::new(215, "support-up-to-12-disks"),

nexus/db-model/src/switch_port.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -650,10 +650,11 @@ impl Into<external::SwitchPortRouteConfig> for SwitchPortRouteConfig {
650650
)]
651651
#[diesel(table_name = switch_port_settings_bgp_peer_config)]
652652
pub struct SwitchPortBgpPeerConfig {
653+
pub id: Uuid,
653654
pub port_settings_id: Uuid,
654655
pub bgp_config_id: Uuid,
655656
pub interface_name: Name,
656-
pub addr: IpNetwork,
657+
pub addr: Option<IpNetwork>,
657658
pub hold_time: SqlU32,
658659
pub idle_hold_time: SqlU32,
659660
pub delay_open: SqlU32,
@@ -740,10 +741,11 @@ impl SwitchPortBgpPeerConfig {
740741
p: &BgpPeer,
741742
) -> Self {
742743
Self {
744+
id: Uuid::new_v4(),
743745
port_settings_id,
744746
bgp_config_id,
745747
interface_name,
746-
addr: p.addr.into(),
748+
addr: p.addr.map(|a| a.into()),
747749
hold_time: p.hold_time.into(),
748750
idle_hold_time: p.idle_hold_time.into(),
749751
delay_open: p.delay_open.into(),

nexus/db-queries/src/db/datastore/bgp.rs

Lines changed: 97 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -828,19 +828,28 @@ impl DataStore {
828828
Ok(results)
829829
}
830830

831+
/// Look up communities for a BGP peer.
832+
///
833+
/// For numbered peers, pass `Some(addr)`. For unnumbered peers, pass `None`
834+
/// (the function will query using the sentinel value 0.0.0.0/32).
831835
pub async fn communities_for_peer(
832836
&self,
833837
opctx: &OpContext,
834838
port_settings_id: Uuid,
835839
interface_name: &str,
836-
addr: IpNetwork,
840+
addr: Option<IpNetwork>,
837841
) -> ListResultVec<SwitchPortBgpPeerConfigCommunity> {
838842
use nexus_db_schema::schema::switch_port_settings_bgp_peer_config_communities::dsl;
839843

844+
// For unnumbered peers (addr is None), use UNSPECIFIED as sentinel
845+
let db_addr: IpNetwork = addr.unwrap_or_else(|| {
846+
std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED).into()
847+
});
848+
840849
let results = dsl::switch_port_settings_bgp_peer_config_communities
841850
.filter(dsl::port_settings_id.eq(port_settings_id))
842851
.filter(dsl::interface_name.eq(interface_name.to_owned()))
843-
.filter(dsl::addr.eq(addr))
852+
.filter(dsl::addr.eq(db_addr))
844853
.load_async(&*self.pool_connection_authorized(opctx).await?)
845854
.await
846855
.map_err(|e| {
@@ -852,43 +861,66 @@ impl DataStore {
852861
Ok(results)
853862
}
854863

864+
/// Look up allowed exports for a BGP peer.
865+
///
866+
/// For numbered peers, pass `Some(addr)`. For unnumbered peers, pass `None`.
855867
pub async fn allow_export_for_peer(
856868
&self,
857869
opctx: &OpContext,
858870
port_settings_id: Uuid,
859871
interface_name: &str,
860-
addr: IpNetwork,
872+
addr: Option<IpNetwork>,
861873
) -> LookupResult<Option<Vec<SwitchPortBgpPeerConfigAllowExport>>> {
862874
use nexus_db_schema::schema::switch_port_settings_bgp_peer_config as db_peer;
863875
use nexus_db_schema::schema::switch_port_settings_bgp_peer_config::dsl as peer_dsl;
864876
use nexus_db_schema::schema::switch_port_settings_bgp_peer_config_allow_export as db_allow;
865877
use nexus_db_schema::schema::switch_port_settings_bgp_peer_config_allow_export::dsl;
866878

879+
// For unnumbered peers (addr is None), use UNSPECIFIED as sentinel
880+
// for the allow_export table (which has non-nullable addr)
881+
let db_addr: IpNetwork = addr.unwrap_or_else(|| {
882+
std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED).into()
883+
});
884+
867885
let conn = self.pool_connection_authorized(opctx).await?;
868886
let err = OptionalError::new();
869887
self.transaction_retry_wrapper("bgp_allow_export_for_peer")
870888
.transaction(&conn, |conn| {
871889
let err = err.clone();
872890
async move {
873-
let active = peer_dsl::switch_port_settings_bgp_peer_config
874-
.filter(db_peer::port_settings_id.eq(port_settings_id))
875-
.filter(db_peer::addr.eq(addr))
876-
.select(db_peer::allow_export_list_active)
877-
.limit(1)
878-
.first_async::<bool>(&conn)
879-
.await
880-
.map_err(|e| {
881-
let msg = "failed to lookup export settings for peer";
882-
error!(opctx.log, "{msg}"; "error" => ?e);
891+
// Query the main peer config table. For unnumbered peers,
892+
// addr is NULL; for numbered peers, addr matches.
893+
let active = if addr.is_some() {
894+
peer_dsl::switch_port_settings_bgp_peer_config
895+
.filter(db_peer::port_settings_id.eq(port_settings_id))
896+
.filter(db_peer::addr.eq(addr))
897+
.select(db_peer::allow_export_list_active)
898+
.limit(1)
899+
.first_async::<bool>(&conn)
900+
.await
901+
} else {
902+
peer_dsl::switch_port_settings_bgp_peer_config
903+
.filter(db_peer::port_settings_id.eq(port_settings_id))
904+
.filter(db_peer::addr.is_null())
905+
.filter(db_peer::interface_name.eq(interface_name.to_owned()))
906+
.select(db_peer::allow_export_list_active)
907+
.limit(1)
908+
.first_async::<bool>(&conn)
909+
.await
910+
};
883911

884-
match e {
885-
diesel::result::Error::NotFound => {
886-
let not_found_msg = format!("peer with {addr} not found for port settings {port_settings_id}");
887-
err.bail(Error::non_resourcetype_not_found(not_found_msg))
888-
},
889-
_ => err.bail(Error::internal_error(msg)),
890-
}
891-
})?;
912+
let active = active.map_err(|e| {
913+
let msg = "failed to lookup export settings for peer";
914+
error!(opctx.log, "{msg}"; "error" => ?e);
915+
916+
match e {
917+
diesel::result::Error::NotFound => {
918+
let not_found_msg = format!("peer with {:?} not found for port settings {port_settings_id}", addr);
919+
err.bail(Error::non_resourcetype_not_found(not_found_msg))
920+
},
921+
_ => err.bail(Error::internal_error(msg)),
922+
}
923+
})?;
892924

893925
if !active {
894926
return Ok(None);
@@ -903,7 +935,7 @@ impl DataStore {
903935
db_allow::interface_name
904936
.eq(interface_name.to_owned()),
905937
)
906-
.filter(db_allow::addr.eq(addr))
938+
.filter(db_allow::addr.eq(db_addr))
907939
.load_async(&conn)
908940
.await?;
909941

@@ -923,44 +955,67 @@ impl DataStore {
923955
})
924956
}
925957

958+
/// Look up allowed imports for a BGP peer.
959+
///
960+
/// For numbered peers, pass `Some(addr)`. For unnumbered peers, pass `None`.
926961
pub async fn allow_import_for_peer(
927962
&self,
928963
opctx: &OpContext,
929964
port_settings_id: Uuid,
930965
interface_name: &str,
931-
addr: IpNetwork,
966+
addr: Option<IpNetwork>,
932967
) -> LookupResult<Option<Vec<SwitchPortBgpPeerConfigAllowImport>>> {
933968
use nexus_db_schema::schema::switch_port_settings_bgp_peer_config as db_peer;
934969
use nexus_db_schema::schema::switch_port_settings_bgp_peer_config::dsl as peer_dsl;
935970
use nexus_db_schema::schema::switch_port_settings_bgp_peer_config_allow_import as db_allow;
936971
use nexus_db_schema::schema::switch_port_settings_bgp_peer_config_allow_import::dsl;
937972

973+
// For unnumbered peers (addr is None), use UNSPECIFIED as sentinel
974+
// for the allow_import table (which has non-nullable addr)
975+
let db_addr: IpNetwork = addr.unwrap_or_else(|| {
976+
std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED).into()
977+
});
978+
938979
let err = OptionalError::new();
939980
let conn = self.pool_connection_authorized(opctx).await?;
940981
self
941982
.transaction_retry_wrapper("bgp_allow_import_for_peer")
942983
.transaction(&conn, |conn| {
943984
let err = err.clone();
944985
async move {
945-
let active = peer_dsl::switch_port_settings_bgp_peer_config
946-
.filter(db_peer::port_settings_id.eq(port_settings_id))
947-
.filter(db_peer::addr.eq(addr))
948-
.select(db_peer::allow_import_list_active)
949-
.limit(1)
950-
.first_async::<bool>(&conn)
951-
.await
952-
.map_err(|e| {
953-
let msg = "failed to lookup import settings for peer";
954-
error!(opctx.log, "{msg}"; "error" => ?e);
986+
// Query the main peer config table. For unnumbered peers,
987+
// addr is NULL; for numbered peers, addr matches.
988+
let active = if addr.is_some() {
989+
peer_dsl::switch_port_settings_bgp_peer_config
990+
.filter(db_peer::port_settings_id.eq(port_settings_id))
991+
.filter(db_peer::addr.eq(addr))
992+
.select(db_peer::allow_import_list_active)
993+
.limit(1)
994+
.first_async::<bool>(&conn)
995+
.await
996+
} else {
997+
peer_dsl::switch_port_settings_bgp_peer_config
998+
.filter(db_peer::port_settings_id.eq(port_settings_id))
999+
.filter(db_peer::addr.is_null())
1000+
.filter(db_peer::interface_name.eq(interface_name.to_owned()))
1001+
.select(db_peer::allow_import_list_active)
1002+
.limit(1)
1003+
.first_async::<bool>(&conn)
1004+
.await
1005+
};
9551006

956-
match e {
957-
diesel::result::Error::NotFound => {
958-
let not_found_msg = format!("peer with {addr} not found for port settings {port_settings_id}");
959-
err.bail(Error::non_resourcetype_not_found(not_found_msg))
960-
},
961-
_ => err.bail(Error::internal_error(msg)),
962-
}
963-
})?;
1007+
let active = active.map_err(|e| {
1008+
let msg = "failed to lookup import settings for peer";
1009+
error!(opctx.log, "{msg}"; "error" => ?e);
1010+
1011+
match e {
1012+
diesel::result::Error::NotFound => {
1013+
let not_found_msg = format!("peer with {:?} not found for port settings {port_settings_id}", addr);
1014+
err.bail(Error::non_resourcetype_not_found(not_found_msg))
1015+
},
1016+
_ => err.bail(Error::internal_error(msg)),
1017+
}
1018+
})?;
9641019

9651020
if !active {
9661021
return Ok(None);
@@ -975,7 +1030,7 @@ impl DataStore {
9751030
db_allow::interface_name
9761031
.eq(interface_name.to_owned()),
9771032
)
978-
.filter(db_allow::addr.eq(addr))
1033+
.filter(db_allow::addr.eq(db_addr))
9791034
.load_async(&conn)
9801035
.await?;
9811036

0 commit comments

Comments
 (0)