-
Notifications
You must be signed in to change notification settings - Fork 4
use PATH_CHALLENGE for hole punching #231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main-iroh
Are you sure you want to change the base?
Changes from 15 commits
3467051
59187e1
8975e9d
132f969
8ba015b
443346b
fa72173
cc59abb
4b1fbde
d7947f2
4f57a1c
24f2318
ff990de
1d5daee
52f3d2d
41367e8
0ec61d4
9f22564
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -892,26 +892,6 @@ impl Connection { | |
| max_datagrams: usize, | ||
| buf: &mut Vec<u8>, | ||
| ) -> Option<Transmit> { | ||
| if let Some(probing) = self | ||
| .iroh_hp | ||
| .server_side_mut() | ||
| .ok() | ||
| .and_then(iroh_hp::ServerState::next_probe) | ||
| { | ||
| let destination = probing.remote(); | ||
| trace!(%destination, "RAND_DATA packet"); | ||
| let token: u64 = self.rng.random(); | ||
| buf.put_u64(token); | ||
| probing.finish(token); | ||
| return Some(Transmit { | ||
| destination, | ||
| ecn: None, | ||
| size: 8, | ||
| segment_size: None, | ||
| src_ip: None, | ||
| }); | ||
| } | ||
|
|
||
| assert!(max_datagrams != 0); | ||
| let max_datagrams = match self.config.enable_segmentation_offload { | ||
| false => 1, | ||
|
|
@@ -1118,6 +1098,42 @@ impl Connection { | |
| break; | ||
| } | ||
|
|
||
| if transmit.is_empty() { | ||
| // Nothing to send on this path, and nothing yet built; try to send hole punching | ||
| // path challenges. | ||
| if let Some(probing) = self | ||
| .iroh_hp | ||
| .server_side_mut() | ||
| .ok() | ||
| .and_then(iroh_hp::ServerState::next_probe) | ||
| { | ||
| if let Some(new_cid) = self | ||
| .rem_cids | ||
| .get_mut(&path_id) | ||
| .and_then(CidQueue::next_reserved) | ||
| { | ||
| let destination = probing.remote(); | ||
| let token: u64 = self.rng.random(); | ||
| let challenge = frame::PathChallenge(token); | ||
| trace!(%destination, cid=%new_cid, %challenge, "Nat traversal: PATH_CHALLENGE packet"); | ||
| buf.write(challenge); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't have an active PacketBuilder here. So I don't think this is writing a proper encrypted packet. Just writing some random bytes on the wire. Should this not basically do the same as |
||
| QlogSentPacket::default().frame(&Frame::PathChallenge(challenge)); | ||
| self.stats.frame_tx.path_challenge += 1; | ||
|
|
||
| // TODO(@divma): padding | ||
| // Remember the token sent to this remote | ||
| probing.finish(token); | ||
| return Some(Transmit { | ||
| destination, | ||
| ecn: None, | ||
| size: 8, | ||
| segment_size: None, | ||
| src_ip: None, | ||
| }); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| match self.paths.keys().find(|&&next| next > path_id) { | ||
| Some(next_path_id) => { | ||
| // See if this next path can send anything. | ||
|
|
@@ -1319,12 +1335,29 @@ impl Connection { | |
|
|
||
| // Send an off-path PATH_RESPONSE. Prioritized over on-path data to ensure that | ||
| // path validation can occur while the link is saturated. | ||
| if space_id == SpaceId::Data && builder.buf.num_datagrams() == 1 { | ||
| if space_id == SpaceId::Data && builder.buf.is_empty() { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know we spoke about this earlier and assumed this was a better check. But reviewing now I can see that we've already created a (I should make another attempt at making the PacketBuilder own the transmit at this point so that you can't make this mistake. In the upstream PR of the TransmitBuf/TransmitBuilder this might already be better even.) |
||
| let path = self.path_data_mut(path_id); | ||
| if let Some((token, remote)) = path.path_responses.pop_off_path(path.remote) { | ||
| // TODO(flub): We need to use the right CID! We shouldn't use the same | ||
| // CID as the current active one for the path. Though see also | ||
| // https://github.com/quinn-rs/quinn/issues/2184 | ||
| let mut builder = if let Some(fresh_cid) = self | ||
| .rem_cids | ||
| .get_mut(&path_id) | ||
| .and_then(CidQueue::next_reserved) | ||
| { | ||
| PacketBuilder::new( | ||
| now, | ||
| space_id, | ||
| path_id, | ||
| fresh_cid, | ||
| &mut transmit, | ||
| can_send.other, | ||
| self, | ||
| &mut qlog, | ||
| )? | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this works, because the |
||
| } else { | ||
| // TODO(@divma): keep the old logic. We would need to push the challenge | ||
| // back, but we have lost the packet number. | ||
| builder | ||
| }; | ||
| let response = frame::PathResponse(token); | ||
| trace!(%response, "(off-path)"); | ||
| builder.frame_space_mut().write(response); | ||
|
|
@@ -4262,6 +4295,9 @@ impl Connection { | |
| self.ping_path(path_id).ok(); | ||
| } | ||
| } | ||
| } else if self.iroh_hp.client_side().is_ok() { | ||
| // TODO(@divma): temp log. This might be too much | ||
| debug!("Potential Nat traversal PATH_CHALLENGE received"); | ||
| } | ||
| } | ||
| Frame::PathResponse(response) => { | ||
|
|
@@ -4270,44 +4306,19 @@ impl Connection { | |
| .get_mut(&path_id) | ||
| .expect("payload is processed only after the path becomes known"); | ||
|
|
||
| match path.data.challenges_sent.get(&response.0) { | ||
| // Response to an on-path PathChallenge | ||
| Some(info) if info.remote == remote && path.data.remote == remote => { | ||
| let sent_instant = info.sent_instant; | ||
| // TODO(@divma): reset timers using the remaining off-path challenges | ||
| self.timers.stop( | ||
| Timer::PerPath(path_id, PathTimer::PathValidation), | ||
| self.qlog.with_time(now), | ||
| ); | ||
| self.timers.stop( | ||
| Timer::PerPath(path_id, PathTimer::PathChallengeLost), | ||
| self.qlog.with_time(now), | ||
| ); | ||
| if !path.data.validated { | ||
| trace!("new path validated"); | ||
| } | ||
| self.timers.stop( | ||
| Timer::PerPath(path_id, PathTimer::PathOpen), | ||
| self.qlog.with_time(now), | ||
| ); | ||
| // Clear any other on-path sent challenge. | ||
| path.data | ||
| .challenges_sent | ||
| .retain(|_token, info| info.remote != remote); | ||
| path.data.send_new_challenge = false; | ||
| path.data.validated = true; | ||
|
|
||
| // This RTT can only be used for the initial RTT, not as a normal | ||
| // sample: https://www.rfc-editor.org/rfc/rfc9002#section-6.2.2-2. | ||
| let rtt = now.saturating_duration_since(sent_instant); | ||
| path.data.rtt.reset_initial_rtt(rtt); | ||
|
|
||
| self.events | ||
| .push_back(Event::Path(PathEvent::Opened { id: path_id })); | ||
| // mark the path as open from the application perspective now that Opened | ||
| // event has been queued | ||
| if !std::mem::replace(&mut path.data.open, true) { | ||
| trace!("path opened"); | ||
| use paths::OnPathResponseReceived::*; | ||
| match path.data.on_path_response_received(now, response.0, remote) { | ||
| OnPath { was_open } => { | ||
| use PathTimer::*; | ||
| let qlog = self.qlog.with_time(now); | ||
|
|
||
| self.timers | ||
| .stop(Timer::PerPath(path_id, PathValidation), qlog.clone()); | ||
| self.timers | ||
| .stop(Timer::PerPath(path_id, PathOpen), qlog.clone()); | ||
| if !was_open { | ||
| self.events | ||
| .push_back(Event::Path(PathEvent::Opened { id: path_id })); | ||
| if let Some(observed) = path.data.last_observed_addr_report.as_ref() | ||
| { | ||
| self.events.push_back(Event::Path(PathEvent::ObservedAddr { | ||
|
|
@@ -4320,20 +4331,44 @@ impl Connection { | |
| prev.challenges_sent.clear(); | ||
| prev.send_new_challenge = false; | ||
| } | ||
|
|
||
| match path.data.earliest_expiring_challenge() { | ||
| Some(new_expire_time) => { | ||
| let expires = new_expire_time | ||
| + self.ack_frequency.max_ack_delay_for_pto(); | ||
| self.timers.set( | ||
| Timer::PerPath(path_id, PathChallengeLost), | ||
| expires, | ||
| qlog, | ||
| ) | ||
| } | ||
| None => self | ||
| .timers | ||
| .stop(Timer::PerPath(path_id, PathChallengeLost), qlog), | ||
| } | ||
| } | ||
| // Response to an off-path PathChallenge | ||
| Some(info) if info.remote == remote => { | ||
| OffPath => { | ||
| debug!("Response to off-path PathChallenge!"); | ||
| path.data | ||
| .challenges_sent | ||
| .retain(|_token, info| info.remote != remote); | ||
| match path.data.earliest_expiring_challenge() { | ||
| Some(new_expire_time) => { | ||
| let expires = new_expire_time | ||
| + self.ack_frequency.max_ack_delay_for_pto(); | ||
| self.timers.set( | ||
| Timer::PerPath(path_id, PathTimer::PathChallengeLost), | ||
| expires, | ||
| self.qlog.with_time(now), | ||
| ) | ||
| } | ||
| None => self.timers.stop( | ||
| Timer::PerPath(path_id, PathTimer::PathChallengeLost), | ||
| self.qlog.with_time(now), | ||
| ), | ||
| } | ||
| } | ||
| // Response to a PathChallenge we recognize, but from an invalid remote | ||
| Some(info) => { | ||
| debug!(%response, from=%remote, expected=%info.remote, "ignoring invalid PATH_RESPONSE") | ||
| Unknown => debug!(%response, "ignoring invalid PATH_RESPONSE"), | ||
| Invalid { expected } => { | ||
| debug!(%response, from=%remote, %expected, "ignoring invalid PATH_RESPONSE") | ||
| } | ||
| // Response to an unknown PathChallenge | ||
| None => debug!(%response, "ignoring invalid PATH_RESPONSE"), | ||
| } | ||
| } | ||
| Frame::MaxData(bytes) => { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Edit: see the suggestion i make above to address these things.
I know we've tried to cover this bit about where to put this before, but upon reading this again I wondered:
path_should_sendis false. but since holepunching is time-sensitive, should it should probably be prioritised over any other data to be sent on this path. otherwise application data can starve out the holepunching.I think the congestion controller bit is fine to ignore at first. We can not count the off-path path challenges to a congestion controller for now (but should create an issue).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another problem with this location that I hadn't realised before is that this is now prioritised over a CONNECTION_CLOSE frame, which it shouldn't.