feat(desktop): Windows support (wintun tun + routing, UAC, portable) - #52
Merged
Conversation
loss-and-quick
force-pushed
the
feat/windows-support
branch
4 times, most recently
from
June 20, 2026 21:56
a19ed6e to
f7c1dcb
Compare
Closed
2 tasks
loss-and-quick
force-pushed
the
feat/windows-support
branch
from
June 21, 2026 11:37
d0264b8 to
134680a
Compare
proc.rs was POSIX-only: pid matching read /proc/<pid>/exe dev/ino and termination went through libc::kill. Split the OS-specific half into a cfg-gated `imp` module so the crate builds on Windows too: - POSIX keeps the /proc inode match + SIGTERM/SIGKILL teardown. - Windows matches a pid by its full image path (QueryFullProcessImageNameW) and ends it with TerminateProcess; there is no SIGTERM, but the wintun driver reclaims a core's adapter on process exit, so the hard kill stays clean for a graceful stop. run/silent/spawn_logged/read_pidfile were already portable and stay shared. libc moves under cfg(unix); windows-sys (Win32 Threading + Foundation) comes in under cfg(windows). Shell/proc-dependent unit tests are gated to unix.
The `-fwmark` flag is a Linux SO_MARK feature (an `ip rule` keeps the marked upstream socket out of the tunnel). Windows has no fwmark — its server bypass is a host route — so `spawn_tun2socks` now takes `Option<u32>` and omits the flag when `None`. Linux/Android callers pass `Some(FWMARK)` unchanged.
The desktop module was Linux-only and compiled unconditionally. Split it so
each OS owns its data-path while the OS-neutral parts stay shared:
- desktop/{linux,windows}/ each provide a `DesktopPlatform`, re-exported by
cfg(target_os) in desktop/mod.rs.
- The DNS/address helpers and the xray-config server parsing (collect servers
-> resolve -> bypass CIDRs) move into the shared desktop/net.rs; sing-box
config finalisation moves into shared desktop/singbox.rs (pure JSON). Each
routing back-end only supplies its OS-specific resolver source (/etc/resolv.conf
vs Get-DnsClientServerAddress).
Windows mirrors the Linux model: sing-box drives its own wintun tun
(auto_route); the xray path bridges a wintun device through tun2socks and
installs the bypass + split-default by hand via `route`/`netsh`, reading the
default route and adapter index through Get-NetRoute/Get-NetAdapter. Traffic
counters are stubbed (0/0) until iphlpapi is wired; the tun stack is "system"
(wintun), not gvisor. The uplink monitor polls the default route instead of
`ip monitor route`.
Paths honour a portable layout: a `portable.dat` marker next to the exe pins
all state beside the app (run-from-anywhere, nothing in %APPDATA%/registry);
installed builds fall back to %APPDATA%/%LOCALAPPDATA%.
Elevation gains a Windows arm: UAC via ShellExecuteW(runas) + IsUserAnAdmin,
mirroring the Linux pkexec whole-process re-exec. libc moves under cfg(unix);
windows-sys (UI Shell) comes in under cfg(windows).
sing-box's tun inbound and tun2socks both dlopen wintun.dll from the app directory, so the Windows desktop bundle needs it next to the cores. fetch-cores-desktop.sh now pulls the pinned wintun build (new WINTUN_VERSION in core-versions.sh) and stages wintun.dll without a target suffix — it ships as a Tauri bundle resource (placed next to the exe), not an externalBin sidecar, since those only handle executables.
Add a windows-latest matrix row to the desktop job so each nightly also produces the Windows installers. wintun.dll is declared as a Tauri bundle resource (placed next to the exe) in tauri.windows.conf.json. Artifact collection now also picks up the NSIS setup .exe and .msi, and a Windows-only step assembles a portable zip (app exe + suffix-stripped cores + wintun.dll + a portable.dat marker) for run-from-anywhere use. Release promotion is deferred until the bundle is validated on real hardware.
The `rust` job runs on Linux and never compiles the #[cfg(windows)] Platform + Win32 code, so a Windows-build break would only surface in the next nightly. Add a desktop-windows job mirroring desktop-linux (compile-only, no bundle) so it's caught in the PR. The Android daemon needs no equivalent: it has no cfg(target_os) gates, so the workspace `rust` job already compiles it per-PR.
Verified against both cores' sources: sing-box embeds wintun.dll (go:embed, loaded from memory) so its tun comes up without a DLL on disk, while tun2socks loads it via LoadLibraryEx(LOAD_LIBRARY_SEARCH_APPLICATION_DIR) — i.e. next to the exe. So the earlier "both need it" assumption was wrong: - start_singbox no longer fails when wintun.dll is absent. - start_xray still requires it (the tun2socks path genuinely does). - capabilities.tun is now always true on Windows (sing-box can always tun); the xray path's DLL requirement is enforced at start instead. The bundled wintun.dll stays — it's what the tun2socks/xray path loads.
tauri-build validates declared bundle.resources at compile time, so the desktop-windows job (which doesn't fetch cores) failed with "resource path binaries\wintun.dll doesn't exist". Drop a placeholder before the compile-only build; the real wintun.dll is staged by fetch-cores for the nightly/release bundle.
Windows desktop is now a supported target; reflect it in the README platform badge alongside Android and Linux.
A GUI process spawning a console-subsystem child on Windows flashes a black console window for each spawn (tauri-apps/tauri#13230). The Windows data-path spawns many — powershell (Get-Net*), route, netsh, and the xray/sing-box/ tun2socks cores — so Start/Stop would blink consoles repeatedly. Set CREATE_NO_WINDOW on every child spawned through proc.rs (run + spawn_logged); a no-op on other platforms.
With a second `[[bin]]` (`codegen`) in the crate, `cargo tauri build` picks the alphabetically-first binary (`codegen` < `kasumi-desktop`) as the app and bundles THAT (tauri-apps/tauri#4807, #13420). The shipped `kasumi-desktop.exe` was actually `codegen`, which unconditionally runs `export_generated()` → panics writing to a missing build-tree path, and (a plain bin without `main.rs`'s `windows_subsystem` attribute) runs as a console app → black window. Pin the app binary with `default-run = "kasumi-desktop"` so the bundle ships the real app.
Spawns cleared the child env and set only our own vars. On Windows that drops
SystemRoot/windir, without which ws2_32 can't load its Winsock provider catalog,
so xray/sing-box failed to open any socket ("The requested service provider could
not be loaded or initialized"). Clear only on POSIX (the Android/Linux isolation
intent); on Windows overlay our vars onto the inherited environment.
current_exe/%APPDATA% give backslashes, so joining sub-paths with `/` produced mixed paths like `C:\…\Kasumi-Proxy/kasumi-proxy\singbox.log`. Build with `\`.
loss-and-quick
force-pushed
the
feat/windows-support
branch
from
June 21, 2026 13:21
d07e029 to
20ead8d
Compare
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.
Summary
Brings Windows back as a desktop target (it was dropped because the backend and the
desktop data-path were POSIX-only). One product, one data-path shape on every desktop OS —
Windows mirrors the Linux model rather than reinventing it.
What was POSIX-only, and how it's split now:
crates/kasumi-backend/src/proc.rs— process identity. POSIX matched a pid by/proc/<pid>/exedev/ino and killed vialibc::kill; Windows now matches by full imagepath (
QueryFullProcessImageNameW) and ends viaTerminateProcess, behind acfg-gatedimpmodule.run/silent/spawn_logged/read_pidfilestay shared.src-tauri/src/desktop/— was Linux-only and compiled unconditionally. Split intodesktop/{linux,windows}/, each providing aDesktopPlatformre-exported bycfg(target_os). The DNS/address helpers, the xray-config server parsing, and thesing-box config finalisation move into the shared
desktop/net.rs+desktop/singbox.rs.Windows data-path (same model as Linux): sing-box drives its own wintun tun
(
auto_route); the xray path bridges a wintun device through tun2socks and installs theserver-bypass + split-default by hand via
route/netsh, reading the default route andadapter index through
Get-NetRoute/Get-NetAdapter.spawn_tun2socksdrops-fwmarkonWindows (a Linux SO_MARK feature — the bypass is a host route).
No console flashing. A GUI process spawning a console-subsystem child pops a black
console window on Windows (tauri-apps/tauri#13230). The data-path spawns many — powershell,
route,netsh, and the cores — soCREATE_NO_WINDOWis set on every child spawnedthrough
proc.rs. (Combined with the app's own windowed subsystem, fixed separately in #55.)Elevation: a Windows arm — UAC via
ShellExecuteW(runas)+IsUserAnAdmin, mirroringthe Linux pkexec whole-process re-exec.
Portable build: a
portable.datmarker next to the exe pins all state beside the app(run-from-anywhere, nothing in
%APPDATA%/registry); installed builds (NSIS/MSI) fall backto the roaming/local profile dirs.
Packaging/CI:
fetch-cores-desktop.shpulls the pinnedwintun.dll(tun2socks loads itfrom disk; sing-box embeds its own), declared as a Tauri bundle resource next to the exe.
The nightly desktop job gains a
windows-latestmatrix row producing the NSIS/MSI installersplus a portable zip, and a
desktop-windowscompile-smoke runs per-PR. Release promotion(
release.yml) is deferred until the bundle is validated on real hardware.Affected layer
frontend/— React Web UImodule/— Magisk/KernelSU/APatch payloadscripts/— build / release helpers.github/Verification
cargo fmt --all --check,cargo clippy --workspace --all-targets -- -D warnings,cargo test --workspace— clean (Linux, nix devshell). Codegen drift gate empty.cargo clippy -p kasumi-backendandcargo clippy -p kasumi-desktopclean againstx86_64-pc-windows-gnu, exercising every#[cfg(windows)]path. The msvc bundle + on-hardware run go through the nightly job.actionlintclean;shellcheck -s bashclean.Checklist
Notes for reviewers
as a pre-socks upstream; that would split the data-path shape between OSes, force the
backend to build a tun-shim config, and run two cores per xray profile. This PR keeps one
tun2socks-based shape on both desktops.
wintun.dllfrom its own dir (LoadLibraryEx); sing-box embeds it (go:embed). So thebundled DLL is only needed for the xray/tun2socks path — enforced at
start_xray.and the uplink monitor polls the default route instead of an iphlpapi change callback.
branch is rebased on top.