-
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?
Conversation
This is not retransmittable data
…ble' into hole-punch-with-challenge
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main-iroh #231 +/- ##
=============================================
- Coverage 76.57% 76.36% -0.21%
=============================================
Files 83 83
Lines 23152 23220 +68
=============================================
+ Hits 17728 17733 +5
- Misses 5424 5487 +63 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
quinn-proto/src/connection/mod.rs
Outdated
| let token: u64 = self.rng.random(); | ||
| trace!(%destination, cid=%new_cid, token=format!("{:08x}", token), "Nat traversal: PATH_CHALLENGE packet"); | ||
| // TODO(@divma): abstract writting path challenges, this logic should | ||
| // no be here |
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.
How far do you want to go? I was thinking that at some point we have a generic function that writes frames (takes frame::Frame enum) and takes care of all the things we want for writing them:
- log the FRAME_TYPE being written with appropriate fields (please add that to the code here 😄 )
- increment the frame stats
- records the frame with qlog
- writes the frame to the packet/buffer
That's probably better as a separate refactor though.
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.
completely agree on all accounts, this current state is gross
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.
btw, the log line is already there friend :p
|
Documentation for this PR has been generated and is available at: https://n0-computer.github.io/quinn/pr/231/docs/iroh_quinn/ Last updated: 2025-12-15T21:09:43Z |
flub
left a comment
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.
Only managed to cover the sending side, will continue with the rest.
|
|
||
| if transmit.is_empty() { | ||
| // Nothing to send on this path, and nothing yet built; try to send hole punching | ||
| // path challenges. |
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:
- are we doing this on the right side of the congestion controller?
- do we need to have a congestion controller for each 4-tuple we're sending off-path patch_challenges for?
- this branch is only taken if
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.
| 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); |
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.
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 send_pref_path_challenge()? Perhaps that function can be made more generic to be like send_off_path_path_challenge.
| // 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() { |
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.
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 PacketBuilder on line 1230. At that time we already wrote a packet header into the transmit buffer. So really the previous check was correct because we should be in the first packet being built.
(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.)
| can_send.other, | ||
| self, | ||
| &mut qlog, | ||
| )? |
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.
I don't think this works, because the PacketBuilder is already created on line 1230 and already wrote the packet header with a CID. Perhaps it is best to move off-path
| max_datagrams, | ||
| self.path_data(path_id).current_mtu().into(), | ||
| ); | ||
| if let Some(challenge) = self.send_prev_path_challenge(now, &mut transmit, path_id) { |
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.
OMG, just realised this bug, because it has the same challenge that you have with off-path PATH_RESPONSE and I was wondering how it worked.
This needs to happen for each path we have! Not just PathId(0).
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.
we do it for every path but I'm not sure the buffer has the right contents. Probably doesn't
| } | ||
| } | ||
| }; | ||
|
|
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.
Suggestion:
This is the place where we can do all the off-path stuff:
send_prev_path_challenge()send_off_path_path_challenge(), to be created, probably share a lot of code with the previous function.send_off_path_path_response().
This allow each of them to start their own packet before the packet for the on-path stuff is stared. Additionally it makes sure they are prioritised over the on-path data, so application data can not hinder holepunching. If there are no CIDs to do their thing then these functions would return None (all would have the same signature as send_prev_path_challenge) and the loop would carry on with on-path data for the path.
Finally we'd have to leave a TODO to handle the congestion control properly for them. But for the first cut don't think it's problematic to skip this.
All of them need to be gated on !close, because they should not happen when we're closing probably.
…, fix PATH_ABANDON logic (#234) * fix(proto): Never use `self.close` from within quinn-proto, to ensure errors are returned from `poll` * Use `Connection::poll` instead of accessing `.state().take_error()` * Don't allow closing the last path, if no other *validated* path exists. Closes #244 * Add yet another regression test, also fix a typo in random_interaction * Set the `PathNotAbandoned` timer to at least the idle timeout Otherwise it's possible to mistake the connection timing out for a transport error of PATH_ABANDON not being sent. * `cargo make format` * Only return a transport error for an unanswered PATH_ABANDON if we still receive data on that path * Remove unused `PathNotAbandoned` timer * Rename `PathAbandon` to `ForgetPath` and `drop_path_state` to `forget_path` * Ignore "last path abandoned by peer" errors * Apply clippy suggestion * I guess a follow-up clippy fix? Lol * Rename "forget" to "discard" * Store `last_allowed_receive` in `PathState` * Chnage error message for failing PATH_ABANDON response * Fix timer name in qlog * Nit: Better warn message * Comment suggestion Co-authored-by: Floris Bruynooghe <[email protected]> * Fix comment grammar Co-authored-by: Floris Bruynooghe <[email protected]> * Add reference to `on_packet_authenticated` in comment --------- Co-authored-by: Floris Bruynooghe <[email protected]>
Co-authored-by: “ramfox” <“[email protected]”>
Description
TBD
Breaking Changes
TBD
Notes & open questions
TBD