Skip to content

Conversation

@divagant-martian
Copy link
Collaborator

Description

TBD

Breaking Changes

TBD

Notes & open questions

TBD

@codecov-commenter
Copy link

codecov-commenter commented Dec 10, 2025

Codecov Report

❌ Patch coverage is 38.33333% with 74 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.36%. Comparing base (8dfdc04) to head (52f3d2d).

Files with missing lines Patch % Lines
quinn-proto/src/connection/mod.rs 27.16% 59 Missing ⚠️
quinn-proto/src/connection/paths.rs 61.53% 15 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@n0bot n0bot bot added this to iroh Dec 10, 2025
@github-project-automation github-project-automation bot moved this to 🏗 In progress in iroh Dec 10, 2025
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
Copy link
Collaborator

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.

Copy link
Collaborator Author

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

Copy link
Collaborator Author

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

@divagant-martian divagant-martian changed the title use hole PATH_CHALLENGE for hole punching use PATH_CHALLENGE for hole punching Dec 10, 2025
@github-actions
Copy link

github-actions bot commented Dec 15, 2025

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

Copy link
Collaborator

@flub flub left a 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.
Copy link
Collaborator

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_send is 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).

Copy link
Collaborator

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);
Copy link
Collaborator

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() {
Copy link
Collaborator

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,
)?
Copy link
Collaborator

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) {
Copy link
Collaborator

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).

Copy link
Collaborator Author

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

}
}
};

Copy link
Collaborator

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.

matheus23 and others added 3 commits December 15, 2025 16:05
…, 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]>
@flub flub added this to the iroh 1.0 RC milestone Dec 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: 🏗 In progress

Development

Successfully merging this pull request may close these issues.

6 participants