mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-12 09:33:23 +00:00
feat(peer): transport-preference roaming cutover
When a peer is reachable over more than one transport at once, prefer the faster one and hold it. roam_current_addr upgrades eagerly to a higher- preference transport (BLE -> Wi-Fi Aware / UDP) but falls back to a lower one only after the preferred link has been silent past a hysteresis window (~2 heartbeat intervals), so a stray BLE keepalive can't drag an active Aware session back to BLE. poll_transport_discovery additionally stops re-probing an alternate path strictly worse than the one the peer is already on, so BLE rediscovery no longer keeps grabbing a peer that belongs on Aware. Equal preferences reduce to plain last-authenticated- packet-wins roaming, so single-transport deployments are unaffected.
This commit is contained in:
@@ -263,6 +263,11 @@ impl Node {
|
||||
let ce_flag = header.flags & FLAG_CE != 0;
|
||||
let sp_flag = header.flags & FLAG_SP != 0;
|
||||
|
||||
// Transport-preference cutover: gate roaming so a faster transport
|
||||
// (Wi-Fi Aware / UDP) isn't dragged back to a slower one (BLE) by a
|
||||
// stray packet. Computed before the peer borrow (needs `self`).
|
||||
let roam_pref = self.transport_preference(packet.transport_id);
|
||||
let roam_hysteresis = self.roam_hysteresis_ms();
|
||||
if let Some(peer) = self.peers.get_mut(&node_addr) {
|
||||
if let Some(mmp) = peer.mmp_mut() {
|
||||
mmp.receiver.record_recv(
|
||||
@@ -274,7 +279,13 @@ impl Node {
|
||||
);
|
||||
let _spin_rtt = mmp.spin_bit.rx_observe(sp_flag, header.counter, now);
|
||||
}
|
||||
peer.set_current_addr(packet.transport_id, packet.remote_addr.clone());
|
||||
peer.roam_current_addr(
|
||||
packet.transport_id,
|
||||
packet.remote_addr.clone(),
|
||||
roam_pref,
|
||||
packet.timestamp_ms,
|
||||
roam_hysteresis,
|
||||
);
|
||||
peer.link_stats_mut()
|
||||
.record_recv(packet.data.len(), packet.timestamp_ms);
|
||||
peer.touch(packet.timestamp_ms);
|
||||
@@ -356,10 +367,18 @@ impl Node {
|
||||
return;
|
||||
};
|
||||
let now = Instant::now();
|
||||
let roam_pref = self.transport_preference(transport_id);
|
||||
let roam_hysteresis = self.roam_hysteresis_ms();
|
||||
let mut address_changed = false;
|
||||
if let Some(peer) = self.peers.get_mut(node_addr) {
|
||||
peer.reset_decrypt_failures();
|
||||
address_changed = peer.set_current_addr(transport_id, remote_addr.clone());
|
||||
address_changed = peer.roam_current_addr(
|
||||
transport_id,
|
||||
remote_addr.clone(),
|
||||
roam_pref,
|
||||
packet_timestamp_ms,
|
||||
roam_hysteresis,
|
||||
);
|
||||
peer.link_stats_mut()
|
||||
.record_recv(packet_len, packet_timestamp_ms);
|
||||
peer.touch(packet_timestamp_ms);
|
||||
|
||||
@@ -620,6 +620,18 @@ impl Node {
|
||||
let remote_addr = peer.addr;
|
||||
|
||||
if self.peers.contains_key(&node_addr) {
|
||||
// Don't re-probe an alternate path that is strictly worse
|
||||
// than the one the peer is already on: a peer settled on a
|
||||
// faster transport (Wi-Fi Aware / UDP) must not be pulled
|
||||
// back to BLE by BLE rediscovery. Equal-or-better candidates
|
||||
// still refresh, so BLE → Aware upgrades proceed.
|
||||
if let Some(cur) = self.peers.get(&node_addr).and_then(|p| p.transport_id()) {
|
||||
if self.transport_preference(cur)
|
||||
> self.transport_preference(candidate_transport_id)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let transport_name = transport.transport_type().name;
|
||||
let candidate = PeerAddress::new(transport_name, remote_addr.to_string());
|
||||
if self.active_peer_candidate_is_fresh_enough_to_skip(
|
||||
|
||||
@@ -1093,6 +1093,29 @@ impl Node {
|
||||
.map(|(id, _)| *id)
|
||||
}
|
||||
|
||||
/// Link preference of a transport instance by id (higher = preferred),
|
||||
/// from its transport type. See [`crate::transport::transport_link_preference`].
|
||||
/// Unknown ids get 0 so any real transport outranks a stale/removed one.
|
||||
pub(crate) fn transport_preference(&self, id: TransportId) -> u8 {
|
||||
self.transports
|
||||
.get(&id)
|
||||
.map(|h| crate::transport::transport_link_preference(h.transport_type().name))
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// How long a preferred transport must be silent before a lower-preference
|
||||
/// transport may take over the peer's link (the roaming cutover hysteresis).
|
||||
/// ~2 heartbeat intervals so a single missed heartbeat can't cause a
|
||||
/// spurious downgrade; floored so a tiny configured interval can't flap.
|
||||
pub(crate) fn roam_hysteresis_ms(&self) -> u64 {
|
||||
(self
|
||||
.config()
|
||||
.node
|
||||
.heartbeat_interval_secs
|
||||
.saturating_mul(2_000))
|
||||
.max(15_000)
|
||||
}
|
||||
|
||||
/// Resolve an Ethernet peer address ("interface/mac") to a transport ID
|
||||
/// and binary TransportAddr.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user