Skip to content

Commit f71832f

Browse files
Queue server challenges
1 parent 64ff191 commit f71832f

File tree

4 files changed

+120
-18
lines changed

4 files changed

+120
-18
lines changed

quinn-proto/src/connection/mod.rs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4488,8 +4488,52 @@ impl Connection {
44884488
)));
44894489
}
44904490
}
4491-
Frame::ReachOut(_frame) => {
4492-
// TODO(@divma): handle
4491+
Frame::ReachOut(reach_out) => {
4492+
let Some(hp_state) = self.iroh_hp.as_mut() else {
4493+
return Err(TransportError::PROTOCOL_VIOLATION(
4494+
"received REACH_OUT frame when iroh's nat traversal was not negotiated",
4495+
));
4496+
};
4497+
4498+
match hp_state.handle_reach_out(reach_out) {
4499+
Ok(None) => {
4500+
// no action required here
4501+
}
4502+
Ok(Some((challenge_info, is_new_round))) => {
4503+
let iroh_hp::ChallengeNeeded {
4504+
token,
4505+
ip,
4506+
port,
4507+
round,
4508+
} = challenge_info;
4509+
if is_new_round {
4510+
// TODO(@divma): this depends on round starting on 1 right now,
4511+
// because the round should be greater to the default one, which is
4512+
// zero
4513+
self.spaces[SpaceId::Data].pending.challenges_round = round;
4514+
self.spaces[SpaceId::Data].pending.challenges.clear();
4515+
}
4516+
4517+
self.spaces[SpaceId::Data]
4518+
.pending
4519+
.challenges
4520+
.push((token, ip, port));
4521+
}
4522+
Err(iroh_hp::Error::WrongConnectionSide) => {
4523+
return Err(TransportError::PROTOCOL_VIOLATION(
4524+
"server sent REACH_OUT frames for nat traversal",
4525+
));
4526+
}
4527+
Err(iroh_hp::Error::TooManyAddresses) => {
4528+
return Err(TransportError::PROTOCOL_VIOLATION(
4529+
"client exceeded allowed REACH_OUT frames for this round",
4530+
));
4531+
}
4532+
Err(error) => {
4533+
warn!(%error,"error handling REACH_OUT frame");
4534+
// TODO(@divma): check if this is reachable
4535+
}
4536+
}
44934537
}
44944538
}
44954539
}
@@ -5391,6 +5435,7 @@ impl Connection {
53915435
max_remote_addresses,
53925436
max_local_addresses,
53935437
self.side(),
5438+
self.rng.clone(),
53945439
));
53955440
debug!(
53965441
%max_remote_addresses, %max_local_addresses,

quinn-proto/src/connection/spaces.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -564,8 +564,12 @@ pub struct Retransmits {
564564
pub(super) remove_address: BTreeSet<RemoveAddress>,
565565
/// Round and local addresses to advertise in `REACH_OUT` frames
566566
pub(super) reach_out: Option<(VarInt, Vec<(IpAddr, u16)>)>,
567-
/// Round and remote addresses to which `PATH_CHALLENGE`s need to be sent
568-
pub(super) challenges: Option<(VarInt, Vec<(IpAddr, u16)>)>,
567+
/// Round of the nat traversal challenges that are pending
568+
///
569+
/// This is only used for bitwise operations on the retransmission data.
570+
pub(super) challenges_round: VarInt,
571+
/// Tokens and remote addresses to which `PATH_CHALLENGE`s need to be sent
572+
pub(super) challenges: Vec<(u64, IpAddr, u16)>,
569573
}
570574

571575
impl Retransmits {
@@ -592,7 +596,7 @@ impl Retransmits {
592596
&& self.add_address.is_empty()
593597
&& self.remove_address.is_empty()
594598
&& self.reach_out.is_none()
595-
&& self.challenges.is_none()
599+
&& self.challenges.is_empty()
596600
}
597601
}
598602

@@ -633,15 +637,11 @@ impl ::std::ops::BitOrAssign for Retransmits {
633637
_ => {}
634638
}
635639

636-
// if there are two rounds, prefer the most recent pending challenges set
637-
let lhs_round = self.challenges.as_ref().map(|(round, _)| *round);
638-
let rhs_round = rhs.challenges.as_ref().map(|(round, _)| *round);
639-
match (lhs_round, rhs_round) {
640-
(None, Some(_)) => self.challenges = rhs.challenges.clone(),
641-
(Some(lhs_round), Some(rhs_round)) if rhs_round > lhs_round => {
642-
self.challenges = rhs.challenges.clone()
643-
}
644-
_ => {}
640+
if self.challenges_round < rhs.challenges_round {
641+
self.challenges_round = rhs.challenges_round;
642+
self.challenges = rhs.challenges.clone();
643+
} else if self.challenges_round == rhs.challenges_round {
644+
self.challenges.extend_from_slice(&rhs.challenges);
645645
}
646646
}
647647
}

quinn-proto/src/iroh_hp.rs

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ use std::{
33
net::{IpAddr, SocketAddr},
44
};
55

6+
use rand::{Rng, rngs::StdRng};
67
use rustc_hash::FxHashMap;
78

89
use crate::{
910
PathId, Side, VarInt,
10-
frame::{AddAddress, RemoveAddress},
11+
frame::{AddAddress, ReachOut, RemoveAddress},
1112
};
1213

1314
/// Maximum number of addresses to handle, applied both to local and remote addresses, regardless
@@ -45,6 +46,17 @@ pub(crate) struct NatTraversalRound {
4546
pub(crate) prev_round_path_ids: Vec<PathId>,
4647
}
4748

49+
pub(crate) struct ChallengeNeeded {
50+
/// Token to send on the challenge
51+
pub(crate) token: u64,
52+
/// Destination address of the challenge
53+
pub(crate) ip: IpAddr,
54+
/// Destination port of the challenge
55+
pub(crate) port: u16,
56+
/// Round to which this challenge belongs to
57+
pub(crate) round: VarInt,
58+
}
59+
4860
// TODO(@divma): unclear to me what these events are useful for\
4961
#[derive(Debug, Clone)]
5062
pub enum Event {
@@ -63,6 +75,8 @@ pub(crate) struct State {
6375
///
6476
/// This is set by the remote endpoint.
6577
max_local_addresses: usize,
78+
/// Random generator
79+
rng: StdRng,
6680
/// Candidate addresses the remote server reports as potentially reachable, to use for nat
6781
/// traversal attempts.
6882
remote_addresses: FxHashMap<VarInt, (IpAddr, u16)>,
@@ -83,7 +97,7 @@ pub(crate) struct State {
8397
round_path_ids: Vec<PathId>,
8498
/// Challenges sent by servers to validate client addresses without attempting to open
8599
/// multipath paths
86-
challenges: FxHashMap<u64, (IpAddr, u16)>,
100+
challenges: FxHashMap<(IpAddr, u16), u64>,
87101
}
88102

89103
/// Nat traversal api exclusive to clients
@@ -147,10 +161,16 @@ impl State {
147161
}
148162
}
149163

150-
pub(crate) fn new(max_remote_addresses: u8, max_local_addresses: u8, side: Side) -> Self {
164+
pub(crate) fn new(
165+
max_remote_addresses: u8,
166+
max_local_addresses: u8,
167+
side: Side,
168+
rng: StdRng,
169+
) -> Self {
151170
Self {
152171
remote_addresses: Default::default(),
153172
local_addresses: Default::default(),
173+
rng,
154174
next_local_addr_id: Default::default(),
155175
side,
156176
round: Default::default(),
@@ -209,6 +229,43 @@ impl State {
209229
self.round_path_ids = path_ids;
210230
Ok(())
211231
}
232+
233+
/// Handles a received [`ReachOut`]
234+
///
235+
/// It returns the token that should be sent in response to this frame as a challenge, and
236+
/// whether this starts a new nat traversal round.
237+
///
238+
/// If this frame was ignored, it returns `None`.
239+
pub(crate) fn handle_reach_out(
240+
&mut self,
241+
reach_out: ReachOut,
242+
) -> Result<Option<(ChallengeNeeded, bool)>, Error> {
243+
let ReachOut { round, ip, port } = reach_out;
244+
if self.side.is_client() {
245+
return Err(Error::WrongConnectionSide);
246+
}
247+
248+
if round >= self.round {
249+
let is_new_round = round > self.round;
250+
if is_new_round {
251+
self.challenges.clear();
252+
}
253+
if self.challenges.len() >= self.max_remote_addresses {
254+
return Err(Error::TooManyAddresses);
255+
}
256+
let token = self.rng.random();
257+
self.challenges.insert((ip, port), token);
258+
let challenge_info = ChallengeNeeded {
259+
token,
260+
ip,
261+
port,
262+
round,
263+
};
264+
return Ok(Some((challenge_info, is_new_round)));
265+
}
266+
267+
Ok(None)
268+
}
212269
}
213270

214271
impl<'a> ClientSide<'a> {

quinn-proto/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ pub struct Transmit {
329329
//
330330

331331
/// The maximum number of CIDs we bother to issue per path
332-
const LOC_CID_COUNT: u64 = 8;
332+
const LOC_CID_COUNT: u64 = 12;
333333
const RESET_TOKEN_SIZE: usize = 16;
334334
const MAX_CID_SIZE: usize = 20;
335335
const MIN_INITIAL_SIZE: u16 = 1200;

0 commit comments

Comments
 (0)