Skip to content

feat: Windows support (TUN, service, named pipe) - #89

Merged
heidari-dev merged 13 commits into
mainfrom
feat/windows
Nov 14, 2026
Merged

heidari-dev merged 13 commits into
mainfrom
feat/windows

Conversation

@heidari-dev

@heidari-dev heidari-dev commented Nov 14, 2026 •

Copy link
Copy Markdown
Owner

Part of #15.

Kite runs on Windows: TUN mode through Wintun, the daemon as a service on a named pipe, and the desktop app on that pipe. The installer is the next PR.

What changes:

  • crates/kite-win (new, Windows only) holds the unsafe: a protected directory for the keys, the pipe with its access list, the IP Helper route calls, the service glue. Everything else keeps forbid(unsafe_code).
  • The daemon core is shared with Linux; daemon/sock.rs and daemon/pipe.rs hold what differs. The pipe lets in SYSTEM, administrators and the local group kite, refuses remote clients, and only an elevated administrator may pair. Clients open it at identification level.
  • TUN mode: four routes on the Wintun adapter (0.0.0.0/1, 128.0.0.0/1, ::/1, 8000::/1), a host route for the relay only when it is reached through the default route, a catch-all NRPT rule so DNS goes to 1.1.1.1 through the tunnel. kite restore and the service at start remove what a killed run left.
  • wintun.dll is never committed. scripts/fetch-wintun.ps1 pins the zip and the DLL by SHA-256; kite loads it by full path from its own directory and checks the signature.
  • No kill switch on Windows: --kill-switch is refused, the app hides the toggle. Tracked in Windows: kill switch #88.
  • Tray: Slint's own SystemTrayIcon (Windows notification area); ksni stays Linux-only. No new crate. The icon registers with the shell (Kite: Disconnected).
  • Service account: LocalSystem. Making the adapter and changing routes need administrator rights; no narrower account can, see the threat model. The installer will cut the token's privileges.
  • Dependencies: windows-sys 0.61 and windows-service 0.8 were already in the tree through tun and tun2proxy.

Memory (release build, Windows 11, no daemon running): tray only, 19.5 MB working set and 2.5 MB private; window open, 24.8 MB and 6.6 MB private.

Tested:

  • cargo test --workspace and clippy on Windows; the Linux build of kite-cli and the other crates checks. New tests: pipe access and a second first instance refused, the caller admin check, junction refused by private_dir, TUN state parsing, the daemon over a pipe (limits, bad requests, kill switch refused), the desktop client over a pipe.
  • CI job tun-windows runs scripts/tun-smoke.ps1 on a runner: it checks adapter, routes, DNS rule and the route to 1.1.1.1 while up, and that all of it is gone after kite disconnect and after the daemon was killed and kite restore ran.
  • Not run here: TUN mode with a phone. That is the acceptance test.

Docs: docs/cli.md, docs/threat-model.md (Windows rows and gaps), apps/desktop/README.md.

Smoke test notes:

  • The relay in the CI test runs on the same machine as kite, so the sockets it opens to the Internet enter the tunnel too and come back to it. That is why kite status shows over a hundred streams at 0 s: the runner's own traffic plus that loop. The connection from kite to the relay is not part of it; it goes to a loopback address, which no tunnel route covers, and setup adds no host route for loopback. With a real phone the relay is on another machine and there is no loop.
  • A disconnect used to be able to hang under that load. Fixed in the product: when the tunnel is told to end, the routes and the DNS rule come off at once, the tunnel's tasks get 4 s and are then dropped, and the runtime is dropped with a 2 s limit instead of waiting for a task that does not stop.

…utes

Windows-only crate that holds the unsafe calls: a protected directory for keys, the named pipe with its DACL and an administrator check on the caller, and the IP Helper route calls TUN mode needs.
… Wintun

The daemon core is shared with Linux; sock.rs and pipe.rs hold what differs (who may connect, who may pair). On Windows the pipe is closed to everyone but SYSTEM, administrators and the group kite, and only an elevated administrator may pair. TUN mode takes the two halves of the address space through a Wintun adapter, keeps the relay out of its own tunnel with a host route when it needs one, and sends DNS to the tunnel with a catch-all NRPT rule. kite restore removes all of it. No kill switch on Windows yet.
…pt, smoke test and docs

The window and the CLI talk to the service over the named pipe. The kill switch toggle is hidden and --kill-switch refused on Windows (issue 88). scripts/fetch-wintun.ps1 pins the Wintun zip and DLL by SHA-256. scripts/tun-smoke.ps1 checks what TUN mode changes and that it is all gone after a disconnect and after a killed daemon; CI runs it on a Windows runner.
…vel clients, cleanup at service start, signed Wintun

private_dir opens the directory without following links and works through that handle. The pipe clients no longer allow impersonation. The service removes a killed run's DNS rule when it starts. The group kite is looked up on this computer only. The server log is rate limited. wintun.dll must be signed by WireGuard LLC.
…asks behave

Once the tunnel is told to end, the routes and the DNS rule come off at once beside the wind-down, the tunnel's tasks get 4 s before they are dropped, and the runtime is dropped with a 2 s limit (it waited for ever on a task that did not stop). The disconnect wait on Windows allows for taking the DNS rule off through PowerShell.
…d the smoke test's relay refuses external targets

ipstack drops a TCP stream with block_in_place and waits for its task, which needs a worker; the one worker moves to a thread of the blocking pool for that time. The pool held 4 threads, so a flood of connections ending together left the worker stuck and the stop, its timers included, never ran. TUN mode now allows 512. relay-dev gets --refuse-external so the CI test's relay, which runs on the machine under test, no longer loops its own connections through the tunnel.
@heidari-dev
heidari-dev merged commit b2bef68 into main Nov 14, 2026
18 checks passed
@heidari-dev
heidari-dev deleted the feat/windows branch November 14, 2026 08:55
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.

1 participant