Skip to content

build: merge upstream main and require tokio 1.54 - #4

Merged
acking-you merged 14 commits into
devfrom
chore/upgrade-deps-2026-10
Oct 27, 2026
Merged

acking-you merged 14 commits into
devfrom
chore/upgrade-deps-2026-10

Conversation

@acking-you

Copy link
Copy Markdown
Owner

Merges upstream narrowlink/ipstack main (44bb34f) into the fork, for the dependency refresh in acking-you/proxy-everything. tun2proxy's matching PR checks out this branch.

Upstream changes now included

How our fixes were reconciled

  • Executor-blocking drop (e3c5645): upstream Close a dropped TCP session without blocking on its task narrowlink/ipstack#86 fixes the same bug (and adds the FIN on drop), so upstream's implementation is kept together with our regression test.
  • Half-close parked behind unacknowledged data (6e1ce25): not fixed upstream, so ours stays, now with a test that fails without it.
  • RST on aborted relays, stale-ACK reset, and the UDP fixes (datagram preservation, ICMP port-unreachable rejection) carry over unchanged.

Other changes

  • tokio requirement 1.53 → 1.54 (every other requirement already admits the latest release).
  • build.rs: create <profile>/examples before copying wintun.dll; on Windows targets the copy failed when that directory did not exist, which broke cargo check for dependents.

Verified (nightly-2026-10-26)

Full CI suite (42 unit + 23 doc tests); library clippy clean for x86_64-unknown-linux-gnu, x86_64-pc-windows-msvc, aarch64-apple-ios, aarch64-linux-android. proxy-everything builds warning-free against this branch and its TUN tests pass. Not built: Windows tests (criterion needs MSVC headers).

🤖 Generated with Claude Code

IntellyCode and others added 14 commits August 17, 2026 21:04
A session that goes quiet for longer than the configured timeout is
closed, which is the configured behaviour rather than a fault. The
surrounding lifecycle events already record at debug: a session
beginning, a session dropped, a session closed after 2MSL. A proxy
holding many idle connections reports every one of these twice at
warn, which is where an application looks for problems it can act on.
`IpStackTcpStream::drop` waited for the session's task to finish, through
`block_in_place` and `block_on`. `block_in_place` works by handing the worker's
core to the blocking pool, and the pool stops accepting work once the runtime
begins shutting down. A session dropped from that point on blocks its worker
forever: the core is never handed off, the task it waits for has no thread left
to run on, and the runtime's own shutdown waits for that worker in turn.

The farewell packet reaches the device through an unbounded channel, so it is
sent from the drop itself. The task is told to exit through the channel it
already watches, and aborted rather than awaited. Aborting drops the task's
`destroy_messenger`, and the watcher that removes the session treats a dropped
sender the same as a sent one, so the session still leaves the stack.
…k#90)

* Enforce the TCP receive window to bound per-session memory

The receive window ipstack advertises now holds. A segment beyond it is
dropped for the peer to resend, the head-of-line segment always admitted
so the stream advances. The handoff channel to the reader is bounded and
filled by reserving a slot before consuming, so buffered data leaves the
reassembly map only once it has a home; a reader that frees space wakes
the loop to flush more and the follow-up ACK carries the reopened window.
The window is advertised as zero below one segment, so a stalled reader
puts the peer into persist mode until space frees.

Consuming trims a stale head entry a re-segmented retransmission left
below the ack, and a FIN is accepted only once the data before it has
been consumed, so a full channel never strands the tail.

* Cover reserve-before-consume with an async test

The stream's first tokio test drives extract_data_n_write_upstream
against a full handoff channel: buffered data stays in the reassembly
map and the ack holds until the reader drains a slot, then the tail
flushes and the ack advances.
…wlink#91)

* Negotiate window scaling so a peer's window reaches past 65535

The SYN exchange settles a shift for each direction. A peer whose SYN
carries a window scale option gets one back in the SYN-ACK, and every
window it advertises afterwards is honoured shifted left by its count,
limited to the 14 the RFC allows and the 1 GiB that implies. The stack's
own shift is the smallest that expresses its read buffer in the 16-bit
field, zero for the 16 KiB default, and every window it advertises is
shifted down by it. The window field of a segment carrying SYN travels
unscaled in both directions.

A peer that offers no scaling leaves both shifts off and sees the raw
16-bit window it saw before.

The options a segment carries are assembled where its flags are known,
so create_raw_packet writes the list it is handed.

* Wake a writer held by a closed peer window and pin the SYN window

A writer held by a closed peer window is woken by the segment that
completes the handshake and by an established connection's data segments,
as every other segment that can reopen the window already does.

The window tests assert the SYN's window before any later segment
replaces it, the handshake test asserts the shift the SYN-ACK carries, and
the zero-window test uses a recording waker so it fails when a reopening
segment leaves the writer asleep. A read buffer too large for any shift is
limited to the maximum, with a warning.
* Retransmit on a timer and close the connection at R2

The session loop waits on the earliest deadline its inflight queue holds
and sweeps when it passes, so a lost segment with nothing behind it is
retransmitted on time rather than when the peer's own timeout recovers it.
A write that reopens an empty queue wakes the loop, since the deadline is
read on entry and a later segment always sits behind one already waited
on. The arrival path asks nothing about timeouts.

The timeout belongs to the connection. It doubles on every expiry up to a
minute, and an acknowledgement retiring a segment sent exactly once puts
it back to the configured value: only that retirement says which
transmission the acknowledgement answers.

A segment stays in the inflight queue until it is acknowledged. Reaching
R2 transmissions of the same segment closes the connection, per RFC 9293
§3.8.3, which at seven transmissions is 123 seconds against the at least
100 that SHLD-11 asks for.

* Restart the retransmission timer for every segment and keep the ceiling above the configured timeout

An expiry restarts the timer for every outstanding segment, so no segment
is retransmitted before the backed-off timeout has run from that expiry.
The backoff is bounded by the larger of MAX_RTO and the configured
timeout, so an expiry never shortens it.

* Close at R2 without resending, stop the timer after a reset, and floor the timeout at one second

* Reuse RTO as the timeout floor and read the deadline from the queue head
In-sequence data is acknowledged on arrival and held in a received
queue, which the reader drains through the bounded handoff channel. The
advertised window counts both the reassembly buffer and the queue, and a
segment is stored only up to the window's right edge. A FIN arriving in
sequence is acknowledged where it lands. A closing connection hands the
reader every stored byte before end-of-stream.
poll_write checked that the send buffer was not full, then sent the whole
buffer up to the MSS. The last segment could end past the right edge of the
peer window; the peer trims it and the tail has to be retransmitted.

Cut each write to the usable window: min(max_unacked_bytes, send_window)
minus the bytes in flight.

Co-authored-by: hypnosis <hypnosis@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The session loop built the `local { seq, ack }` string and the header
description for every packet, and poll_write another string for every
held write, whatever the log level. At the default level none of them is
ever printed, so every packet paid for an allocation and a format that
went unused. The context is now a value that formats itself when a log
line is emitted, and the header description is built only when trace is
enabled.
…transmissions, and keep segments inside the peer's window (narrowlink#97)

* Answer an out-of-order segment with a duplicate ACK

A data segment is now always acknowledged. One that fills no gap, because
it arrived out of order or was already received, used to leave nothing
for the reader and so produced no ACK at all, which left the peer's
retransmission timer as the only way past a lost segment. RFC 5681 §4.2
asks for an immediate duplicate ACK instead, and three of them are what
drive the peer's fast retransmit.

A segment carrying PSH is handled like any other: out of order, it used
to be dropped without an ACK, so the peer resent it on its timer and
backed off with every loss.

* Keep the new part of a retransmission that straddles the ack

A segment starting below the ack was dropped whole, even when it ran past
it. That happens when the peer re-segments a retransmission after an ACK
it missed: the bytes past the ack were then lost with the rest, and the
peer had to send them yet again. The segment is now trimmed to the part
that is new (RFC 9293 §3.10.7.4); one lying entirely below the ack is
still dropped as a duplicate.

* Keep each segment inside the peer's window

A write was held only once the data in flight had reached the peer's
window, and was then sent as a whole segment, so the last segment before
the window filled could run up to a segment past it. RFC 9293 §3.8.6.1
keeps what is in flight within the window. A segment now carries no more
than the room the window has left.

The write also copies only the bytes the segment carries. It used to copy
the caller's whole buffer and send one segment of it, so a large write
was copied again for every segment it produced.

---------

Co-authored-by: Sajjad Pourali <sajjad@pourali.com>
Brings in upstream's TCP work: timer-driven retransmission with R2 close,
window scaling, an enforced receive window with a bounded reader handoff,
duplicate ACKs for out-of-order segments, sends kept inside the peer
window, and a device EOF/error exit from the main loop.

Conflict resolution:
- Drop: upstream narrowlink#86 fixes the same executor-blocking drop as our
  e3c5645, and additionally sends the farewell FIN from the destructor.
  Take upstream's implementation; keep our regression test for a
  saturated blocking pool.
- Main loop: upstream moved retransmission onto a timer, so the
  per-packet retransmission sweep is gone. Our wake of a shutdown parked
  behind in-flight data (6e1ce25) is not covered upstream and stays,
  now with a test that fails without it.
- Tests: union of both suites; our abort test uses the new
  create_raw_packet options argument.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every other dependency requirement already admits its latest release
(etherparse 0.21, rand 0.10, thiserror 2, clap 4.6, criterion 0.8, tun 0.8).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cargo creates `<profile>/examples` only when it builds an example. A
dependent crate, or any `cargo check`, never does, so on Windows targets
the build script failed with NotFound before the crate compiled at all.
Current nightly cargo no longer creates the directory up front.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-27T08:39:27.251659Z 418ed41 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 418ed41ed9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/stream/tcp.rs Resolved
@acking-you
acking-you merged commit 418ed41 into dev Oct 27, 2026
4 checks passed
@acking-you
acking-you deleted the chore/upgrade-deps-2026-10 branch October 27, 2026 12:08
acking-you added a commit that referenced this pull request Oct 27, 2026
…n the MTU (#5)

Silly-window avoidance advertised a zero window until a full MTU of space was
free. With an MTU above TcpConfig::read_buffer_size (e.g. 32768 against the
default 16384) that never happens, so even the SYN-ACK of an empty connection
carried a zero window and normal data never flowed. Bound the threshold by
half the read buffer, as RFC 1122 4.2.3.3 does; with the default MTU 1500 and
16 KiB buffer the threshold, and the behaviour, are unchanged.

Raised in review of #4.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants