fix(desktop): tunnel root traffic; escape the core by fwmark, not uid - #216
Merged
Conversation
loss-and-quick
force-pushed
the
fix/desktop-root-traffic-bypasses-tun
branch
from
July 9, 2026 06:00
f47956c to
0892938
Compare
loss-and-quick
force-pushed
the
fix/desktop-root-traffic-bypasses-tun
branch
from
July 9, 2026 14:32
0892938 to
4dc0791
Compare
…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
force-pushed
the
fix/desktop-root-traffic-bypasses-tun
branch
from
July 13, 2026 19:14
4dc0791 to
64286d3
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
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:
kasumi-core) — emits only the app-filter uids inexclude_uid; whether uid 0 skips the tun is now a platform decision.tune_configprepends uid 0 back on every capture-all tun; the wire config is unchanged (regression-tested).prepare_singbox_configstampsroute.default_markon the config, and the data-path installsip rule … fwmark <mark> goto 32766ahead of theauto_routerules, swept with the auto_route orphans on teardown. Marked traffic (server uplink, geo-directdials) leaves via the physical default route; everything unmarked — any uid, root included — is tunnelled. This mirrors sing-box's ownAutoRedirectMarkModeescape (sing-tuntun_linux.go), but works with the gvisor stack.exclude_uidnever built rules there (everything already tunnelled) and no mark is injected off-Linux (route.default_markis 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 UIcrates/·src-tauri/— Rust core / backend / Tauri desktopmodule/— Android installable zip (thin launcher over the Rust daemon)scripts/— build / release helpers.github/Verification
Rust (
crates/·src-tauri/):cargo fmt --all --checkcargo clippy --workspace --all-targets -- -D warningscargo 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-backendis not touched by this PR and the test fails identically onmainunder the same conditionscargo run -p kasumi-desktop --bin codegenleavesgitcleanNew tests: builder emits no
exclude_uidwithout an app filter / never bakes uid 0 (tun_uid_exclusion_is_app_filter_only), Android tune restores the exact previous shape incl. system-stackauto_redirectand leaves allowlisted/force tuns alone (tune_config_excludes_root_from_capture_all_tuns), Linux mark injection (escape_mark_lands_in_route_on_linux).Checklist
module/bin/<abi>/,geoip/geosite, builtmodule/webroot/,src-tauri/gen/— all gitignored on purpose)frontend/src/generated/was regenerated from Rust, not hand-editedi18n/en.tsand every locale file updated (no partial translations)kasumi-proxy,Kasumi Proxy, camelCase)Notes for reviewers
ip ruleshows9000: from all uidrange 0-0 goto 9010while the native tun is up — that rule is the uid-0 exemption this PR removes.tun_linux.go): in the defaultauto_routemode the only uid-independent self-escape is the mark mode'sfwmark <output_mark> gotorule;bind_interfaceis not an option on this path (it would defeatauto_detect_interface, seeoutbound_bind.rs). Hence fwmark, not a socket bind.goto 32766rather thanlookup 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 unresolvedgoto— a backstopunreachablerule at 8991 for the same mark then hard-fails marked traffic instead of letting it loop.0x4b53is distinct from sing-tun's own auto-redirect marks (0x2023–0x2025); the rule priority 8990 sits just belowSINGBOX_MAIN_RULE_PRIO(9000).