Skip to content

Commit c369b52

Browse files
feat: switch from rand data to path challenges for nat traversal (#373)
* introduce CidQueue::remaining * add method to send nat traversal path challenges * remove rand data from poll transmit * remove unused import * remvoe active_probes from nat traversal state * register all the things * do not set the timer, handle ina different pr
1 parent d5580a5 commit c369b52

File tree

3 files changed

+96
-43
lines changed

3 files changed

+96
-43
lines changed

quinn-proto/src/cid_queue.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ pub(crate) struct CidQueue {
3535
///
3636
/// When [`Self::cursor_reserved`] and [`Self::cursor`] are equal, no CID is considered
3737
/// reserved.
38+
///
39+
/// The reserved CIDs section of the buffer, if non-empty will always be ahead of the active
40+
/// CID.
3841
cursor_reserved: usize,
3942
}
4043

@@ -132,6 +135,14 @@ impl CidQueue {
132135
Some(cid_data.0)
133136
}
134137

138+
/// Returns the number of unused CIDs (neither active nor reserved).
139+
pub(crate) fn remaining(&self) -> usize {
140+
self.iter_from_reserved()
141+
.count()
142+
.checked_sub(1)
143+
.expect("iterator is non empty")
144+
}
145+
135146
/// Iterate CIDs in CidQueue that are not `None`, including the active CID
136147
fn iter_from_active(&self) -> impl Iterator<Item = (usize, CidData)> + '_ {
137148
(0..Self::LEN).filter_map(move |step| {
@@ -140,9 +151,13 @@ impl CidQueue {
140151
})
141152
}
142153

143-
/// Iterate CIDs in CidQueue that are not `None`, including the active CID.
154+
/// Iterate CIDs in CidQueue that are not `None`, from [`Self::cursor_reserved`].
155+
///
156+
/// The iterator will always have at least one item, as it will include the active CID when no
157+
/// CID has been reserved, or the last reserved CID otherwise.
144158
///
145-
/// Along with the CID, it returns the offset counted from [`Self::cursor_reserved`] where the CID is stored.
159+
/// Along with the CID, it returns the offset counted from [`Self::cursor_reserved`] where the
160+
/// CID is stored.
146161
fn iter_from_reserved(&self) -> impl Iterator<Item = (usize, CidData)> + '_ {
147162
(0..(Self::LEN - self.reserved_len())).filter_map(move |step| {
148163
let index = (self.cursor_reserved + step) % Self::LEN;

quinn-proto/src/connection/mod.rs

Lines changed: 76 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{
88
sync::Arc,
99
};
1010

11-
use bytes::{BufMut, Bytes, BytesMut};
11+
use bytes::{Bytes, BytesMut};
1212
use frame::StreamMetaVec;
1313

1414
use rand::{Rng, SeedableRng, rngs::StdRng};
@@ -1014,26 +1014,6 @@ impl Connection {
10141014
max_datagrams: NonZeroUsize,
10151015
buf: &mut Vec<u8>,
10161016
) -> Option<Transmit> {
1017-
if let Some(probing) = self
1018-
.iroh_hp
1019-
.server_side_mut()
1020-
.ok()
1021-
.and_then(iroh_hp::ServerState::next_probe)
1022-
{
1023-
let destination = probing.remote();
1024-
trace!(%destination, "RAND_DATA packet");
1025-
let token: u64 = self.rng.random();
1026-
buf.put_u64(token);
1027-
probing.finish(token);
1028-
return Some(Transmit {
1029-
destination,
1030-
ecn: None,
1031-
size: 8,
1032-
segment_size: None,
1033-
src_ip: None,
1034-
});
1035-
}
1036-
10371017
let max_datagrams = match self.config.enable_segmentation_offload {
10381018
false => NonZeroUsize::MIN,
10391019
true => max_datagrams,
@@ -1221,6 +1201,9 @@ impl Connection {
12211201
if let Some(response) = self.send_off_path_path_response(now, buf, path_id) {
12221202
return Some(response);
12231203
}
1204+
if let Some(challenge) = self.send_nat_traversal_path_challenge(now, buf, path_id) {
1205+
return Some(challenge);
1206+
}
12241207
None
12251208
}
12261209

@@ -1997,6 +1980,78 @@ impl Connection {
19971980
})
19981981
}
19991982

1983+
/// Send a nat traversal challenge (off-path) on this path if possible.
1984+
///
1985+
/// This will ensure the path still has a remaining CID to use if the active one should be
1986+
/// retired.
1987+
fn send_nat_traversal_path_challenge(
1988+
&mut self,
1989+
now: Instant,
1990+
buf: &mut Vec<u8>,
1991+
path_id: PathId,
1992+
) -> Option<Transmit> {
1993+
let server_side = self.iroh_hp.server_side_mut().ok()?;
1994+
let probe = server_side.next_probe()?;
1995+
if !self.paths.get(&path_id)?.data.validated {
1996+
// Path is not usable for probing
1997+
return None;
1998+
}
1999+
2000+
let remote_cids = self.remote_cids.get_mut(&path_id)?;
2001+
2002+
// Check if this path has enough CIDs to send a probe. One to be reserved, one in case the
2003+
// active CID needs to be retired.
2004+
if remote_cids.remaining() < 2 {
2005+
return None;
2006+
}
2007+
2008+
let cid = remote_cids.next_reserved()?;
2009+
let remote = probe.remote();
2010+
let token = self.rng.random();
2011+
probe.mark_as_sent();
2012+
2013+
let frame = frame::PathChallenge(token);
2014+
2015+
let mut buf = TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into());
2016+
buf.start_new_datagram();
2017+
2018+
let mut builder =
2019+
PacketBuilder::new(now, SpaceId::Data, path_id, cid, &mut buf, false, self)?;
2020+
let stats = &mut self.stats.frame_tx;
2021+
builder.write_frame_with_log_msg(frame, stats, Some("(nat-traversal)"));
2022+
builder.finish_and_track(now, self, path_id, PadDatagram::ToMinMtu);
2023+
2024+
let path = &mut self.paths.get_mut(&path_id).expect("checked").data;
2025+
2026+
path.challenges_sent.insert(
2027+
token,
2028+
paths::SentChallengeInfo {
2029+
sent_instant: now,
2030+
network_path: FourTuple {
2031+
remote,
2032+
local_ip: None,
2033+
},
2034+
},
2035+
);
2036+
2037+
let size = buf.len();
2038+
2039+
self.stats.udp_tx.on_sent(1, size);
2040+
self.path_stats
2041+
.entry(path_id)
2042+
.or_default()
2043+
.udp_tx
2044+
.on_sent(1, size);
2045+
2046+
Some(Transmit {
2047+
destination: remote,
2048+
size,
2049+
ecn: None,
2050+
segment_size: None,
2051+
src_ip: None,
2052+
})
2053+
}
2054+
20002055
/// Indicate what types of frames are ready to send for the given space
20012056
///
20022057
/// *packet_size* is the number of bytes available to build the next packet.

quinn-proto/src/iroh_hp.rs

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use std::{
55
net::{IpAddr, SocketAddr},
66
};
77

8-
use identity_hash::IntMap;
98
use rustc_hash::{FxHashMap, FxHashSet};
109
use tracing::trace;
1110

@@ -304,10 +303,6 @@ pub(crate) struct ServerState {
304303
round: VarInt,
305304
/// Addresses to which PATH_CHALLENGES need to be sent.
306305
pending_probes: FxHashSet<IpPort>,
307-
/// Sent PATH_CHALLENGES for this round.
308-
///
309-
/// This is used to validate the remotes assigned to each token.
310-
active_probes: IntMap<u64, IpPort>,
311306
}
312307

313308
impl ServerState {
@@ -319,7 +314,6 @@ impl ServerState {
319314
next_local_addr_id: Default::default(),
320315
round: Default::default(),
321316
pending_probes: Default::default(),
322-
active_probes: Default::default(),
323317
}
324318
}
325319

@@ -364,12 +358,6 @@ impl ServerState {
364358
if round > self.round {
365359
self.round = round;
366360
self.pending_probes.clear();
367-
// TODO(@divma): This log is here because I'm not sure if dropping the challenges
368-
// without further interaction with the connection is going to cause issues.
369-
for (token, remote) in self.active_probes.drain() {
370-
let remote: SocketAddr = remote.into();
371-
trace!(token=format!("{:08x}", token), %remote, "dropping nat traversal challenge pending response");
372-
}
373361
} else if self.pending_probes.len() >= self.max_remote_addresses {
374362
return Err(Error::TooManyAddresses);
375363
}
@@ -385,21 +373,18 @@ impl ServerState {
385373
.map(|remote| ServerProbing {
386374
remote,
387375
pending_probes: &mut self.pending_probes,
388-
active_probes: &mut self.active_probes,
389376
})
390377
}
391378
}
392379

393380
pub(crate) struct ServerProbing<'a> {
394381
remote: IpPort,
395382
pending_probes: &'a mut FxHashSet<IpPort>,
396-
active_probes: &'a mut IntMap<u64, IpPort>,
397383
}
398384

399385
impl<'a> ServerProbing<'a> {
400-
pub(crate) fn finish(self, token: u64) {
386+
pub(crate) fn mark_as_sent(self) {
401387
self.pending_probes.remove(&self.remote);
402-
self.active_probes.insert(token, self.remote);
403388
}
404389

405390
pub(crate) fn remote(&self) -> SocketAddr {
@@ -555,16 +540,14 @@ mod tests {
555540

556541
dbg!(&state);
557542
assert_eq!(state.pending_probes.len(), 2);
558-
assert_eq!(state.active_probes.len(), 0);
559543

560544
let probe = state.next_probe().unwrap();
561-
probe.finish(1);
545+
probe.mark_as_sent();
562546
let probe = state.next_probe().unwrap();
563-
probe.finish(2);
547+
probe.mark_as_sent();
564548

565549
assert!(state.next_probe().is_none());
566550
assert_eq!(state.pending_probes.len(), 0);
567-
assert_eq!(state.active_probes.len(), 2);
568551
}
569552

570553
#[test]

0 commit comments

Comments
 (0)