Skip to content

Commit d986afd

Browse files
committed
bgp: unify numbered and unnumbered peer config
Signed-off-by: Trey Aspelund <trey@oxidecomputer.com>
1 parent 3e66478 commit d986afd

35 files changed

Lines changed: 2336 additions & 3614 deletions

bgp/src/config.rs

Lines changed: 0 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -2,187 +2,10 @@
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;
6-
use mg_api_types::bgp::config::{
7-
BgpPeerParameters, Neighbor, UnnumberedNeighbor,
8-
};
9-
use mg_api_types::bgp::peer::PeerId;
10-
use mg_api_types_versions::v1;
115
use rdb::Asn;
12-
use schemars::JsonSchema;
13-
use serde::{Deserialize, Serialize};
14-
use std::num::NonZeroU16;
15-
16-
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
17-
pub struct PeerConfig {
18-
pub name: String,
19-
pub group: String,
20-
pub id: PeerId,
21-
pub port: NonZeroU16,
22-
pub hold_time: u64,
23-
pub idle_hold_time: u64,
24-
pub delay_open: u64,
25-
pub connect_retry: u64,
26-
pub keepalive: u64,
27-
pub resolution: u64,
28-
}
296

307
#[derive(Debug, Clone, Copy)]
318
pub struct RouterConfig {
329
pub asn: Asn,
3310
pub id: u32,
3411
}
35-
36-
// ----- PeerConfig boundary conversions -----
37-
//
38-
// `PeerConfig` is bgp-internal (non-published) and tracks only the
39-
// fields needed to drive the connection state machine — most
40-
// BgpPeerParameters fields are session-level and don't belong here.
41-
// Each conversion destructures both the outer Neighbor-shaped type
42-
// and the embedded BgpPeerParameters so a field addition on either
43-
// fails to bind here, forcing a deliberate decision about whether
44-
// the new field belongs on PeerConfig. Bindings prefixed `_:` are
45-
// intentionally dropped.
46-
47-
impl From<Neighbor> for PeerConfig {
48-
fn from(rq: Neighbor) -> Self {
49-
let Neighbor {
50-
asn: _,
51-
name,
52-
group,
53-
host,
54-
parameters,
55-
} = rq;
56-
let BgpPeerParameters {
57-
hold_time,
58-
idle_hold_time,
59-
delay_open,
60-
connect_retry,
61-
keepalive,
62-
resolution,
63-
passive: _,
64-
remote_asn: _,
65-
min_ttl: _,
66-
md5_auth_key: _,
67-
multi_exit_discriminator: _,
68-
communities: _,
69-
local_pref: _,
70-
enforce_first_as: _,
71-
vlan_id: _,
72-
ipv4_unicast: _,
73-
ipv6_unicast: _,
74-
deterministic_collision_resolution: _,
75-
idle_hold_jitter: _,
76-
connect_retry_jitter: _,
77-
src_addr: _,
78-
src_port: _,
79-
} = parameters;
80-
Self {
81-
name,
82-
group,
83-
id: PeerId::Ip(host.ip()),
84-
port: NonZeroU16::new(host.port()).unwrap_or(BGP_PORT),
85-
hold_time,
86-
idle_hold_time,
87-
delay_open,
88-
connect_retry,
89-
keepalive,
90-
resolution,
91-
}
92-
}
93-
}
94-
95-
impl From<v1::bgp::config::Neighbor> for PeerConfig {
96-
fn from(rq: v1::bgp::config::Neighbor) -> Self {
97-
let v1::bgp::config::Neighbor {
98-
asn: _,
99-
name,
100-
group,
101-
host,
102-
parameters,
103-
} = rq;
104-
let v1::bgp::config::BgpPeerParameters {
105-
hold_time,
106-
idle_hold_time,
107-
delay_open,
108-
connect_retry,
109-
keepalive,
110-
resolution,
111-
passive: _,
112-
remote_asn: _,
113-
min_ttl: _,
114-
md5_auth_key: _,
115-
multi_exit_discriminator: _,
116-
communities: _,
117-
local_pref: _,
118-
enforce_first_as: _,
119-
allow_import: _,
120-
allow_export: _,
121-
vlan_id: _,
122-
} = parameters;
123-
Self {
124-
name,
125-
group,
126-
id: PeerId::Ip(host.ip()),
127-
port: NonZeroU16::new(host.port()).unwrap_or(BGP_PORT),
128-
hold_time,
129-
idle_hold_time,
130-
delay_open,
131-
connect_retry,
132-
keepalive,
133-
resolution,
134-
}
135-
}
136-
}
137-
138-
impl PeerConfig {
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 {
143-
let UnnumberedNeighbor {
144-
asn: _,
145-
name,
146-
group,
147-
interface,
148-
act_as_a_default_ipv6_router: _,
149-
parameters,
150-
} = n.clone();
151-
let BgpPeerParameters {
152-
hold_time,
153-
idle_hold_time,
154-
delay_open,
155-
connect_retry,
156-
keepalive,
157-
resolution,
158-
passive: _,
159-
remote_asn: _,
160-
min_ttl: _,
161-
md5_auth_key: _,
162-
multi_exit_discriminator: _,
163-
communities: _,
164-
local_pref: _,
165-
enforce_first_as: _,
166-
vlan_id: _,
167-
ipv4_unicast: _,
168-
ipv6_unicast: _,
169-
deterministic_collision_resolution: _,
170-
idle_hold_jitter: _,
171-
connect_retry_jitter: _,
172-
src_addr: _,
173-
src_port: _,
174-
} = parameters;
175-
Self {
176-
name,
177-
group,
178-
id: PeerId::Interface(interface),
179-
port: BGP_PORT,
180-
hold_time,
181-
idle_hold_time,
182-
delay_open,
183-
connect_retry,
184-
keepalive,
185-
resolution,
186-
}
187-
}
188-
}

bgp/src/connection.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::{
1313
use slog::Logger;
1414
use std::{
1515
net::{SocketAddr, ToSocketAddrs},
16+
num::NonZeroU16,
1617
sync::{Arc, Mutex, mpsc::Sender},
1718
thread::JoinHandle,
1819
time::Duration,
@@ -31,6 +32,13 @@ pub const MAX_MD5SIG_KEYLEN: usize = 80;
3132
#[cfg(target_os = "macos")]
3233
pub const MAX_MD5SIG_KEYLEN: usize = 80;
3334

35+
/// Resolved socket addresses for an outbound connection attempt.
36+
#[derive(Debug, Clone, Copy)]
37+
pub struct ConnectTarget {
38+
pub destination: SocketAddr,
39+
pub source: Option<SocketAddr>,
40+
}
41+
3442
/// Implementors of this trait listen to and accept inbound BGP connections.
3543
pub trait BgpListener<Cnx: BgpConnection> {
3644
/// Bind to an address and listen for connections.
@@ -63,6 +71,7 @@ pub trait BgpListener<Cnx: BgpConnection> {
6371
conn: &Cnx,
6472
min_ttl: Option<u8>,
6573
md5_key: Option<String>,
74+
listen_port: NonZeroU16,
6675
) -> Result<(), Error>;
6776

6877
/// `SocketAddr` the listener is receiving connections on
@@ -79,7 +88,7 @@ pub trait BgpConnector<Cnx: BgpConnection> {
7988
/// Returns a handle to the connection attempt, allowing the caller to track
8089
/// and manage it.
8190
fn connect(
82-
peer: SocketAddr,
91+
addrs: ConnectTarget,
8392
timeout: Duration,
8493
log: Logger,
8594
event_tx: Sender<FsmEvent<Cnx>>,

bgp/src/connection_channel.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use crate::{
1111
IO_TIMEOUT,
1212
clock::ConnectionClock,
1313
connection::{
14-
BgpConnection, BgpConnector, BgpListener, ConnectionDirection,
15-
ConnectionId, ThreadState,
14+
BgpConnection, BgpConnector, BgpListener, ConnectTarget,
15+
ConnectionDirection, ConnectionId, ThreadState,
1616
},
1717
error::Error,
1818
log::{connection_log, connection_log_lite},
@@ -275,6 +275,7 @@ impl BgpListener<BgpConnectionChannel> for BgpListenerChannel {
275275
_conn: &BgpConnectionChannel,
276276
_min_ttl: Option<u8>,
277277
_md5_key: Option<String>,
278+
_listen_port: std::num::NonZeroU16,
278279
) -> Result<(), Error> {
279280
// Policy application is ignored for test connections
280281
Ok(())
@@ -563,15 +564,16 @@ pub struct BgpConnectorChannel;
563564

564565
impl BgpConnector<BgpConnectionChannel> for BgpConnectorChannel {
565566
fn connect(
566-
peer: SocketAddr,
567+
addrs: ConnectTarget,
567568
timeout: Duration,
568569
log: Logger,
569570
event_tx: Sender<FsmEvent<BgpConnectionChannel>>,
570571
config: SessionInfo,
571572
) -> Result<JoinHandle<()>, Error> {
572573
let direction = ConnectionDirection::Outbound;
573-
let addr = config
574-
.bind_addr
574+
let peer = addrs.destination;
575+
let addr = addrs
576+
.source
575577
.expect("source address required for channel-based connection");
576578

577579
connection_log_lite!(log,

bgp/src/connection_tcp.rs

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use crate::{
66
IO_TIMEOUT,
77
clock::ConnectionClock,
88
connection::{
9-
BgpConnection, BgpConnector, BgpListener, ConnectionDirection,
10-
ConnectionId, ThreadState,
9+
BgpConnection, BgpConnector, BgpListener, ConnectTarget,
10+
ConnectionDirection, ConnectionId, ThreadState,
1111
},
1212
error::Error,
1313
log::{connection_log, connection_log_lite},
@@ -207,6 +207,7 @@ impl BgpListener<BgpConnectionTcp> for BgpListenerTcp {
207207
conn: &BgpConnectionTcp,
208208
min_ttl: Option<u8>,
209209
md5_key: Option<String>,
210+
_listen_port: std::num::NonZeroU16,
210211
) -> Result<(), Error> {
211212
let tcp_stream = lock!(conn.conn);
212213

@@ -230,7 +231,8 @@ impl BgpListener<BgpConnectionTcp> for BgpListenerTcp {
230231

231232
#[cfg(target_os = "illumos")]
232233
{
233-
let local = get_md5_source_addrs(conn.peer.ip())?;
234+
let local =
235+
get_md5_source_addrs(conn.peer.ip(), _listen_port.get())?;
234236
conn.manage_md5_associations(
235237
tcp_stream.as_raw_fd(),
236238
key,
@@ -258,12 +260,13 @@ pub struct BgpConnectorTcp;
258260

259261
impl BgpConnector<BgpConnectionTcp> for BgpConnectorTcp {
260262
fn connect(
261-
peer: SocketAddr,
263+
addrs: ConnectTarget,
262264
timeout: Duration,
263265
log: Logger,
264266
event_tx: Sender<FsmEvent<BgpConnectionTcp>>,
265267
config: SessionInfo,
266268
) -> Result<JoinHandle<()>, Error> {
269+
let peer = addrs.destination;
267270
let s = create_outbound_socket(peer, &log)?;
268271

269272
// Apply MD5 authentication before connecting (Linux)
@@ -290,8 +293,15 @@ impl BgpConnector<BgpConnectionTcp> for BgpConnectorTcp {
290293
#[cfg(target_os = "illumos")]
291294
let md5_locals = if let Some(key) = &config.md5_auth_key {
292295
Some(
293-
setup_outbound_md5(s.as_raw_fd(), key, peer.ip(), peer, &log)
294-
.map_err(|e| {
296+
setup_outbound_md5(
297+
s.as_raw_fd(),
298+
key,
299+
peer.ip(),
300+
peer,
301+
config.addr.listen_port.get(),
302+
&log,
303+
)
304+
.map_err(|e| {
295305
connection_log_lite!(log,
296306
warn,
297307
"failed to apply MD5 auth for {peer}: {e}";
@@ -334,7 +344,7 @@ impl BgpConnector<BgpConnectionTcp> for BgpConnectorTcp {
334344
}
335345

336346
// Bind to source address/port if specified
337-
if let Some(src) = config.bind_addr {
347+
if let Some(src) = addrs.source {
338348
let ba: socket2::SockAddr = src.into();
339349
if let Err(e) = s.bind(&ba) {
340350
connection_log_lite!(log,
@@ -1387,7 +1397,10 @@ fn source_address_select(dst: IpAddr) -> anyhow::Result<Vec<IpAddr>> {
13871397
/// Helper function to select and validate MD5 source addresses.
13881398
/// Eliminates duplication between inbound and outbound paths
13891399
#[cfg(target_os = "illumos")]
1390-
fn get_md5_source_addrs(peer_ip: IpAddr) -> Result<Vec<SocketAddr>, Error> {
1400+
fn get_md5_source_addrs(
1401+
peer_ip: IpAddr,
1402+
local_port: u16,
1403+
) -> Result<Vec<SocketAddr>, Error> {
13911404
let sources = source_address_select(peer_ip)
13921405
.map_err(|e| Error::InvalidAddress(e.to_string()))?;
13931406

@@ -1399,7 +1412,7 @@ fn get_md5_source_addrs(peer_ip: IpAddr) -> Result<Vec<SocketAddr>, Error> {
13991412

14001413
Ok(sources
14011414
.iter()
1402-
.map(|x| SocketAddr::new(*x, crate::BGP_PORT.get()))
1415+
.map(|x| SocketAddr::new(*x, local_port))
14031416
.collect())
14041417
}
14051418

@@ -1410,8 +1423,17 @@ fn any_port(mut s: SocketAddr) -> SocketAddr {
14101423
s
14111424
}
14121425

1413-
/// Generate all four bidirectional SA pairs for MD5 protection.
1414-
/// Covers both directions of traffic with both port configurations
1426+
/// Generate PF_KEY TCP-MD5 selectors for both possible BGP TCP 4-tuples.
1427+
///
1428+
/// `src` should use our BGP listening port. During outbound pre-connect setup
1429+
/// this comes from session configuration because the socket is not connected
1430+
/// yet; during inbound setup the dispatcher passes the same shared listen port
1431+
/// used by every session.
1432+
/// `dst` uses the peer's configured/listening port for outbound connections and
1433+
/// the peer's observed client endpoint for inbound connections.
1434+
///
1435+
/// Port 0 is used as the PF_KEY wildcard for the side whose ephemeral client
1436+
/// port is not known.
14151437
#[cfg(target_os = "illumos")]
14161438
fn sa_set(src: SocketAddr, dst: SocketAddr) -> [(SocketAddr, SocketAddr); 4] {
14171439
// There are two directions of traffic we have to cover with two port
@@ -1510,6 +1532,7 @@ fn setup_outbound_md5(
15101532
key: &str,
15111533
peer_ip: IpAddr,
15121534
peer: SocketAddr,
1535+
local_port: u16,
15131536
logger: &Logger,
15141537
) -> Result<(String, Vec<SocketAddr>), Error> {
15151538
let sources = source_address_select(peer_ip).map_err(|e| {
@@ -1537,7 +1560,7 @@ fn setup_outbound_md5(
15371560

15381561
let local: Vec<SocketAddr> = sources
15391562
.iter()
1540-
.map(|x| SocketAddr::new(*x, crate::BGP_PORT.get()))
1563+
.map(|x| SocketAddr::new(*x, local_port))
15411564
.collect();
15421565

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

0 commit comments

Comments
 (0)