Commit Graph

5111 Commits

Author SHA1 Message Date
Vladimir Petrzhikovskii 4cfbafa4cf fix(proto): congestion: avoid double-reducing CUBIC fast convergence
Quinn mistakenly applied the beta multiplier to the newly adjusted `W_max` instead of the current window.
This double-reduced the window to 59.5% instead of the intended 70%.
This fixes the math by snapshotting the original window for the multiplicative decrease.
2026-07-02 19:11:05 -05:00
dependabot[bot] a6042549b4 deps: bump rcgen from 0.14.7 to 0.14.8
Bumps [rcgen](https://github.com/rustls/rcgen) from 0.14.7 to 0.14.8.
- [Release notes](https://github.com/rustls/rcgen/releases)
- [Commits](https://github.com/rustls/rcgen/compare/v0.14.7...v0.14.8)

---
updated-dependencies:
- dependency-name: rcgen
  dependency-version: 0.14.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 18:34:03 -05:00
Benjamin Saunders 190ef12009 test(noq): Improve 0-RTT integration test
This used more concurrency than necessary and did not guarantee actual 0-RTT
communication.
2026-07-02 18:19:31 -05:00
Benjamin Saunders 7e2de9179e docs(noq): Document SendStream::stopped for detecting 0-RTT rejection
Unlike the `ZeroRttAccepted` future, this is guaranteed to reliably indicate
whether a specific stream was rejected, even if it was opened concurrently
with completion of the handshake.
2026-07-02 17:20:31 -05:00
Benjamin Saunders 3e926fa9d4 feat(noq): Introduce noq::Connection::authenticated 2026-07-02 16:55:15 -05:00
Benjamin Saunders 497973c860 doc(noq): Expand RecvStream::is_0rtt docs 2026-07-02 16:14:53 -05:00
Anthony 8f80192666 feat(proto): allow ProtectedHeader::decode to use a generic reference 2026-07-02 15:53:19 -05:00
Diva Martínez 1692e9d020 Merge commit '07ce61cc2' into quinn-integration
# Conflicts:
#	.github/CODEOWNERS
#	.github/workflows/codecov.yml
#	.github/workflows/rust.yml
#	Cargo.lock
#	Cargo.toml
#	bench/Cargo.toml
#	deny.toml
#	noq-proto/src/config/transport.rs
#	noq-proto/src/connection/cid_state.rs
#	noq-proto/src/connection/datagrams.rs
#	noq-proto/src/connection/pacing.rs
#	noq-proto/src/connection/spaces.rs
#	noq-proto/src/connection/streams/state.rs
#	noq-proto/src/crypto/rustls.rs
#	noq-proto/src/endpoint.rs
#	noq-proto/src/range_set/array_range_set.rs
#	noq-proto/src/tests/mod.rs
#	noq-proto/src/transport_parameters.rs
#	noq-udp/Cargo.toml
#	noq-udp/src/lib.rs
#	noq-udp/src/unix.rs
#	noq-udp/src/windows.rs
#	noq-udp/tests/tests.rs
#	noq/examples/client.rs
#	noq/src/connection.rs
#	noq/src/endpoint.rs
#	noq/src/lib.rs
#	noq/src/recv_stream.rs
#	noq/tests/post_quantum.rs
#	perf/src/lib.rs
#	quinn-proto/Cargo.toml
#	quinn-proto/src/congestion/bbr/bw_estimation.rs
#	quinn-proto/src/congestion/bbr/mod.rs
#	quinn-proto/src/connection/mod.rs
#	quinn-proto/src/connection/packet_builder.rs
#	quinn-proto/src/connection/packet_crypto.rs
#	quinn-proto/src/connection/paths.rs
#	quinn-proto/src/connection/qlog.rs
#	quinn-proto/src/connection/stats.rs
#	quinn-proto/src/frame.rs
#	quinn-proto/src/range_set/btree_range_set.rs
#	quinn-proto/src/tests/util.rs
#	quinn/Cargo.toml
#	quinn/src/tests.rs
2026-07-02 14:55:06 -05:00
Diva Martínez 54bae7f87d fix(proto): make path_stats not require self mutably - api unchanged (#736)
## Description

Continuing perusing stats, `path_stats` and `stats` both require `&mut
self`, which is unintuitive and actually not even needed.
- `stats` doesn't even need a change beside updating the api, so no
change for this function, just noted here for completeness.
- `path_stats` was taking the stats mutably, updating them and then
returning.
There is no point in the update, as this is never read. The next call to
`path_stats` overwrites whatever is in there. Instead of updating the
stored
value with the snapshot fields, it updates the returned stats. This
allows
us to polish the public API and remove the unexpected `&mut self` in
favor
  of the more natural `&self`.

## Breaking Changes

n/a

## Notes & open questions

n/a

## Change checklist
<!-- Remove any that are not relevant. -->
- [x] Self-review.
2026-07-02 12:53:29 +00:00
Heyang Zhou 52a500044d fix(proto): discard stale abandoned path packets (#732)
## Description

Fixes a panic when a stale coalesced datagram is delivered for a path
after that path’s state has already been discarded. The packet receive
path now early-discards packets whose `path_id` is no longer in `paths`
but is known in `abandoned_paths`, before coalesced packet handling can
assume the path still exists.

## Change checklist
- [x] Self-review.
- [x] Tests if relevant.
2026-07-02 10:52:31 +00:00
Floris Bruynooghe 79745edeb0 tests(proto): fix description of how ManyToMany routing works (#726)
## Description

The routing description didn't match the code.

## Breaking Changes

none

## Notes & open questions

n/a

## Change checklist

- [x] Self-review.
2026-07-01 15:54:52 +00:00
Diva Martínez d6e525d412 fix!(proto): Deprecate UdpStats::ios as io-operations can't be measured from this crate (#725)
## Description

Closes #727 
Deprecates `ios` in `UdpStats`. It's a bit of a shame that `UdpStats` is
shared by so many places, path's sending stats, receiving stats,
connection-wide send and recv stats. There is no field that captures
something like this and makes sense across all of them, so adding it
would make for a structural change.

This field was clearly added to test gso batching: a connection-wide
sending stat. This is the only place where this is meaningful, so to be
able to continue testing this, a tet only field is added to the
connection stats.

## Breaking Changes

- Deprecates `UdpStats::ios` without replacement. See #727 If you do
need to count io operations, please do so by incorporating counting
operations in your `AsyncUdpSocket` implementation

## Notes & open questions

If anyone seriously cares about this, we should know thanks to the
deprecation warning, and would prompt us to add something that better
fits with the rest of the stats.

Whatever we do won't just be a undo of this PR as counting doesn't make
sense in the rest of places, and the name is misleading. Basically, the
deprecation warning should allow us to decide whether a larger change is
required to get the `gso_batches` (what the field actually tried to
measure, as noted by the tests) or if it can be cleanly removed. I'm
tempted to say the latter, as I don't think users care too much about
this.

Now, if they _do_ want a count of `io` operations, that's an entirely
different approach that requires interaction with the `AsyncUdpSocket`.

## Change checklist
<!-- Remove any that are not relevant. -->
- [x] Self-review.
- [x] Breaking changes documented.

<!--
tip:
Run `cargo make` in the workspace root to check many light-weight CI
steps locally.
-->
2026-06-30 17:11:26 +00:00
Floris Bruynooghe 424652d6a9 deps: bump anyhow version to avoid unsoundness (#731)
## Description

Today's cargo-deny gift

## Breaking Changes

none

## Notes & open questions

Not updating Cargo.toml because there's no API issue with those
versions for us. It's not our job to make sure users use sound
versions I guess.

## Change checklist

- [x] Self-review.
2026-06-29 15:13:43 +00:00
dignifiedquire 340e9c7da0 chore: Release noq-v1.0.1 noq-udp-v1.0.1 noq-proto-v1.0.1 2026-06-29 11:41:09 +02:00
Diva Martínez 288323bbc8 fix(proto): ensure stats reported in Discarded events are updated (#728)
## Description

`PathStats` has a lot of values that are cumulative counters and so,
those are
updated as their respective actions are performed. Some other values,
like rtt,
are snapshots: views of the current state, and so, are only updated on
demand.
When a path is discarded, it _used_ to call `Connection::path_stats`
which
performs the update, but it was lost in a refactor. As a consequence,
those
values were no longer being updated before the final stats were returned
in the
`Discarded` event, despite the comment explaining the need to do so
remaining
in the code. 

This fixes the issue by updating the stats and adds a test. Yes, it's a
stats
only test, but the fact this was properly done at some point and
regressed
later is evidence this is needed.

Small consequence is that the return type of `PathStatsMap::discard` is
no
longer needed/useful.

## Breaking Changes

n/a

## Notes & open questions

n/a

## Change checklist
<!-- Remove any that are not relevant. -->
- [x] Self-review.
- [x] Tests if relevant.

<!--
tip:
Run `cargo make` in the workspace root to check many light-weight CI
steps locally.
-->
2026-06-29 07:10:03 +00:00
Diva Martínez 4b7a6e1674 fix(proto): improve queuing and sending of OBSERVED_ADDR frames (#722)
## Description

Supersedes #674
Fixes #329 

Fixes a couple of issues with scheduling and sending OBSERVED_ADDR
frames:
- instead of sending the frame with path challenge and path response,
queue it, to keep a single place where sending occurs.
- remove `may_send_data` as a condition to send observed address frames.
This,
according to the docs, is about sending `Data` data, but this is a path
specific frame, not part of the broader Data space. The check is not
needed
and it's also not really correct. But this being path-specific became
clear
  only recently, so it explains why this might have been added.

## Breaking Changes

n/a

## Notes & open questions

n/a

## Change checklist
<!-- Remove any that are not relevant. -->
- [x] Self-review.
- [x] Documentation updates following the [style
guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text),
if relevant.

<!--
tip:
Run `cargo make` in the workspace root to check many light-weight CI
steps locally.
-->
2026-06-26 07:42:38 +00:00
Floris Bruynooghe af0fa65468 fix(migration): Do not update local_ip for probing packets (#715)
## Description

When receiving a PATH_RESPONSE for which the 4-tuple of the challenge
matches the local network path the path should be marked as
validated. But it should not itself update the PathData::network_path
of that path generation. This is already being correctly handled,
taking packet numbers and probing packets into account, at the end of
Connection::process_payload.

If the local IP *did* somehow change after the challenge was sent but
before the response was received, the PathData::network_path will no
longer match the path the challenge was sent on and the response will
be ignored. This means the timers would remain set and a new challenge
would be sent.

## Breaking Changes

none

## Notes & open questions

none

## Change checklist

- [x] Self-review.
2026-06-25 13:39:08 +00:00
Diva Martínez c37f064618 fix(proto): ObservedAddr retransmission by creating a path-specific retransmittable data queue (#705)
## Description

- Adds `PathRetransmits` to track data that must be sent over a specific
path.
- Fixes re-transmission of `OBSERVED_ADDR` frames. This was not done
correctly
because the loss of a packet containing these frames would set a global
variable to be re-sent, that was then picked up by the first path. Think
a
  path "stealing" re-transmission of the frame from another path.
- Naming standards: Replaces `observed_addr_sent` for
`pending_observed_addr` to
  use the naming convention in the codebase.

Partially replaces #674

## Breaking Changes



## Notes & open questions

Much simpler and elegant this way.
As I mentioned in #674 a test is not really possible because of the
multiple
"safeguards" that prevents this from happening

## Change checklist
- [x] Self-review.
- [x] Documentation updates following the [style
guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text),
if relevant.
2026-06-23 13:49:06 +00:00
Philipp Krüger 32467e4567 fix(proto): Avoid active_connections underflow (#717)
## Description

Fixes an underflow panic on decrementing `active_connections` in the noq
endpoint state.
This occurred when the API was used in this order:
1. client connects to server
2. server calls `Endpoint::accept` and captures the `Incoming`
3. server endpoint closes using `Endpoint::close`
4. server side calls `Incoming::accept` on the captured incoming
5. (EndpointDriver will panic)

The reason for this was that incrementing and decrementing the
`active_connections` was asymmetrical.
We would increment `active_connections` only if the endpoint wasn't
closed yet. However, we would decrement unconditionally, eventually
underflowing.

This PR removes the condition for incrementing. This preserves the
behavior of being able to accept `Incoming`s after calling
`Endpoint::close`. This is somewhat unlike what the iroh API expects
(because in iroh you can't do anything with the endpoint *after*
`Endpoint::close`), but noq's API is just different.

This also adds a regression test that fails without the fix. That needs
a `PanicPropagatingRuntime` implementation and some test helpers to
allow for configuring that though...

## Breaking Changes

None

## Notes & open questions

Disclosure: Heavily assisted with GLM 5.2.

## Change checklist
- [x] Self-review.
- [x] Tests if relevant.
- [x] All breaking changes documented.
2026-06-22 13:47:30 +00:00
Philipp Krüger 147892a1d1 fix(proto): Use correct info in retransmission of *_BLOCKED frames (#716)
## Description

Please merge this after #710 

Prior to this change, retransmissions of PATHS_BLOCKED and
PATH_CIDS_BLOCKED frames would use "up to date" information for the
known maximum remote path ID and CID queue expected next seq number
respectively.

This is weird, because these frames are informational and meant for
debugging, and either we don't send them when they're not needed anymore
(e.g. between originally trying to send them and retransmission the
maximum remote path ID increased or the CID queue active seq number
increased. In both of these cases we wouldn't actually be blocked
anymore.), or we just retransmit them with with the "outdated" values
that we sent them with on the first transmit.

This PR decides to go for the latter. This provides the appropriate
debugging information to the remote that we were out of path CIDs *at
some point in time*, even if that is now obsolete.

---

Apart from that this PR also fixes the fact that we never actually
queued PATHS_BLOCKED frames in the first place...

## Breaking Changes

None.

## Notes & open questions

Arguably, we should not send any of these frames and I personally hate
them, they haven't really helped me at all and only caused problems so
far.

The only good reason I can find to keep sending them is that the spec
forces the server side to correctly *handle* them (and produce protocol
violations if they're sent incorrectly), and without actually sending
them, we would never exercise these code paths and not make sure that
said server side checks are correct. (Yes I think that's just
unnecessary busywork and wasted bytes/datagrams.)

I've also added a test to check that we transmit & retransmit
PATHS_BLOCKED frames. Unfortunately it's quite hard to check that the
data that is actually sent is the same PATHS_BLOCKED frame as originally
constructed. Instead I did a manual check by looking at the logs. Please
trust me bro.

## Change checklist

- [x] Self-review.
- [x] Tests if relevant.
2026-06-19 10:06:22 +00:00
Philipp Krüger 8ca5916dec fix(proto): Ignore PATH_CIDS_BLOCKED when it refers to abandoned paths (#710)
## Description

When receiving a `PATH_CIDS_BLOCKED` frame we used to check the
`next_seq` value for protocol compliance like so:
```rs
if next_seq.0
    > self
        .local_cid_state
        .get(&path_id)
        .map(|cid_state| cid_state.active_seq().1 + 1)
        .unwrap_or_default()
{
    return Err(TransportError::PROTOCOL_VIOLATION(
        "PATH_CIDS_BLOCKED next sequence number larger than in local state",
    ));
}
```
But the `.unwrap_or_default()` would fail if we don't actually store
path state for the given path. This can occur when the path has already
been abandoned and discarded.
Usually, we wouldn't hit this case because the most likely value for
`next_seq` for PATH_CIDS_BLOCKED is 0, *unless* it is lost and
retransmitted.

The added regression test simulates all of these rare circumstances at
once:
- All PATH_NEW_CONNECTION_ID frames from server to client are delayed
such that opening a path on the client side generates a
PATH_CIDS_BLOCKED frame
- The PATH_CIDS_BLOCKED frame is lost as well to ensure it is resent
with a newer `next_seq` number *after* the delayed
PATH_NEW_CONNECTION_ID frames have come in
- The path is abadoned and discarded on both ends before we send the
delayed PATH_CIDS_BLOCKED frame triggering the bug

The fix is to correctly check for
`!self.abandoned_paths.contains(&path_id)` in the above condition.

## Breaking Changes

None

## Notes & open questions

This bug report is what originally prompted this work:
https://github.com/n0-computer/iroh/issues/4347

It should now be fixed.

## Change checklist

- [x] Self-review.
- [x] Tests if relevant.
- [x] All breaking changes documented.
2026-06-18 16:24:31 +00:00
Franz Heinzmann 5a0d249f27 fix(noq-udp): Set Windows ioctls to suppress ICMP errors on recv (#707)
Description

On Windows, the OS reports the fate of a previously sent UDP datagram
against the **next `recv`** on that socket. A send to a port with no
listener draws an ICMP port-unreachable, reported as `WSAECONNRESET`; a
send whose datagram has its TTL expire in transit draws an ICMP
time-exceeded, reported as `WSAENETRESET`. We already ignore
`WSAECONNRESET` on the recv path (the Rust standard library maps
`WSAECONNRESET` to `io::ErrorKind::ConnectionReset which we ignore in
the `EndpointDriver`).

Windows provides ioctls to suppress these notifications altogether,
which saves a syscall and has no downsides for us, as we never handle
recv errors related to a previous send anyway. This disables both
`SIO_UDP_CONNRESET` (port-unreachable / `WSAECONNRESET`) and
`SIO_UDP_NETRESET` (TTL-expired / `WSAENETRESET`) in
`UdpSocketState::new`.


## Notes & open questions

Comes with a regression test that I confirmed to fail on Windows without
the fix. It deterministically reproduces the port-unreachable
(`WSAECONNRESET`) path.

`SIO_UDP_CONNRESET` and `SIO_UDP_NETRESET` are ioctls, not socket
options, so they go through `WSAIoctl` rather than the existing
`set_socket_option`/`setsockopt` helper. `SIO_UDP_NETRESET` requires
Windows 8+, and neither is implemented under Wine; a missing ioctl is
logged and tolerated rather than treated as fatal.

## Change checklist

- [x] Self-review.
- [x] Documentation updates following the [style
guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text),
if relevant.
- [x] Tests if relevant.
- [x] All breaking changes documented.

---------

Co-authored-by: Floris Bruynooghe <flub@n0.computer>
2026-06-18 09:45:41 +00:00
Floris Bruynooghe abdd49b480 refactor(multipath): Track OpenStatus on PacketNumberSpace (#711)
## Description

A path's open status, whether the application has been informed of the
path being usable, does not belong to the 4-tuple state of the path
generation. It is something that is specific to the PacketNumberSpace
as it stay stable regardless of the 4-tuple the path migrates to.

Contributes to #591.

## Breaking Changes

none

## Notes & open questions

none

## Change checklist

- [x] Self-review.
- [x] Documentation updates following the [style
guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text),
if relevant.
2026-06-18 09:20:17 +00:00
Floris Bruynooghe b9b9fcf02d fix(proto): Move pending path responses to PacketNumberSpace (#708)
## Description

This primarily moves pending path responses to the PacketNumberSpace
they should be sent on. There is nothing in a path response that
signals it is on- or off-path, every response must be sent on the
4-tuple it was received on. The only difference is at sending time: an
on-path PATH_RESPONSE could be combined with other on-path data ready
to send.

## Breaking Changes

none

## Notes & open questions

This PR does a bunch of renames in independent commits. Best to review
commit-by-commit and also to merge without squashing.

## Change checklist

- [x] Self-review.
- [x] Documentation updates following the [style
guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text),
if relevant.
2026-06-18 09:15:50 +00:00
Floris Bruynooghe 611ecf32b9 refactor(qnt): higher priority for add and remove address frames (#709)
## Description

This moves the ADD_ADDRESS and REMOVE_ADDRESS frames to be the same
priority as REACH_OUT (they are only sent by the server while only the
client sends REACH_OUT frames). In particular they need to be sent
before any user data and before new connection IDs. Otherwise
PATH_NEW_CONNECTION_ID frames being issued for other paths are
delaying the ADD_ADDRESS frames which can already be used by QNT.

## Breaking Changes

n/a

## Notes & open questions

This is part of my notes from investigating #4247.

## Change checklist

- [x] Self-review.
2026-06-18 09:00:26 +00:00
Floris Bruynooghe 854eebd809 chore(ci): Enable merge queue trigger for patchay tests
This means the merge queue queue will trigger starting the patchbay
checks. And those checks are currently required to merge, so without
it the merge queue can not merge PRs.
2026-06-18 10:32:19 +02:00
dignifiedquire 88b05460ce chore: Release noq-proto-v1.0.0 noq-udp-v1.0.0 noq-v1.0.0 2026-06-15 11:20:37 +02:00
Floris Bruynooghe 16805f8ce5 refactor(logging): improve Display of PATH_ABANDON frame (#706)
## Description

This now includes the error_code that is part of the frame.

## Breaking Changes

none

## Notes & open questions

A small thing i still had lying around in a stash. Might as well
submit this separately.

## Change checklist

- [x] Self-review.

---------

Co-authored-by: Diva Martínez <26765164+divagant-martian@users.noreply.github.com>
2026-06-13 14:44:24 +00:00
Floris Bruynooghe 8c712cd952 fix(qlog): Avoid another panic for an invariant (#702)
## Description

We really can not afford these panics for broken
invariants. Especially not in auxiliary code like qlog.

## Breaking Changes

none

## Notes & open questions

none

## Change checklist

- [x] Self-review.
2026-06-11 15:15:24 +00:00
Floris Bruynooghe 2a342d1703 fix(quic): tail-loss probes in Initial space must be padded (#698)
## Description

When the server sends a tail-loss probe in the Initial space it was not
padded to 1200-bytes. However a tail-loss probe is by-definition
ack-eliciting, so it MUST be padded to 1200 bytes.

Because of how space_can_send works it does not give us accurate
information for tail-loss probes. Which is why this bug occurred. We do
want to eventually get rid of space_can_send in #507 which would be a
more systematic fix, hence I'm fine with just the simple fix of catering
for this exception here.

## Breaking Changes

none

## Notes & open questions

I believe this will effectively fix #690. This is the reason that a
packet of an invalid size gets produced.

That issue has a secondary problem that the data scheduled for the 2nd
tail-loss probe than does not fit all in the 2nd tail-loss probe. This
will also be fixed as a side effect of the first tail-loss probe being
padded so the 2nd bug won't trigger.

That 2nd bug of not tracking the exact size needs is also an already
known bug that so far doesn't cause too much real-life trouble. It's
other symptom is tail-loss probes sending an entire GSO batch of empty
packets.

If you inspect the output of the test carefully there are still some
inefficient and minor bugs happening after "continue connection
establishment". Though none of them seem to be very critical right away.

## Change checklist

- [x] Self-review.
- [x] Tests if relevant.
2026-06-11 14:09:59 +00:00
Franz Heinzmann a8edb1218e deps: update n0-qlog (#703)
## Description

This updates `n0-qlog` to `0.2.0`, which merges in changes from upstream
`qlog` and brings us up-to-date to the latest draft standards. See
https://github.com/n0-computer/n0-qlog/pull/16 for all changes.

## Change checklist

- [x] Self-review.
2026-06-11 09:52:50 +00:00
Floris Bruynooghe cc44e0849b fix(multipath): ignore PATH_RESPONSE once the path is abandoned (#700)
## Description

The fix in #695 was only gating emitting the event to the
application. However the entire frame should be ignored: the timers
are already all cancelled when the path is
abandoned (Connection::abandon_path). And this branch *could*
accidentally set a new timer if there are still other path challenges
without a response on the path. Which would then lead to spurious
firing of timers for an abandoned path.

## Breaking Changes

none

## Notes & open questions

I'm choosing to do this in the match branches so that the received
challenge is removed from the `PathData::on_path_challenges_unconfirmed`
field. This probably no longer matters, but it seems tidier than
entirely ignoring the frame.

Follow up to
https://github.com/n0-computer/noq/pull/695/files#r3386765693

I'm hoping the existing test is sufficient. Testing this particular
anomaly would be very tricky as we can't yet inject a custom packet
when we want it.

## Change checklist

- [x] Self-review.
2026-06-10 15:46:17 +00:00
Diva Martínez 6c5e246864 fix(proto): prevent PATH_CHALLENGE/RESPONSE from being sent in datagrams that can't be expanded (#699)
## Description

Philipp's monkeys generated a case in which path challenges are sent on
the
second datagram of a batch, where the first one is smaller than the
required
`MIN_INITIAL_SIZE`. This causes a misalignment of the batch since these
frames
must be sent on expanded datagrams.

## Breaking Changes

n/a

## Notes & open questions

Regression test is in #694

## Change checklist
- [x] Self-review.
2026-06-10 10:26:56 +00:00
Franz Heinzmann 000e1bb649 fix(noq-proto): Never emit PathEvent::Established after a path has been abandoned (#695)
## Description

This change makes sure that we never emit a `PathEvent::Established` for
a path that has already been abandoned.

It may happen that a PATH_RESPONSE frame which validates a path is
received after a PATH_ABANDON frame was received for the same path. In
this case, before this fix we used to emit a `PathEvent::Established`
*after* a `PathEvent::Abandoned`. This ordering makes no sense for
applications, and it makes state tracking e.g. for iroh difficult and
error-prone.

This PR fixes this by not emitting `PathEvent::Established` for paths
that are already abandoned. Comes with a regression test that fails
without the fix.


## Change checklist
<!-- Remove any that are not relevant. -->
- [x] Self-review.
- [x] Documentation updates following the [style
guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text),
if relevant.
- [x] Tests if relevant.

---------

Co-authored-by: Diva Martínez <26765164+divagant-martian@users.noreply.github.com>
2026-06-10 07:34:54 +00:00
Diva Martínez 7ae29bd2a7 refactor(proto): simplify AddressDiscovery::Role (#693)
## Description

This is simply a LOC reduction PR. This is a change the quinn
maintainers
wanted and I finally got to do it for when we decide to upstream this.
Simple
refactor, nothing fancy. Just less lines of code.

## Breaking Changes


## Notes & open questions


## Change checklist
- [x] Self-review.
- [x] Documentation updates following the [style
guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text),
if relevant.
2026-06-09 08:04:39 +00:00
Floris Bruynooghe 9764042f8f fix(multipath): Store abandoned paths in ArrayRangeSet (#691)
## Description

This stores the abandoned paths in an ArrayRangeSet, which results
that the memory is bounded by the number of allowed concurrent
multipath paths.

## Breaking Changes


## Notes & open questions

Mainly for comparison with #688.

- ~~AbandonedPaths::len is kind of sad, needing to construct a full
  iter. Could be optimised to iterate over the ranges only and doing
  the sums of each range end - start. That's probably reasonable.~~
  done now.
  
- ~~All the casts to u32 are a bit unfortunate, but they are correct I
  think.~~ They are gone now that the ArrayRangeSet can contain a u32.
  
- ~~The ARRAY_RANGE_SET_INLINE_CAPACITY (2) is not really a suitable
  value for this use. It'll almost always allocate in practice. This
  could probably be fixed by making it a const generic so we can pick
  a different value for the use here.
  Doing this might give this version an advantage?~~
  This is now done.

- ~~Making the ArrayRangeSet generic over u32 (or PathId) or u64 would
  save space in the inline vec. But some traits range depends on are
  not public or unstable and it's a bit tricky.~~
  done

Question is, which version would we prefer? I've not benchmarked these
against each other, it's hard to say which would be better and
probably doesn't really matter. Memory and allocation wise I don't
think it makes much difference, I think only CPU might be interesting
but suspect it doesn't matter much.

It is kind of nice that the compaction only needs to exist once. I
think this version is more complex but also it's already written. So
I'm undecided.

I'll still add tests, both versions need them anyway.

## Change checklist

- [x] Self-review.
- [x] Documentation updates following the [style
guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text),
if relevant.
- [x] Tests if relevant.

---------

Co-authored-by: Diva Martínez <26765164+divagant-martian@users.noreply.github.com>
2026-06-08 14:11:20 +00:00
Floris Bruynooghe 9eb172965d fix(bbr3): Call on_packet_sent when ACK-eliciting data is sent (#689)
## Description

It seems this was missed when this was ported from the upstream
PR. This is different from #657 which was doing this unconditionally,
regardless when the packet needed to be accounted for congestion
control or whether it was ack-eliciting.

I did not continue the tests. It's something extremely specific and
artificial that is being tested. It's like testing everything with a
mock, I'm not sure the value it provides is worth all the stuff that
is setup. Ideally we figure out a better way to test that congestion
controllers actually behave correctly sometime.

## Breaking Changes

none

## Notes & open questions

Replaces #657, I considered taking it over. But this way I can't
approve my own PR.

## Change checklist

- [x] Self-review.
2026-06-03 16:14:12 +00:00
Floris Bruynooghe cc8822e288 tests: Add tests for easy-hard NAT combinations (#685)
## Description

We should be able to traverse NATs with a combination of a "very easy"
and "hard" NAT. Adjust the SimpleFirewallRouting to allow emulating
that behaviour.

Verified by reverting c20a684349 and
then test_hard_nat_server_opens_path fails.

## Breaking Changes

none

## Notes & open questions

Look how simple this was. #675 was a red herring.

## Change checklist

- [x] Self-review.
- [x] Documentation updates following the [style
guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text),
if relevant.
- [x] Tests if relevant.

---------

Co-authored-by: Philipp Krüger <philipp.krueger1@gmail.com>
2026-06-03 10:39:55 +00:00
Floris Bruynooghe 33dba68d36 refactor(tests): Introduce ConnPairBuilder (#680)
## Description

This introduces a ConnPairBuilder to construct the ConnPair and
switches everything over to use it. The individual ConnPair
construction methods were becoming unwieldy.

There are probably a few style arguments to be had, but the current
code doesn't look to bad. We can keep tweaking this.

## Breaking Changes

none

## Notes & open questions

split off from #675 and taken a bit further.

## Change checklist

- [x] Self-review.
- [x] Documentation updates following the [style
guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text),
if relevant.

---------

Co-authored-by: Diva Martínez <26765164+divagant-martian@users.noreply.github.com>
2026-06-01 12:01:44 +00:00
Floris Bruynooghe 7d5f1abf9d refactor(tests): Do not allow dead code by default (#678)
## Description

This really surprised me that this wasn't calling me out on dead
code. I don't think this is very helpful.

ConnPair has loads of dead code, I assume if we'd convert all the Pair
usage to ConnPair they would go away so that is kind of intentional?
So I allowed it just there for now.

## Breaking Changes

none

## Notes & open questions

none

## Change checklist

- [x] Self-review.
2026-05-29 15:42:10 +00:00
Floris Bruynooghe c706ab2abe refactor(tests): Do not use so many star imports (#677)
## Description

This removes all the star-imports from the tests, making all the
imports much more explicit.

I guess tests/mod.rs' `use super::*` is the equivalent of the usual
test layout, but them not being in the same file makes it really hard
to follow imports. And implicitly relying on imports of 3rd party
crates via star imports is not very nice. So I think it being in a
separate file justifies not using the `use super::*`.

## Breaking Changes

none

## Notes & open questions

I know it is opiniated. I stumbled upon this because I was looking why
dead code was not being flagged in tests. Stand by for another PR...

## Change checklist

- [x] Self-review.
2026-05-29 15:41:20 +00:00
Friedel Ziegelmayer cf013f8479 test(proto): add failing proptest seed from CI (#609)
Found in #606

---------

Co-authored-by: Philipp Krüger <philipp.krueger1@gmail.com>
2026-05-29 12:26:51 +00:00
Philipp Krüger ba24e6be09 feat(proto): Implement backoff for on-path path challenges (#676)
## Description

This implements a simple backoff algorithm for resending the
PATH_CHALLENGEs that are sent on-path, very similar to the backoff
mechanism in `pto_time_and_space`.

Fixes the proptest failure in #609 

## Notes & open questions

This is one way to "fix" on-path path challenges.

I did look at what a version would look like that perhaps tried to use
`Retransmits` or the `LossDetection` timer, but (1) `Retransmits` is
used across all paths and so far only used for data that can be
transmitted over any valid path (speaking of the data space-kind only),
and the `LossDetection` timer is specifically about detecting loss via
acknowledgements, not about detecting loss from missing
`PATH_RESPONSE`s. It's possible that a `PATH_CHALLENGE` is ACKed, but
needs to be retransmitted, because the ACK was sent over a different
path than the `PATH_RESPONSE`, and the `PATH_RESPONSE` got lost.
We *could* look into a system that resends both `PATH_CHALLENGE` as well
as `PATH_RESPONSE`, but then you'd need to retransmit the
`PATH_RESPONSE` using the same token, which would invalidate RTT
estimation based on path challenges.
All in all, keeping our own timer and roughly duplicating the backoff
algorithm from `pto_time_and_space` seems like the most reasonable move
forward.

## Change checklist
- [x] Self-review.
- [x] Documentation updates following the [style
guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text),
if relevant.
- [x] Tests if relevant.
- [x] All breaking changes documented.
2026-05-29 11:56:10 +00:00
Philipp Krüger 616dc26446 test(proto): Take Transmit::src_ip into account for ManyToManyRouting (#673)
## Description

This changes the `ManyToManyRouting::route_client_to_server` and
`ManyToManyRouting::route_server_to_client` functions to take
`Transmit::src_ip` into account.

`Transmit::src_ip` is set when noq-proto wants to pin the traffic sent
to a specific interface. In that case, `src_ip` will be the IP address
of the local interface that's to be used to send to the remote.
If the routing table changes in between noq-proto assigning a local IP
for a path (and thus choosing a `src_ip`) and sending again - the
`src_ip` may be different from what the `ManyToManyRouting` table would
use otherwise.
In those cases, `src_ip` should force staying on the same 4-tuple.

We also check that the route even exists. It's possible that the
`ManyToManyRouting` table is modified in a way that will cut the network
path on a certain 4-tuple. In that case, we need to return
`RoutingDecision::Drop`.
Thus the logic is the following:
- Look for all local interfaces that are connected to given destination
socket address (this may be multiple)
- Filter all these local interfaces for the interfaces with an IP
address matching our `src_ip`.
- Choose the first local interface among those interfaces as our route.

That local interface address will then be the remote that the other side
will see for the incoming `Transmit`.

This is just some effort in making the `ManyToManyRouting` table a
little less weird. Otherwise it would represent a weird OS that
completely ignores the `src_ip` setting.

## Change checklist
<!-- Remove any that are not relevant. -->
- [x] Self-review.
2026-05-27 07:06:30 +00:00
dignifiedquire c80da25006 chore: Release noq-proto-v1.0.0-rc.1 noq-udp-v1.0.0-rc.1 noq-v1.0.0-rc.1 2026-05-26 16:26:39 +02:00
Floris Bruynooghe a303cfa60e feat(proto)!: Make server handshake migration configurable
This makes the mechanism that allows the server to migrate during the
handshake configurable. The default is false, which is standards
compliant.
2026-05-22 16:46:37 +02:00
Floris Bruynooghe c20a684349 fix(qnt): Do not discard successful probes for unknown remotes (#672)
## Description

When we receive a successful probe response from an unknown remote
that only means the remote managed to challenge us from that
remote. It is not because this remote was not advertised in an
ADD_ADDRESS frame that it should be ignored.

This now successfully opens paths if the server is behind a
Desitnation Endpoint Dependent NAT.

Replaces #647 

## Breaking Changes

n/a

## Notes & open questions

I *really* wanted to have tests for this in proto, but they will come
later. In the meantime I'll point the patchbay tests from
https://github.com/n0-computer/iroh/pull/4254 to this PR which will
test this.

## Change checklist

- [x] Self-review.
- [x] Documentation updates following the [style
guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text),
if relevant.
- [x] Tests if relevant.
2026-05-22 13:50:00 +00:00
Floris Bruynooghe abb7cbdb7d refactor(tests): Remove TestEndpoint::addr (#666)
## Description

The RoutingTable is in charge of which addresses a TestEndpoint
has. So now all decisions are made there remove the addr field and
clean up any remaining usages. This should avoid confusion in the
future.

## Breaking Changes

n/a

## Notes & open questions

Maybe I'm finally done with cleaning up existing test infra. I'm
medium enthusiastic about this whole thing. I think it's an
improvement but there's definitely some things that look more complex
now. And some of the tests still do just plain weird stuff. Like when
opening a 2nd path we should now probably have a router that has a 2nd
path. But it was originally written without any router so cheated by
disconnecting the first path and relying on not reaching the
keep-alive interval or idle-timeout of the first path. So those are
still further improvements. But I need to work towards writing my
test, so I'll stop here.

## Change checklist

- [x] Self-review.
- [ ] Tests if relevant.
2026-05-22 11:53:46 +00:00
Franz Heinzmann a635174133 refactor(noq-proto)!: Remove identity_hash from public API (#646)
## Description

Based on #643

Removes `identity_hash` from the public API of `noq_proto`.

## Breaking Changes

* changed: `noq_proto::PathId` no longer
implements`identity_hash::IdentityHashable`

## Notes & open questions

Not sure if it's worth it?

## Change checklist

- [x] Self-review.
- [x] All breaking changes documented.

---------

Co-authored-by: Philipp Krüger <philipp.krueger1@gmail.com>
2026-05-22 11:05:42 +00:00
Philipp Krüger be30bc5e24 feat(proto): Mark PathEvent as #[non_exhaustive] (#648)
## Description

This is for future-proofing the API.

Closes #642 

## Breaking Changes

- `enum PathEvent` and all of its cases are now marked as
`#[non_exhaustive]`

## Notes & open questions

I'm ignoring the `_ =>` case, as anything else would mean we would "do
something" in case e.g. noq is bound against a newer noq-proto.
In `#[cfg(test)]` however, I panic, as in that case the version we
depend on must be up to date.

## Change checklist
<!-- Remove any that are not relevant. -->
- [x] Self-review.
2026-05-21 14:42:40 +00:00