Repository navigation
build: merge upstream main and require tokio 1.54 - #4
Merged
Merged
Conversation
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>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 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".
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
write_packet_to_device.How our fixes were reconciled
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.6e1ce25): not fixed upstream, so ours stays, now with a test that fails without it.Other changes
build.rs: create<profile>/examplesbefore copyingwintun.dll; on Windows targets the copy failed when that directory did not exist, which brokecargo checkfor 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