Skip to content

Commit a253291

Browse files
authored
Merge pull request #21 from FastNetMon/fix/bmp-replay-router-boundary
Stop BMP replay identity capture at the router boundary
2 parents efe3688 + edf9a5a commit a253291

2 files changed

Lines changed: 91 additions & 2 deletions

File tree

‎src/ingress/register.rs‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,10 @@ impl Register {
284284
Some((emit, entries.get(&emit)?.history_generation))
285285
}
286286

287-
/// Copy a bounded lineage under one lock: child -> peer -> router.
288-
/// Missing ancestors or cycles cannot be silently encoded as default peers.
287+
/// Copy a bounded wire-identity lineage under one lock: child -> peer ->
288+
/// router. A router's parent is an opaque unit ID, not encoding metadata.
289+
/// Missing required ancestors or cycles cannot be silently encoded as
290+
/// default peers.
289291
pub(crate) fn snapshot_lineage(
290292
&self,
291293
id: IngressId,
@@ -295,6 +297,9 @@ impl Register {
295297
let first = inline.cloned().or_else(|| entries.get(&id).cloned())?;
296298
let mut snapshot = vec![(id, first)];
297299
while let Some(parent) = snapshot.last()?.1.parent_ingress {
300+
if snapshot.last()?.1.ingress_type == Some(IngressType::Bmp) {
301+
break;
302+
}
298303
if snapshot.len() >= 8
299304
|| snapshot.iter().any(|(id, _)| *id == parent)
300305
{

‎src/units/bmp_tcp_out/replay.rs‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,90 @@ mod tests {
183183
RouteStatus::Active,
184184
))
185185
}
186+
187+
#[test]
188+
fn missing_required_router_still_rejects_replay_capture() {
189+
let registry = Register::default();
190+
registry.update_info(7, peer(65001).with_parent_ingress(3u32));
191+
assert!(SnapshotCache::default()
192+
.capture(&route(7), &registry)
193+
.is_none());
194+
}
195+
196+
#[tokio::test]
197+
async fn replay_stops_at_router_before_opaque_unit_parent() {
198+
// BMP input reserves a unit ID without an IngressInfo entry. The
199+
// router points at it for internal ownership; it is not wire identity.
200+
for source in [7, 8] {
201+
let registry = Register::default();
202+
let unit = registry.register();
203+
registry.update_info(
204+
3,
205+
IngressInfo::new()
206+
.with_ingress_type(IngressType::Bmp)
207+
.with_parent_ingress(unit)
208+
.with_name("example-router"),
209+
);
210+
registry.update_info(7, peer(65001).with_parent_ingress(3u32));
211+
registry.update_info(
212+
8,
213+
IngressInfo::new()
214+
.with_ingress_type(IngressType::BgpPath)
215+
.with_parent_ingress(7u32)
216+
.with_path_id(33u32),
217+
);
218+
assert!(registry.get(unit).is_none());
219+
220+
let (tx, mut rx) = mpsc::channel(8);
221+
let client = Arc::new(ClientState::new(
222+
"127.0.0.1:1234".parse().unwrap(),
223+
tx,
224+
10,
225+
1_000_000,
226+
));
227+
assert!(matches!(
228+
client
229+
.buffer_update_if_dumping(route(source), &registry)
230+
.await,
231+
super::super::client_state::BufferUpdateResult::Buffered
232+
));
233+
for id in [8, 7, 3] {
234+
registry.remove(id);
235+
}
236+
let queued = client.take_buffered_updates().await;
237+
assert_eq!(queued.len(), 1);
238+
let identities = &queued[0].identities;
239+
assert_eq!(identities.session_identity(source), Some((7, 0)));
240+
assert!(identities.get(unit).is_none());
241+
assert_eq!(
242+
identities.get(3).unwrap().name.as_deref(),
243+
Some("example-router")
244+
);
245+
assert!(
246+
send_update_to_client(
247+
&client,
248+
&queued[0].update,
249+
identities,
250+
true,
251+
FanInPeerDistinguisher::default(),
252+
false,
253+
)
254+
.await
255+
);
256+
let up = rx.try_recv().unwrap();
257+
assert_eq!(up[5], 3);
258+
assert_eq!(
259+
u32::from_be_bytes(up[32..36].try_into().unwrap()),
260+
65001
261+
);
262+
assert!(up
263+
.windows(b"example-router".len())
264+
.any(|part| part == b"example-router"));
265+
assert_eq!(rx.try_recv().unwrap()[5], 0);
266+
assert!(rx.try_recv().is_err());
267+
}
268+
}
269+
186270
#[tokio::test]
187271
async fn replay_keeps_original_headers_across_retirement_and_same_id_reconnect(
188272
) {

0 commit comments

Comments
 (0)