Skip to content

feat: support Windows TUN mode - #102

Open
kitty-eu-org wants to merge 5 commits into
cfal:masterfrom
kitty-eu-org:pr-win-tun
Open

kitty-eu-org wants to merge 5 commits into
cfal:masterfrom
kitty-eu-org:pr-win-tun

Conversation

@kitty-eu-org

Copy link
Copy Markdown

Added TUN mode support for Windows to enable transparent proxying. This implementation covers interface creation and basic routing setup.

@happytrudy

Copy link
Copy Markdown

Has there been any progress on this issue?

@cfal cfal left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

i think this PR was mainly written by AI without quite understanding the consequences - and i can't tell whether the tun changes are correct and if they break anything on Linux which was just recently fixed. I'm curious if the entire event loop in run_tun_server actually needs to be changed to support Windows.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

any reason for rename?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The tun-rs library now supports asynchronous mode, so I want to convert all the code to be uniformly asynchronous. Additionally, Windows does not support file descriptors (fd), so I implemented a VirtTunDeviceand channel by referring to the implementation in shadowsocks-rust. This unavoidably leads to substantial changes to your original code.

@kitty-eu-org

Copy link
Copy Markdown
Author

i think this PR was mainly written by AI without quite understanding the consequences - and i can't tell whether the tun changes are correct and if they break anything on Linux which was just recently fixed. I'm curious if the entire event loop in run_tun_server actually needs to be changed to support Windows.

I have indeed used AI to write a lot of code, but I have tested it on Windows and confirmed that tun works properly. It has also been tested on Linux and works correctly

AYastrebov added a commit to AYastrebov/shoes that referenced this pull request Aug 24, 2026
The control API landed, so the roadmap gains a section for what it was
the first step of: seven sub-projects, one merged and six open, in
roughly the order they unblock each other.

Recorded there rather than only in the spec, because two of them are
decisions someone will otherwise make twice. Windows TUN does not exist
-- src/lib.rs gates the module on cfg(unix), so run_prepared returns
Unsupported and a Windows GUI would fail at the Connect button -- and
upstream PR cfal#102, which implements it, is not adoptable: it converts the
stack to AsyncDevice, deleting the dedicated-thread design MOBILE.md's
tuning rests on, and removes the dead-descriptor guard at
tcp_stack_direct.rs:620. macOS, by contrast, needs no new Rust at all.

Also notes that shoes deliberately configures no host routes or DNS, so
each privileged helper owns that -- four different mechanisms on Linux --
and updates Tier 2 #6: the live connection count shipped, and the rest is
blocked on outbounds having no name to key per-server bytes on.
AYastrebov added a commit to AYastrebov/shoes that referenced this pull request Aug 26, 2026
The control API landed, so the roadmap gains a section for what it was
the first step of: seven sub-projects, one merged and six open, in
roughly the order they unblock each other.

Recorded there rather than only in the spec, because two of them are
decisions someone will otherwise make twice. Windows TUN does not exist
-- src/lib.rs gates the module on cfg(unix), so run_prepared returns
Unsupported and a Windows GUI would fail at the Connect button -- and
upstream PR cfal#102, which implements it, is not adoptable: it converts the
stack to AsyncDevice, deleting the dedicated-thread design MOBILE.md's
tuning rests on, and removes the dead-descriptor guard at
tcp_stack_direct.rs:620. macOS, by contrast, needs no new Rust at all.

Also notes that shoes deliberately configures no host routes or DNS, so
each privileged helper owns that -- four different mechanisms on Linux --
and updates Tier 2 #6: the live connection count shipped, and the rest is
blocked on outbounds having no name to key per-server bytes on.
AYastrebov added a commit to AYastrebov/shoes that referenced this pull request Aug 28, 2026
The smoltcp loop moves from tcp_stack_direct.rs to stack_common.rs behind
a StackDevice trait and a shared StackHandle manager, unchanged in
behaviour; the Unix backend keeps its descriptor and poll(), and
tcp_stack_wintun.rs drives a wintun ring-buffer session - dedicated
thread, batching and the dead-device backstop preserved - waiting on the
session's event handles and woken at shutdown through Session::shutdown(),
the way wireguard-go does it. shoes creates and configures the adapter
itself (wintun_device.rs): deterministic GUID per name so Windows reuses
one network profile, ring sized as wireguard-go's, and wintun.dll's
signature verified before it is loaded. Routes and DNS stay the host's:
`destination` is refused on Windows because the wintun configuration path
would turn it into a system default route, and the adapter address must be
IPv4 because wintun-bindings' netsh arm cannot express IPv6. Upstream PR
cfal#102 was read for which knobs matter and deliberately not
adopted - it deletes the dedicated-thread design and the busy-loop
backstop, both kept here.

Making the full test suite run on Windows (the CI job was check-only)
surfaced two pre-existing Windows defects, both fixed: QUIC inbounds
panicked at startup on SO_REUSEPORT, which does not exist there - they now
bind one endpoint and say so when reducing a configured num_endpoints -
and the hysteria2/TUIC UDP relays lost all their IPv4 traffic because
Windows defaults IPV6_V6ONLY on where Linux defaults it off; dual-stack
sockets now request it explicitly.

A live end-to-end run on Windows 11 (fake-IP DNS in 6-12 ms, TLS with SNI
restored from the fake address, 20 MB at ~35 MB/s, UDP relay, ICMP, clean
teardown) then caught two defects that were never Windows-specific, fixed
everywhere: a UDP response queued while the stack thread was idle waited
out the 1 s poll timeout because nothing woke the thread on enqueue - the
response path now carries a waker, a byte down the wake pipe on Unix and
an auto-reset event in the Windows wait - and icmp_enabled never actually
answered pings, because smoltcp's echo replies sit behind its
auto-icmp-echo-reply feature, now enabled.

Co-Authored-By: Claude Fable 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.

3 participants