Skip to content

fix(desktop): tunnel root traffic; escape the core by fwmark, not uid - #216

Merged
loss-and-quick merged 2 commits into
mainfrom
fix/desktop-root-traffic-bypasses-tun
Jul 13, 2026
Merged

fix(desktop): tunnel root traffic; escape the core by fwmark, not uid#216
loss-and-quick merged 2 commits into
mainfrom
fix/desktop-root-traffic-bypasses-tun

Conversation

@loss-and-quick

@loss-and-quick loss-and-quick commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Summary

On desktop the daemon/core stack runs as root, and the neutral sing-box builder baked exclude_uid: [0] into every native tun. That is an Android-ism (the root daemon and core must not be captured by their own tun) — on a Linux desktop the same rule (ip rule: from all uidrange 0-0 goto <nop>) exempts every root process on the host from the VPN. Concretely: on NixOS, nix-daemon (root) dialed the blocked network directly and timed out while the tunnel was up.

Fix, per-layer:

  • builder (kasumi-core) — emits only the app-filter uids in exclude_uid; whether uid 0 skips the tun is now a platform decision.
  • Androidtune_config prepends uid 0 back on every capture-all tun; the wire config is unchanged (regression-tested).
  • Linux desktop — the core's own egress escapes by fwmark instead of by uid: prepare_singbox_config stamps route.default_mark on the config, and the data-path installs ip rule … fwmark <mark> goto 32766 ahead of the auto_route rules, swept with the auto_route orphans on teardown. Marked traffic (server uplink, geo-direct dials) leaves via the physical default route; everything unmarked — any uid, root included — is tunnelled. This mirrors sing-box's own AutoRedirectMarkMode escape (sing-tun tun_linux.go), but works with the gvisor stack.
  • Windows — untouched: exclude_uid never built rules there (everything already tunnelled) and no mark is injected off-Linux (route.default_mark is rejected by sing-box on non-Linux).

The bridged path (xray / external tun engines) already tunnels root and escapes via the uplink socket bind — no change.

Affected layer

  • frontend/ — React Web UI
  • crates/ · src-tauri/ — Rust core / backend / Tauri desktop
  • module/ — Android installable zip (thin launcher over the Rust daemon)
  • scripts/ — build / release helpers
  • CI / .github/
  • Docs only

Verification

Rust (crates/ · src-tauri/):

  • cargo fmt --all --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace — one failure: net::tests::tcp_ping_fails_on_unreachable_host, a known environment flake (an active VPN tun on the dev machine captures the TEST-NET-1 connect and it "succeeds"); kasumi-backend is not touched by this PR and the test fails identically on main under the same conditions
  • Codegen drift: cargo run -p kasumi-desktop --bin codegen leaves git clean

New tests: builder emits no exclude_uid without an app filter / never bakes uid 0 (tun_uid_exclusion_is_app_filter_only), Android tune restores the exact previous shape incl. system-stack auto_redirect and leaves allowlisted/force tuns alone (tune_config_excludes_root_from_capture_all_tuns), Linux mark injection (escape_mark_lands_in_route_on_linux).

Checklist

  • Title is a scoped Conventional Commit; commits are logically split
  • No build artifacts committed (module/bin/<abi>/, geoip/geosite, built module/webroot/, src-tauri/gen/ — all gitignored on purpose)
  • Generated frontend/src/generated/ was regenerated from Rust, not hand-edited
  • If user-visible strings changed: i18n/en.ts and every locale file updated (no partial translations)
  • Renames touching the project id were grepped in all case forms (kasumi-proxy, Kasumi Proxy, camelCase)

Notes for reviewers

  • Diagnosis is directly observable on an affected host: ip rule shows 9000: from all uidrange 0-0 goto 9010 while the native tun is up — that rule is the uid-0 exemption this PR removes.
  • Upstream check (sing-box v1.13.13 / sing-tun tun_linux.go): in the default auto_route mode the only uid-independent self-escape is the mark mode's fwmark <output_mark> goto rule; bind_interface is not an option on this path (it would defeat auto_detect_interface, see outbound_bind.rs). Hence fwmark, not a socket bind.
  • goto 32766 rather than lookup main: if the main lookup fails (uplink flap) evaluation must not fall through into the auto_route rules below and loop the marked traffic. On a host where the 32766 main rule itself was deleted (some VPNs do), the kernel skips the unresolved goto — a backstop unreachable rule at 8991 for the same mark then hard-fails marked traffic instead of letting it loop.
  • The escape mark 0x4b53 is distinct from sing-tun's own auto-redirect marks (0x20230x2025); the rule priority 8990 sits just below SINGBOX_MAIN_RULE_PRIO (9000).

@loss-and-quick
loss-and-quick force-pushed the fix/desktop-root-traffic-bypasses-tun branch from f47956c to 0892938 Compare July 9, 2026 06:00
@github-actions github-actions Bot added platform:android Android (root module) platform:linux Linux desktop (Tauri) platform:windows Windows desktop (Tauri) backend Rust core/backend/desktop (crates/, src-tauri/) labels Jul 9, 2026
@loss-and-quick
loss-and-quick force-pushed the fix/desktop-root-traffic-bypasses-tun branch from 0892938 to 4dc0791 Compare July 9, 2026 14:32
…policy

exclude_uid=[0] in the neutral builder is an Android-ism (its root daemon
and core must not be captured); on desktop it exempted every root process
(e.g. nix-daemon) from the VPN. The builder now emits only app-filter uids;
Android's tune_config prepends uid 0 back — wire config unchanged.
Replaces the uid-0 escape: route.default_mark stamps the core's egress,
and an ip rule (fwmark → goto 32766) above the auto_route rules diverts it
to the physical default route; everything unmarked, root included, is
tunnelled. goto, not lookup main, so a failed main lookup can't fall back
into auto_route and loop. Installed before the core spawns; swept with the
auto_route orphans on teardown. Windows untouched.
@loss-and-quick
loss-and-quick force-pushed the fix/desktop-root-traffic-bypasses-tun branch from 4dc0791 to 64286d3 Compare July 13, 2026 19:14
@loss-and-quick
loss-and-quick merged commit a4559ea into main Jul 13, 2026
10 checks passed
@loss-and-quick
loss-and-quick deleted the fix/desktop-root-traffic-bypasses-tun branch July 13, 2026 19:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Rust core/backend/desktop (crates/, src-tauri/) platform:android Android (root module) platform:linux Linux desktop (Tauri) platform:windows Windows desktop (Tauri)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant