Skip to content

Commit 9f79cfb

Browse files
committed
bgp: resolve unnumbered peers from interface snapshots
Replace the BGP-facing unnumbered trait methods that returned separate scope and neighbor projections with active-interface snapshot lookups. The unnumbered manager now answers those lookups from a single active interface map entry, so BGP callers receive the interface name, scope ID, and discovered neighbor consistently. Use the scope-indexed snapshot for inbound link-local session-key resolution and the interface-indexed snapshot for outbound connection target resolution. Signed-off-by: Trey Aspelund <trey@oxidecomputer.com>
1 parent 668a1a6 commit 9f79cfb

7 files changed

Lines changed: 136 additions & 161 deletions

File tree

bgp/src/connection.rs

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -53,32 +53,21 @@ pub fn resolve_session_key(
5353
{
5454
match mgr.get_active_interface_by_scope(v6_addr.scope_id()) {
5555
Ok(Some(interface)) => {
56-
match mgr.get_discovered_ndp_neighbor(&interface) {
57-
Ok(Some(neighbor)) if neighbor == *v6_addr.ip() => {
58-
return PeerId::Interface(interface);
59-
}
60-
Ok(Some(neighbor)) => {
61-
debug!(log,
62-
"incoming unnumbered peer does not match discovered NDP neighbor";
63-
"interface" => interface.as_str(),
64-
"peer" => peer_addr,
65-
"discovered" => neighbor.to_string(),
66-
);
67-
}
68-
Ok(None) => {
69-
debug!(log,
70-
"incoming unnumbered peer has no discovered NDP neighbor";
71-
"interface" => interface.as_str(),
72-
"peer" => peer_addr,
73-
);
74-
}
75-
Err(e) => {
76-
error!(log,
77-
"discovered unnumbered neighbor query error: {e}";
78-
"interface" => interface.as_str(),
79-
"error" => format!("{e}"),
80-
);
81-
}
56+
if interface.discovered_neighbor == Some(*v6_addr.ip()) {
57+
return PeerId::Interface(interface.interface);
58+
} else if let Some(neighbor) = interface.discovered_neighbor {
59+
debug!(log,
60+
"incoming unnumbered peer does not match discovered NDP neighbor";
61+
"interface" => interface.interface.as_str(),
62+
"peer" => peer_addr,
63+
"discovered" => neighbor.to_string(),
64+
);
65+
} else {
66+
debug!(log,
67+
"incoming unnumbered peer has no discovered NDP neighbor";
68+
"interface" => interface.interface.as_str(),
69+
"peer" => peer_addr,
70+
);
8271
}
8372
}
8473
// Fallback to IP-based key on failure/missing interface

bgp/src/dispatcher.rs

Lines changed: 30 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -197,65 +197,57 @@ mod tests {
197197
use crate::{connection::resolve_session_key, session::PeerId};
198198
use slog::Logger;
199199
use std::{net::Ipv6Addr, sync::Arc};
200-
use unnumbered::{BgpUnnumbered, UnnumberedError};
200+
use unnumbered::{BgpUnnumbered, BgpUnnumberedInterface, UnnumberedError};
201201

202202
struct TestUnnumbered {
203-
scope_result: Result<Option<String>, UnnumberedError>,
204-
neighbor_result: Result<Option<Ipv6Addr>, UnnumberedError>,
203+
interface_result:
204+
Result<Option<BgpUnnumberedInterface>, UnnumberedError>,
205205
}
206206

207207
impl BgpUnnumbered for TestUnnumbered {
208208
fn get_active_interface_by_scope(
209209
&self,
210210
_scope_id: u32,
211-
) -> Result<Option<String>, UnnumberedError> {
212-
self.scope_result.clone()
211+
) -> Result<Option<BgpUnnumberedInterface>, UnnumberedError> {
212+
self.interface_result.clone()
213213
}
214214

215-
fn get_active_interface_scope_id(
215+
fn get_active_interface(
216216
&self,
217217
_interface: &str,
218-
) -> Result<Option<u32>, UnnumberedError> {
218+
) -> Result<Option<BgpUnnumberedInterface>, UnnumberedError> {
219219
Ok(None)
220220
}
221-
222-
fn get_discovered_ndp_neighbor(
223-
&self,
224-
_interface: &str,
225-
) -> Result<Option<Ipv6Addr>, UnnumberedError> {
226-
self.neighbor_result.clone()
227-
}
228221
}
229222

230223
fn log() -> Logger {
231224
Logger::root(slog::Discard, slog::o!())
232225
}
233226

234227
fn unnumbered_manager(
235-
scope_result: Result<Option<String>, UnnumberedError>,
228+
interface_result: Result<
229+
Option<BgpUnnumberedInterface>,
230+
UnnumberedError,
231+
>,
236232
) -> Arc<dyn BgpUnnumbered> {
237-
Arc::new(TestUnnumbered {
238-
scope_result,
239-
neighbor_result: Ok(None),
240-
})
233+
Arc::new(TestUnnumbered { interface_result })
241234
}
242235

243-
fn unnumbered_manager_with_neighbor(
244-
scope_result: Result<Option<String>, UnnumberedError>,
245-
neighbor_result: Result<Option<Ipv6Addr>, UnnumberedError>,
246-
) -> Arc<dyn BgpUnnumbered> {
247-
Arc::new(TestUnnumbered {
248-
scope_result,
249-
neighbor_result,
250-
})
236+
fn active_interface(
237+
discovered_neighbor: Option<Ipv6Addr>,
238+
) -> BgpUnnumberedInterface {
239+
BgpUnnumberedInterface {
240+
interface: "eth0".into(),
241+
scope_id: 7,
242+
discovered_neighbor,
243+
}
251244
}
252245

253246
#[test]
254247
fn discovered_link_local_uses_interface_key() {
255-
let manager = unnumbered_manager_with_neighbor(
256-
Ok(Some("eth0".into())),
257-
Ok(Some("fe80::1".parse().unwrap())),
258-
);
248+
let manager = unnumbered_manager(Ok(Some(active_interface(Some(
249+
"fe80::1".parse().unwrap(),
250+
)))));
259251
let peer = "[fe80::1%7]:179".parse().unwrap();
260252

261253
let key = resolve_session_key(peer, Some(&manager), &log());
@@ -265,7 +257,7 @@ mod tests {
265257

266258
#[test]
267259
fn undiscovered_link_local_uses_ip_key() {
268-
let manager = unnumbered_manager(Ok(Some("eth0".into())));
260+
let manager = unnumbered_manager(Ok(Some(active_interface(None))));
269261
let peer = "[fe80::1%7]:179".parse().unwrap();
270262

271263
let key = resolve_session_key(peer, Some(&manager), &log());
@@ -275,10 +267,9 @@ mod tests {
275267

276268
#[test]
277269
fn mismatched_link_local_uses_ip_key() {
278-
let manager = unnumbered_manager_with_neighbor(
279-
Ok(Some("eth0".into())),
280-
Ok(Some("fe80::2".parse().unwrap())),
281-
);
270+
let manager = unnumbered_manager(Ok(Some(active_interface(Some(
271+
"fe80::2".parse().unwrap(),
272+
)))));
282273
let peer = "[fe80::1%7]:179".parse().unwrap();
283274

284275
let key = resolve_session_key(peer, Some(&manager), &log());
@@ -312,7 +303,9 @@ mod tests {
312303

313304
#[test]
314305
fn non_link_local_address_uses_ip_session_key() {
315-
let manager = unnumbered_manager(Ok(Some("eth0".into())));
306+
let manager = unnumbered_manager(Ok(Some(active_interface(Some(
307+
"fe80::1".parse().unwrap(),
308+
)))));
316309
let peer = "[2001:db8::1]:179".parse().unwrap();
317310

318311
let key = resolve_session_key(peer, Some(&manager), &log());

bgp/src/session.rs

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,36 +1759,23 @@ impl<Cnx: BgpConnection + 'static> SessionRunner<Cnx> {
17591759
PeerId::Interface(iface) => {
17601760
// Unnumbered: query BgpUnnumbered for NDP neighbor
17611761
let mgr = self.unnumbered_manager.as_ref()?;
1762-
match mgr.get_discovered_ndp_neighbor(iface) {
1763-
Ok(Some(addr)) => {
1764-
match mgr.get_active_interface_scope_id(iface) {
1765-
Ok(Some(scope_id)) => {
1766-
Some(SocketAddr::V6(SocketAddrV6::new(
1767-
addr,
1768-
self.neighbor.port.get(),
1769-
0,
1770-
scope_id,
1771-
)))
1772-
}
1773-
Ok(None) => None,
1774-
Err(e) => {
1775-
session_log_lite!(
1776-
self,
1777-
debug,
1778-
"failed to query scope for interface";
1779-
"interface" => iface,
1780-
"error" => e.to_string()
1781-
);
1782-
None
1783-
}
1784-
}
1762+
match mgr.get_active_interface(iface) {
1763+
Ok(Some(interface)) => {
1764+
interface.discovered_neighbor.map(|addr| {
1765+
SocketAddr::V6(SocketAddrV6::new(
1766+
addr,
1767+
self.neighbor.port.get(),
1768+
0,
1769+
interface.scope_id,
1770+
))
1771+
})
17851772
}
1786-
Ok(None) => None, // No neighbor discovered yet - expected
1773+
Ok(None) => None, // Interface inactive - expected
17871774
Err(e) => {
17881775
session_log_lite!(
17891776
self,
17901777
debug,
1791-
"failed to query neighbor for interface";
1778+
"failed to query active unnumbered interface";
17921779
"interface" => iface,
17931780
"error" => e.to_string()
17941781
);
@@ -1816,17 +1803,17 @@ impl<Cnx: BgpConnection + 'static> SessionRunner<Cnx> {
18161803
.unnumbered_manager
18171804
.as_ref()
18181805
.ok_or(ResolveTargetError::NoUnnumberedManager)?;
1819-
let addr = mgr
1820-
.get_discovered_ndp_neighbor(iface)?
1821-
.ok_or(ResolveTargetError::NoNeighbor)?;
1822-
let scope_id = mgr
1823-
.get_active_interface_scope_id(iface)?
1806+
let interface = mgr
1807+
.get_active_interface(iface)?
18241808
.ok_or(ResolveTargetError::InterfaceInactive)?;
1809+
let addr = interface
1810+
.discovered_neighbor
1811+
.ok_or(ResolveTargetError::NoNeighbor)?;
18251812
Ok(SocketAddr::V6(SocketAddrV6::new(
18261813
addr,
18271814
self.neighbor.port.get(),
18281815
0,
1829-
scope_id,
1816+
interface.scope_id,
18301817
)))
18311818
}
18321819
PeerId::Ip(ip) => {

bgp/src/test.rs

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use std::{
3838
thread::{Builder, sleep},
3939
time::{Duration, Instant},
4040
};
41-
use unnumbered::BgpUnnumbered;
41+
use unnumbered::{BgpUnnumbered, BgpUnnumberedInterface, UnnumberedError};
4242

4343
// Use non-standard port outside the privileged range to avoid needing privs
4444
const TEST_BGP_PORT: u16 = 10179;
@@ -178,74 +178,73 @@ struct NeighborConfig {
178178

179179
#[derive(Clone)]
180180
struct TestBgpUnnumbered {
181-
scopes: Arc<Mutex<HashMap<u32, String>>>,
182-
neighbors: Arc<Mutex<HashMap<String, Option<Ipv6Addr>>>>,
181+
interfaces: Arc<Mutex<HashMap<String, BgpUnnumberedInterface>>>,
183182
}
184183

185184
impl TestBgpUnnumbered {
186185
fn new() -> Arc<Self> {
187186
Arc::new(TestBgpUnnumbered {
188-
scopes: Arc::new(Mutex::new(HashMap::new())),
189-
neighbors: Arc::new(Mutex::new(HashMap::new())),
187+
interfaces: Arc::new(Mutex::new(HashMap::new())),
190188
})
191189
}
192190

193191
fn activate_interface(&self, interface: impl Into<String>, scope_id: u32) {
194192
let interface = interface.into();
195-
lock!(self.scopes).insert(scope_id, interface.clone());
196-
lock!(self.neighbors).entry(interface).or_insert(None);
193+
lock!(self.interfaces).insert(
194+
interface.clone(),
195+
BgpUnnumberedInterface {
196+
interface,
197+
scope_id,
198+
discovered_neighbor: None,
199+
},
200+
);
197201
}
198202

199203
fn deactivate_interface(&self, interface: &str) {
200-
lock!(self.scopes).retain(|_, name| name != interface);
201-
lock!(self.neighbors).remove(interface);
204+
lock!(self.interfaces).remove(interface);
202205
}
203206

204207
fn discover_ndp_neighbor(
205208
&self,
206209
interface: &str,
207210
addr: Ipv6Addr,
208211
) -> Result<(), &'static str> {
209-
let mut neighbors = lock!(self.neighbors);
210-
let entry =
211-
neighbors.get_mut(interface).ok_or("interface not active")?;
212-
*entry = Some(addr);
212+
let mut interfaces = lock!(self.interfaces);
213+
let interface = interfaces
214+
.get_mut(interface)
215+
.ok_or("interface not active")?;
216+
interface.discovered_neighbor = Some(addr);
213217
Ok(())
214218
}
215219

216220
fn expire_ndp_neighbor(
217221
&self,
218222
interface: &str,
219223
) -> Result<Option<Ipv6Addr>, &'static str> {
220-
let mut neighbors = lock!(self.neighbors);
221-
let entry =
222-
neighbors.get_mut(interface).ok_or("interface not active")?;
223-
Ok(entry.take())
224+
let mut interfaces = lock!(self.interfaces);
225+
let interface = interfaces
226+
.get_mut(interface)
227+
.ok_or("interface not active")?;
228+
Ok(interface.discovered_neighbor.take())
224229
}
225230
}
226231

227232
impl BgpUnnumbered for TestBgpUnnumbered {
228233
fn get_active_interface_by_scope(
229234
&self,
230235
scope_id: u32,
231-
) -> Result<Option<String>, unnumbered::UnnumberedError> {
232-
Ok(lock!(self.scopes).get(&scope_id).cloned())
233-
}
234-
235-
fn get_discovered_ndp_neighbor(
236-
&self,
237-
interface: &str,
238-
) -> Result<Option<Ipv6Addr>, unnumbered::UnnumberedError> {
239-
Ok(lock!(self.neighbors).get(interface).copied().flatten())
236+
) -> Result<Option<BgpUnnumberedInterface>, UnnumberedError> {
237+
Ok(lock!(self.interfaces)
238+
.values()
239+
.find(|interface| interface.scope_id == scope_id)
240+
.cloned())
240241
}
241242

242-
fn get_active_interface_scope_id(
243+
fn get_active_interface(
243244
&self,
244245
interface: &str,
245-
) -> Result<Option<u32>, unnumbered::UnnumberedError> {
246-
Ok(lock!(self.scopes).iter().find_map(|(scope_id, name)| {
247-
(name == interface).then_some(*scope_id)
248-
}))
246+
) -> Result<Option<BgpUnnumberedInterface>, UnnumberedError> {
247+
Ok(lock!(self.interfaces).get(interface).cloned())
249248
}
250249
}
251250

unnumbered/src/bgp.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@
55
use crate::UnnumberedError;
66
use std::net::Ipv6Addr;
77

8+
/// Active unnumbered interface state exposed to BGP.
9+
#[derive(Clone, Debug, Eq, PartialEq)]
10+
pub struct BgpUnnumberedInterface {
11+
pub interface: String,
12+
pub scope_id: u32,
13+
pub discovered_neighbor: Option<Ipv6Addr>,
14+
}
15+
816
/// Trait for managing unnumbered BGP sessions via NDP neighbor discovery.
917
///
1018
/// This trait provides the interface between BGP and the external NDP/neighbor
@@ -18,15 +26,10 @@ pub trait BgpUnnumbered: Send + Sync {
1826
fn get_active_interface_by_scope(
1927
&self,
2028
scope_id: u32,
21-
) -> Result<Option<String>, UnnumberedError>;
22-
23-
fn get_active_interface_scope_id(
24-
&self,
25-
interface: &str,
26-
) -> Result<Option<u32>, UnnumberedError>;
29+
) -> Result<Option<BgpUnnumberedInterface>, UnnumberedError>;
2730

28-
fn get_discovered_ndp_neighbor(
31+
fn get_active_interface(
2932
&self,
3033
interface: &str,
31-
) -> Result<Option<Ipv6Addr>, UnnumberedError>;
34+
) -> Result<Option<BgpUnnumberedInterface>, UnnumberedError>;
3235
}

unnumbered/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ pub mod error;
77
mod interface;
88
pub mod manager;
99

10-
pub use bgp::BgpUnnumbered;
10+
pub use bgp::{BgpUnnumbered, BgpUnnumberedInterface};
1111
pub use error::UnnumberedError;
1212
pub use interface::{
1313
InterfaceMap, NewUnnumberedInterfaceError, UnnumberedInterface,

0 commit comments

Comments
 (0)