Skip to content

Commit 9bb5037

Browse files
authored
happy maps (#682)
* Use iddqd::BiHashMap for scope_id/interface map The unnumbered manager needs bidirectional lookup between scope_id and interface name. The prod code used a single HashMap with O(n) reverse scans; the mock maintained two manually synchronized HashMaps. Replace both with a ScopeMap newtype wrapping iddqd::BiHashMap. * Use iddqd IdOrdMap for Router.sessions Updates Router.sessions to be a new SessionMap which wraps an IdOrdMap, allowing us to use the SessionRunner's PeerId as a HashMap key. This ensures the map keys are borrowed from the values, so it's impossible to insert a SessionRunner into the map using a key that isn't derived from itself, i.e. you can't insert a SessionRunner behind the wrong PeerId. * Remove redundant peer_to_session map The peer_to_session map was very similar to the SessionMap in its uses. It mapped a PeerId to a SessionEndpoint, which was just a lightweight wrapper for a SessionRunner's event_tx (FSM event sender) and config (SessionInfo) used by the dispatcher during lookups for incoming TCP connections. Since both event_tx and SessionInfo are info coming from SessionRunner, this can all be accessed using the existing SessionMap without needing to maintain two separate maps. This gets rid of the peer_to_session map in lieu of using the SessionMap. * Review Feedback Signed-off-by: Trey Aspelund <trey@oxidecomputer.com> --------- Signed-off-by: Trey Aspelund <trey@oxidecomputer.com>
1 parent 16dda29 commit 9bb5037

16 files changed

Lines changed: 365 additions & 290 deletions

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ serial_test = "3.3"
135135
internet-checksum = "0.2.1"
136136
network-interface = { git = "https://github.com/oxidecomputer/network-interface", branch = "illumos" }
137137
natord = "1.0"
138+
iddqd = "0.3"
138139

139140

140141
[workspace.dependencies.opte-ioctl]

bgp/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ itertools.workspace = true
2323
oxnet.workspace = true
2424
uuid.workspace = true
2525
rand.workspace = true
26+
iddqd.workspace = true
2627
clap = { workspace = true, optional = true }
2728

2829
[target.'cfg(target_os = "illumos")'.dependencies]

bgp/src/connection.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ use crate::{
66
clock::ConnectionClock,
77
error::Error,
88
messages::Message,
9-
session::{FsmEvent, PeerId, SessionEndpoint, SessionInfo},
9+
router::SessionMap,
10+
session::{FsmEvent, SessionInfo},
1011
unnumbered::UnnumberedManager,
1112
};
1213
use slog::Logger;
1314
use std::{
14-
collections::BTreeMap,
1515
net::{SocketAddr, ToSocketAddrs},
1616
sync::{Arc, Mutex, mpsc::Sender},
1717
thread::JoinHandle,
@@ -49,11 +49,11 @@ pub trait BgpListener<Cnx: BgpConnection> {
4949
/// Accept a connection. This Listener is non-blocking, so the timeout
5050
/// is used as a sleep between accept attempts. This function may be called
5151
/// multiple times, returning a new connection each time. Policy application
52-
/// is handled by the Dispatcher after the peer_to_session lookup.
52+
/// is handled by the Dispatcher after the session lookup.
5353
fn accept(
5454
&self,
5555
log: Logger,
56-
peer_to_session: Arc<Mutex<BTreeMap<PeerId, SessionEndpoint<Cnx>>>>,
56+
sessions: Arc<Mutex<SessionMap<Cnx>>>,
5757
timeout: Duration,
5858
) -> Result<Cnx, Error>;
5959

bgp/src/connection_channel.rs

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@ use crate::{
1717
error::Error,
1818
log::{connection_log, connection_log_lite},
1919
messages::Message,
20-
session::{
21-
ConnectionEvent, FsmEvent, PeerId, SessionEndpoint, SessionInfo,
22-
},
20+
router::SessionMap,
21+
session::{ConnectionEvent, FsmEvent, PeerId, SessionInfo},
2322
unnumbered::UnnumberedManager,
2423
};
2524
use mg_common::lock;
2625
use slog::{Logger, info};
2726
use std::{
28-
collections::{BTreeMap, HashMap},
27+
collections::HashMap,
2928
net::{SocketAddr, ToSocketAddrs},
3029
sync::{
3130
Arc, Mutex,
@@ -244,38 +243,32 @@ impl BgpListener<BgpConnectionChannel> for BgpListenerChannel {
244243
fn accept(
245244
&self,
246245
log: Logger,
247-
peer_to_session: Arc<
248-
Mutex<BTreeMap<PeerId, SessionEndpoint<BgpConnectionChannel>>>,
249-
>,
246+
sessions: Arc<Mutex<SessionMap<BgpConnectionChannel>>>,
250247
timeout: Duration,
251248
) -> Result<BgpConnectionChannel, Error> {
252249
let (peer, endpoint) = self.listener.accept(timeout)?;
253250

254-
// For channel-based test connections, we use the bind address as the local
255-
// address. In a real network scenario (like TCP), we would need to get the
256-
// actual connection's local address to handle dual-stack correctly, but for
257-
// testing purposes with channels, the bind address is the connection address.
258251
let local = self.bind_addr;
259252

260253
// Resolve peer address to appropriate PeerId (IP or Interface)
261254
let key = self.resolve_session_key(peer);
262255

263-
match lock!(peer_to_session).get(&key) {
264-
Some(session_endpoint) => {
265-
let config = lock!(session_endpoint.config);
266-
Ok(BgpConnectionChannel::with_conn(
267-
local,
268-
peer,
269-
endpoint,
270-
session_endpoint.event_tx.clone(),
271-
IO_TIMEOUT,
272-
log,
273-
ConnectionDirection::Inbound,
274-
&config,
275-
))
276-
}
277-
None => Err(Error::UnknownPeer(peer.ip())),
278-
}
256+
let runner = lock!(sessions)
257+
.get(&key)
258+
.cloned()
259+
.ok_or(Error::UnknownPeer(peer.ip()))?;
260+
261+
let config = lock!(runner.session);
262+
Ok(BgpConnectionChannel::with_conn(
263+
local,
264+
peer,
265+
endpoint,
266+
runner.event_tx.clone(),
267+
IO_TIMEOUT,
268+
log,
269+
ConnectionDirection::Inbound,
270+
&config,
271+
))
279272
}
280273

281274
fn apply_policy(

bgp/src/connection_tcp.rs

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,13 @@ use crate::{
2020
RouteRefreshParseErrorReason, notification_message_from_wire,
2121
open_message_from_wire, route_refresh_message_from_wire,
2222
},
23-
session::{
24-
ConnectionEvent, FsmEvent, PeerId, SessionEndpoint, SessionEvent,
25-
SessionInfo,
26-
},
23+
router::SessionMap,
24+
session::{ConnectionEvent, FsmEvent, PeerId, SessionEvent, SessionInfo},
2725
unnumbered::UnnumberedManager,
2826
};
2927
use mg_common::lock;
3028
use slog::{Logger, info};
3129
use std::{
32-
collections::BTreeMap,
3330
io::Read,
3431
io::Write,
3532
net::{SocketAddr, TcpListener, TcpStream, ToSocketAddrs},
@@ -138,9 +135,7 @@ impl BgpListener<BgpConnectionTcp> for BgpListenerTcp {
138135
fn accept(
139136
&self,
140137
log: Logger,
141-
peer_to_session: Arc<
142-
Mutex<BTreeMap<PeerId, SessionEndpoint<BgpConnectionTcp>>>,
143-
>,
138+
sessions: Arc<Mutex<SessionMap<BgpConnectionTcp>>>,
144139
timeout: Duration,
145140
) -> Result<BgpConnectionTcp, Error> {
146141
let start = Instant::now();
@@ -172,23 +167,24 @@ impl BgpListener<BgpConnectionTcp> for BgpListenerTcp {
172167
// Resolve peer address to appropriate PeerId (IP or Interface)
173168
let key = self.resolve_session_key(peer);
174169

175-
// Check if we have a session for this peer
176-
match lock!(peer_to_session).get(&key) {
177-
Some(session_endpoint) => {
178-
let config = lock!(session_endpoint.config);
179-
return BgpConnectionTcp::with_conn(
180-
local,
181-
peer,
182-
conn,
183-
IO_TIMEOUT,
184-
session_endpoint.event_tx.clone(),
185-
log,
186-
ConnectionDirection::Inbound,
187-
&config,
188-
);
189-
}
190-
None => return Err(Error::UnknownPeer(ip)),
191-
}
170+
// Look up the session runner, clone the Arc, then release
171+
// the sessions lock before accessing session config.
172+
let runner = lock!(sessions)
173+
.get(&key)
174+
.cloned()
175+
.ok_or(Error::UnknownPeer(ip))?;
176+
177+
let config = lock!(runner.session);
178+
return BgpConnectionTcp::with_conn(
179+
local,
180+
peer,
181+
conn,
182+
IO_TIMEOUT,
183+
runner.event_tx.clone(),
184+
log,
185+
ConnectionDirection::Inbound,
186+
&config,
187+
);
192188
}
193189
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
194190
// Check if we've exceeded the timeout

bgp/src/dispatcher.rs

Lines changed: 38 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
use crate::{
66
IO_TIMEOUT,
77
connection::{BgpConnection, BgpListener},
8-
session::{FsmEvent, PeerId, SessionEndpoint, SessionEvent},
8+
router::SessionMap,
9+
session::{FsmEvent, PeerId, SessionEvent},
910
unnumbered::UnnumberedManager,
1011
};
1112
use mg_common::lock;
1213
use slog::{Logger, debug, error, info, warn};
1314
use std::{
14-
collections::BTreeMap,
1515
net::SocketAddr,
1616
sync::atomic::{AtomicBool, Ordering},
1717
sync::{Arc, Mutex},
@@ -21,10 +21,9 @@ use std::{
2121

2222
const UNIT_DISPATCHER: &str = "dispatcher";
2323

24-
pub struct Dispatcher<Cnx: BgpConnection> {
25-
/// Session endpoint map indexed by PeerId (IP or interface name)
26-
/// This unified map supports both numbered and unnumbered BGP sessions
27-
pub peer_to_session: Arc<Mutex<BTreeMap<PeerId, SessionEndpoint<Cnx>>>>,
24+
pub struct Dispatcher<Cnx: BgpConnection + 'static> {
25+
/// Session map shared with all Routers, indexed by PeerId.
26+
pub sessions: Arc<Mutex<SessionMap<Cnx>>>,
2827

2928
/// Optional unnumbered neighbor manager for link-local connection routing.
3029
/// When present, enables routing of IPv6 link-local connections to
@@ -38,7 +37,7 @@ pub struct Dispatcher<Cnx: BgpConnection> {
3837

3938
impl<Cnx: BgpConnection + 'static> Dispatcher<Cnx> {
4039
pub fn new(
41-
peer_to_session: Arc<Mutex<BTreeMap<PeerId, SessionEndpoint<Cnx>>>>,
40+
sessions: Arc<Mutex<SessionMap<Cnx>>>,
4241
listen: String,
4342
log: Logger,
4443
unnumbered_manager: Option<Arc<dyn UnnumberedManager>>,
@@ -50,7 +49,7 @@ impl<Cnx: BgpConnection + 'static> Dispatcher<Cnx> {
5049
));
5150

5251
Self {
53-
peer_to_session,
52+
sessions,
5453
unnumbered_manager,
5554
listen,
5655
log: Mutex::new(log),
@@ -145,7 +144,7 @@ impl<Cnx: BgpConnection + 'static> Dispatcher<Cnx> {
145144

146145
let accepted = match listener.accept(
147146
log.clone(),
148-
self.peer_to_session.clone(),
147+
self.sessions.clone(),
149148
IO_TIMEOUT,
150149
) {
151150
Ok(c) => {
@@ -171,41 +170,40 @@ impl<Cnx: BgpConnection + 'static> Dispatcher<Cnx> {
171170
"session_key" => format!("{key:?}"),
172171
));
173172

174-
match lock!(self.peer_to_session).get(&key).cloned() {
175-
Some(session_endpoint) => {
176-
// Apply connection policy from the session configuration
177-
let min_ttl = lock!(session_endpoint.config).min_ttl;
178-
let md5_key =
179-
lock!(session_endpoint.config).md5_auth_key.clone();
180-
181-
if let Err(e) =
182-
Listener::apply_policy(&accepted, min_ttl, md5_key)
183-
{
184-
warn!(session_log,
185-
"failed to apply policy for connection";
186-
"error" => format!("{e}")
187-
);
188-
}
189-
190-
if let Err(e) =
191-
session_endpoint.event_tx.send(FsmEvent::Session(
192-
SessionEvent::TcpConnectionAcked(accepted),
193-
))
194-
{
195-
error!(session_log,
196-
"failed to send connected event to session";
197-
"error" => format!("{e}")
198-
);
199-
continue 'listener;
200-
}
201-
}
202-
None => {
173+
let (runner, min_ttl, md5_key) = {
174+
let sessions = lock!(self.sessions);
175+
let Some(runner) = sessions.get(&key).cloned() else {
203176
debug!(
204177
session_log,
205178
"no session found for peer, dropping connection"
206179
);
207180
continue 'accept;
208-
}
181+
};
182+
let config = lock!(runner.session);
183+
(
184+
runner.clone(),
185+
config.min_ttl,
186+
config.md5_auth_key.clone(),
187+
)
188+
};
189+
190+
if let Err(e) =
191+
Listener::apply_policy(&accepted, min_ttl, md5_key)
192+
{
193+
warn!(session_log,
194+
"failed to apply policy for connection";
195+
"error" => format!("{e}")
196+
);
197+
}
198+
199+
if let Err(e) = runner.event_tx.send(FsmEvent::Session(
200+
SessionEvent::TcpConnectionAcked(accepted),
201+
)) {
202+
error!(session_log,
203+
"failed to send connected event to session";
204+
"error" => format!("{e}")
205+
);
206+
continue 'listener;
209207
}
210208
}
211209
}
@@ -225,7 +223,7 @@ impl<Cnx: BgpConnection + 'static> Dispatcher<Cnx> {
225223
}
226224
}
227225

228-
impl<Cnx: BgpConnection> Drop for Dispatcher<Cnx> {
226+
impl<Cnx: BgpConnection + 'static> Drop for Dispatcher<Cnx> {
229227
fn drop(&mut self) {
230228
debug!(lock!(self.log), "dropping dispatcher");
231229
}

0 commit comments

Comments
 (0)