Skip to content

Commit fedce01

Browse files
committed
Merge branch 'main' into trey/unnumbered-cleanup
Signed-off-by: Trey Aspelund <trey@oxidecomputer.com>
2 parents 74d89aa + 3e66478 commit fedce01

20 files changed

Lines changed: 1657 additions & 450 deletions

File tree

.github/buildomat/jobs/falcon-lab.sh

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ mkdir -p cargo-bay
5454
mv mgd cargo-bay/
5555
mv ddmd cargo-bay/
5656

57+
# Juniper/cRPD images require a runtime license. Fetch it on the CI runner,
58+
# which has catacomb access, and pass it to the guest by file via cargo-bay.
59+
# The license contents must never be printed or committed.
60+
curl -sSfL --retry 10 --retry-all-errors \
61+
-o cargo-bay/falcon-juniper-license.key \
62+
http://catacomb.eng.oxide.computer:12346/falcon/jl
63+
chmod 0600 cargo-bay/falcon-juniper-license.key
64+
5765
export EXT_INTERFACE=${EXT_INTERFACE:-igb0}
5866

5967
first=$(bmat address ls -f extra -Ho first)
@@ -62,8 +70,15 @@ gw=$(bmat address ls -f extra -Ho gateway)
6270
server=$(ipadm show-addr "${EXT_INTERFACE}"/dhcp -po ADDR | sed 's#/.*##g')
6371
pfexec ./dhcp-server "${first}" "${last}" "${gw}" "${server}" &> /work/dhcp-server.log &
6472

65-
RUST_LOG=debug pfexec ./falcon-lab run \
66-
trio-unnumbered
73+
run_test() {
74+
local test=$1
75+
local status=0
76+
77+
RUST_LOG=debug pfexec ./falcon-lab run "${test}" || status=$?
78+
pfexec ./falcon-lab cleanup "${test}" || true
79+
return "${status}"
80+
}
6781

68-
RUST_LOG=debug pfexec ./falcon-lab run \
69-
trio-bfd-static-routing
82+
run_test mgd-unnumbered
83+
run_test quartet-unnumbered
84+
run_test quartet-bfd-static-routing

bgp/src/config.rs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,23 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5+
use crate::BGP_PORT;
56
use mg_api_types::bgp::config::{
67
BgpPeerParameters, Neighbor, UnnumberedNeighbor,
78
};
9+
use mg_api_types::bgp::peer::PeerId;
810
use mg_api_types_versions::v1;
911
use rdb::Asn;
1012
use schemars::JsonSchema;
1113
use serde::{Deserialize, Serialize};
12-
use std::net::{SocketAddr, SocketAddrV6};
14+
use std::num::NonZeroU16;
1315

1416
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
1517
pub struct PeerConfig {
1618
pub name: String,
1719
pub group: String,
18-
pub host: SocketAddr,
20+
pub id: PeerId,
21+
pub port: NonZeroU16,
1922
pub hold_time: u64,
2023
pub idle_hold_time: u64,
2124
pub delay_open: u64,
@@ -77,7 +80,8 @@ impl From<Neighbor> for PeerConfig {
7780
Self {
7881
name,
7982
group,
80-
host: *host,
83+
id: PeerId::Ip(host.ip()),
84+
port: NonZeroU16::new(host.port()).unwrap_or(BGP_PORT),
8185
hold_time,
8286
idle_hold_time,
8387
delay_open,
@@ -119,7 +123,8 @@ impl From<v1::bgp::config::Neighbor> for PeerConfig {
119123
Self {
120124
name,
121125
group,
122-
host,
126+
id: PeerId::Ip(host.ip()),
127+
port: NonZeroU16::new(host.port()).unwrap_or(BGP_PORT),
123128
hold_time,
124129
idle_hold_time,
125130
delay_open,
@@ -131,17 +136,15 @@ impl From<v1::bgp::config::Neighbor> for PeerConfig {
131136
}
132137

133138
impl PeerConfig {
134-
/// Construct a `PeerConfig` from an `UnnumberedNeighbor` (uses the supplied
135-
/// IPv6 link-local socket address as the connection target).
136-
pub fn from_unnumbered_neighbor(
137-
n: &UnnumberedNeighbor,
138-
addr: SocketAddrV6,
139-
) -> Self {
139+
/// Construct a `PeerConfig` from an `UnnumberedNeighbor`. The peer is
140+
/// identified by its interface; the connection target is resolved via NDP
141+
/// at connect time, and the port is always the well-known BGP port.
142+
pub fn from_unnumbered_neighbor(n: &UnnumberedNeighbor) -> Self {
140143
let UnnumberedNeighbor {
141144
asn: _,
142145
name,
143146
group,
144-
interface: _,
147+
interface,
145148
act_as_a_default_ipv6_router: _,
146149
parameters,
147150
} = n.clone();
@@ -171,8 +174,9 @@ impl PeerConfig {
171174
} = parameters;
172175
Self {
173176
name,
174-
host: addr.into(),
175177
group,
178+
id: PeerId::Interface(interface),
179+
port: BGP_PORT,
176180
hold_time,
177181
idle_hold_time,
178182
delay_open,

bgp/src/connection_channel.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ impl BgpListener<BgpConnectionChannel> for BgpListenerChannel {
237237
let runner = lock!(sessions)
238238
.get(&key)
239239
.cloned()
240-
.ok_or(Error::UnknownPeer(peer.ip()))?;
240+
.ok_or_else(|| Error::UnknownPeer(key.clone()))?;
241241

242242
let (connection, min_ttl, md5_key) = {
243243
let config = lock!(runner.session);

bgp/src/connection_tcp.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use std::{
2929
io::Read,
3030
io::Write,
3131
net::{SocketAddr, TcpListener, TcpStream, ToSocketAddrs},
32+
num::NonZeroU32,
3233
sync::atomic::AtomicBool,
3334
sync::{Arc, Mutex, atomic::Ordering, mpsc::Sender},
3435
thread::{JoinHandle, sleep},
@@ -156,7 +157,7 @@ impl BgpListener<BgpConnectionTcp> for BgpListenerTcp {
156157
let runner = lock!(sessions)
157158
.get(&key)
158159
.cloned()
159-
.ok_or(Error::UnknownPeer(ip))?;
160+
.ok_or_else(|| Error::UnknownPeer(key.clone()))?;
160161

161162
let (connection, min_ttl, md5_key) = {
162163
let config = lock!(runner.session);
@@ -315,6 +316,22 @@ impl BgpConnector<BgpConnectionTcp> for BgpConnectorTcp {
315316
"timeout" => timeout.as_millis()
316317
);
317318

319+
// Bind the socket to the destination's interface for scoped
320+
// (link-local / unnumbered) peers.
321+
if let SocketAddr::V6(v6) = peer
322+
&& let Some(idx) = NonZeroU32::new(v6.scope_id())
323+
&& let Err(e) = s.bind_device_by_index_v6(Some(idx))
324+
{
325+
connection_log_lite!(log,
326+
warn,
327+
"failed to bind device index {idx} for {peer}: {e}";
328+
"direction" => ConnectionDirection::Outbound,
329+
"peer" => format!("{peer}"),
330+
"error" => format!("{e}")
331+
);
332+
return;
333+
}
334+
318335
// Bind to source address/port if specified
319336
if let Some(src) = config.bind_addr {
320337
let ba: socket2::SockAddr = src.into();
@@ -1381,7 +1398,7 @@ fn get_md5_source_addrs(peer_ip: IpAddr) -> Result<Vec<SocketAddr>, Error> {
13811398

13821399
Ok(sources
13831400
.iter()
1384-
.map(|x| SocketAddr::new(*x, crate::BGP_PORT))
1401+
.map(|x| SocketAddr::new(*x, crate::BGP_PORT.get()))
13851402
.collect())
13861403
}
13871404

@@ -1519,7 +1536,7 @@ fn setup_outbound_md5(
15191536

15201537
let local: Vec<SocketAddr> = sources
15211538
.iter()
1522-
.map(|x| SocketAddr::new(*x, crate::BGP_PORT))
1539+
.map(|x| SocketAddr::new(*x, crate::BGP_PORT.get()))
15231540
.collect();
15241541

15251542
init_md5_associations(fd, key, local.clone(), peer)?;

bgp/src/error.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::{
77
net::{IpAddr, Ipv4Addr},
88
};
99

10+
use mg_api_types::bgp::peer::PeerId;
1011
use num_enum::TryFromPrimitiveError;
1112

1213
#[derive(thiserror::Error, Debug)]
@@ -122,7 +123,7 @@ pub enum Error {
122123
NotConnected,
123124

124125
#[error("Connection attempt from unknown peer: {0}")]
125-
UnknownPeer(IpAddr),
126+
UnknownPeer(PeerId),
126127

127128
#[error("Session for peer already exists")]
128129
PeerExists,

bgp/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ pub mod policy;
1515
pub mod router;
1616
pub mod session;
1717

18+
use std::num::NonZeroU16;
19+
1820
mod rhai_integration;
1921

2022
#[cfg(test)]
@@ -30,7 +32,7 @@ mod test;
3032
#[cfg(test)]
3133
pub mod connection_channel;
3234

33-
pub const BGP_PORT: u16 = 179;
35+
pub const BGP_PORT: NonZeroU16 = NonZeroU16::new(179).unwrap();
3436
pub const BGP_VERSION: u8 = 4;
3537
pub const COMPONENT_BGP: &str = "bgp";
3638
pub const MOD_ROUTER: &str = "router";

bgp/src/router.rs

Lines changed: 12 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -305,104 +305,57 @@ impl<Cnx: BgpConnection + 'static> Router<Cnx> {
305305
}
306306
}
307307

308+
#[allow(clippy::too_many_arguments)]
308309
pub fn ensure_session(
309310
self: &Arc<Self>,
310311
peer: PeerConfig,
311312
bind_addr: Option<SocketAddr>,
312313
event_tx: Sender<FsmEvent<Cnx>>,
313314
event_rx: Receiver<FsmEvent<Cnx>>,
314315
info: SessionInfo,
316+
unnumbered_manager: Option<Arc<dyn BgpUnnumbered>>,
315317
) -> Result<EnsureSessionResult<Cnx>, Error> {
316318
let sessions = lock!(self.sessions);
317-
let key = PeerId::Ip(peer.host.ip());
318-
if sessions.contains_key(&key) {
319+
if sessions.contains_key(&peer.id) {
319320
drop(sessions);
320321
Ok(EnsureSessionResult::Updated(
321322
self.update_session(peer, info)?,
322323
))
323-
} else {
324-
Ok(EnsureSessionResult::New(self.new_session_locked(
325-
sessions, key, peer, bind_addr, event_tx, event_rx, info, None,
326-
)?))
327-
}
328-
}
329-
330-
#[allow(clippy::too_many_arguments)]
331-
pub fn ensure_unnumbered_session(
332-
self: &Arc<Self>,
333-
interface: String,
334-
peer: PeerConfig,
335-
bind_addr: Option<SocketAddr>,
336-
event_tx: Sender<FsmEvent<Cnx>>,
337-
event_rx: Receiver<FsmEvent<Cnx>>,
338-
info: SessionInfo,
339-
unnumbered_manager: Arc<dyn BgpUnnumbered>,
340-
) -> Result<EnsureSessionResult<Cnx>, Error> {
341-
let sessions = lock!(self.sessions);
342-
let key = PeerId::Interface(interface.clone());
343-
if sessions.contains_key(&key) {
344-
drop(sessions);
345-
Ok(EnsureSessionResult::Updated(
346-
self.update_unnumbered_session(&interface, peer, info)?,
347-
))
348324
} else {
349325
Ok(EnsureSessionResult::New(self.new_session_locked(
350326
sessions,
351-
key,
352327
peer,
353328
bind_addr,
354329
event_tx,
355330
event_rx,
356331
info,
357-
Some(unnumbered_manager),
332+
unnumbered_manager,
358333
)?))
359334
}
360335
}
361336

362-
pub fn new_session(
363-
self: &Arc<Self>,
364-
peer: PeerConfig,
365-
bind_addr: Option<SocketAddr>,
366-
event_tx: Sender<FsmEvent<Cnx>>,
367-
event_rx: Receiver<FsmEvent<Cnx>>,
368-
info: SessionInfo,
369-
) -> Result<Arc<SessionRunner<Cnx>>, Error> {
370-
let sessions = lock!(self.sessions);
371-
let key = PeerId::Ip(peer.host.ip());
372-
if sessions.contains_key(&key) {
373-
Err(Error::PeerExists)
374-
} else {
375-
self.new_session_locked(
376-
sessions, key, peer, bind_addr, event_tx, event_rx, info, None,
377-
)
378-
}
379-
}
380-
381337
#[allow(clippy::too_many_arguments)]
382-
pub fn new_unnumbered_session(
338+
pub fn new_session(
383339
self: &Arc<Self>,
384-
interface: String,
385340
peer: PeerConfig,
386341
bind_addr: Option<SocketAddr>,
387342
event_tx: Sender<FsmEvent<Cnx>>,
388343
event_rx: Receiver<FsmEvent<Cnx>>,
389344
info: SessionInfo,
390-
unnumbered_manager: Arc<dyn BgpUnnumbered>,
345+
unnumbered_manager: Option<Arc<dyn BgpUnnumbered>>,
391346
) -> Result<Arc<SessionRunner<Cnx>>, Error> {
392347
let sessions = lock!(self.sessions);
393-
let key = PeerId::Interface(interface);
394-
if sessions.contains_key(&key) {
348+
if sessions.contains_key(&peer.id) {
395349
Err(Error::PeerExists)
396350
} else {
397351
self.new_session_locked(
398352
sessions,
399-
key,
400353
peer,
401354
bind_addr,
402355
event_tx,
403356
event_rx,
404357
info,
405-
Some(unnumbered_manager),
358+
unnumbered_manager,
406359
)
407360
}
408361
}
@@ -411,7 +364,6 @@ impl<Cnx: BgpConnection + 'static> Router<Cnx> {
411364
fn new_session_locked(
412365
self: &Arc<Self>,
413366
mut sessions: MutexGuard<SessionMap<Cnx>>,
414-
peer_id: PeerId,
415367
peer: PeerConfig,
416368
bind_addr: Option<SocketAddr>,
417369
event_tx: Sender<FsmEvent<Cnx>>,
@@ -434,8 +386,8 @@ impl<Cnx: BgpConnection + 'static> Router<Cnx> {
434386
let neighbor = NeighborInfo {
435387
name: Arc::new(Mutex::new(peer.name.clone())),
436388
peer_group: peer.group.clone(),
437-
peer: peer_id,
438-
port: peer.host.port(),
389+
peer: peer.id.clone(),
390+
port: peer.port,
439391
};
440392

441393
let runner = Arc::new(SessionRunner::new(
@@ -460,33 +412,9 @@ impl<Cnx: BgpConnection + 'static> Router<Cnx> {
460412
peer: PeerConfig,
461413
info: SessionInfo,
462414
) -> Result<Arc<SessionRunner<Cnx>>, Error> {
463-
// Use PeerId::Ip for numbered sessions
464-
let key = PeerId::Ip(peer.host.ip());
465-
let session = match lock!(self.sessions).get(&key) {
466-
None => return Err(Error::UnknownPeer(peer.host.ip())),
467-
Some(s) => s.clone(),
468-
};
469-
470-
session.update_session_parameters(peer, info)?;
471-
472-
Ok(session)
473-
}
474-
475-
pub fn update_unnumbered_session(
476-
self: &Arc<Self>,
477-
interface: &str,
478-
peer: PeerConfig,
479-
info: SessionInfo,
480-
) -> Result<Arc<SessionRunner<Cnx>>, Error> {
481-
// Use PeerId::Interface for unnumbered sessions
482-
let key = PeerId::Interface(interface.to_string());
415+
let key = peer.id.clone();
483416
let session = match lock!(self.sessions).get(&key) {
484-
None => {
485-
return Err(Error::InternalCommunication(format!(
486-
"unnumbered session not found for interface: {}",
487-
interface
488-
)));
489-
}
417+
None => return Err(Error::UnknownPeer(key)),
490418
Some(s) => s.clone(),
491419
};
492420

0 commit comments

Comments
 (0)