feat(desktop): proxy mode selection (tun / proxy-only / system / pac) - #65
Merged
Conversation
loss-and-quick
force-pushed
the
feat/proxy-mode
branch
from
June 21, 2026 16:41
78cf565 to
9a35e50
Compare
15 tasks
loss-and-quick
force-pushed
the
feat/proxy-mode
branch
from
June 21, 2026 17:14
9a35e50 to
9fbe547
Compare
loss-and-quick
force-pushed
the
feat/proxy-mode
branch
5 times, most recently
from
June 22, 2026 13:52
fcb6959 to
ba8d4b3
Compare
Default tun preserves current behaviour; non-tun modes drop the sing-box tun inbound whatever the resolved TUN engine (xray is already inbound-only).
Non-tun modes spawn the core on its local socks/http inbound alone — no tun device, no routing rewrite. The privileged helper still owns the data path; the mode travels over the privhelper wire (defaulting to tun for a stale helper). A dedicated no-tun marker keeps the watchdog from expecting a tun helper, and platforms without proxy-mode support (Android) normalize the mode to tun at config build and start. Adds the desktop-only selector (i18n ×8).
gsettings + KDE kioslaverc + environment.d on Linux (layered — each reaches a disjoint set of apps), WinINET registry on Windows. Applied by the Service through new Platform hooks that run in the GUI process, never the privileged helper — the OS proxy lives in the logged-in user's session. Cleared on every stop, mode switch and app exit, so the OS is never left pointing at a dead port.
Loopback PAC server + the OS pointed at its URL (gsettings auto / kioslaverc ProxyType 2 / WinINET AutoConfigURL); torn down with the data path. The env-var layer can't express a PAC, so it is cleared in this mode.
loss-and-quick
force-pushed
the
feat/proxy-mode
branch
from
July 13, 2026 20:31
ba8d4b3 to
157ee72
Compare
15 tasks
19 tasks
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
Add a desktop proxy mode selector, so the client can run without a TUN device — like v2rayN. Both cores already expose a local socks/http inbound, so a non-tun mode just runs the core on that inbound.
A single mutually-exclusive
proxyModesetting (tun|proxy-only|system|pac), because "TUN + system proxy" is meaningless — a tun already captures everything, so one selector makes the impossible state unrepresentable rather than offering two orthogonal axes.127.0.0.1socks/http only; OS untouched.http://127.0.0.1:10811/proxy.pac.Per-layer:
kasumi-core) — theproxyModesetting; any non-tun mode builds sing-box socks-only (no_tun, composed with the existing external-TUN-engine condition; xray is inbound-only already).kasumi-backend) —moderidesStartDataPath;resolve_and_write_configreturns the whole struct now instead of a growing tuple. A newPlatform::supports_proxy_modes(defaultfalse) normalizes the mode totunat config build and start for platforms without support — so a desktop backup restored on Android can't strip the tun inbound out from under the root module.modetravels over the privhelper wire with#[serde(default)]so a stale helper/service build degrades to tun instead of erroring. Non-tun modes spawn the core and stop: no tun device, no routing rewrite, no uplink-bind escape (nothing to escape). A dedicatedno-tunmarker keeps the watchdog from expecting a tun helper for an xray core (the absent-marker fallback would otherwise flag the data-path unhealthy and rebuild it in a loop).system/pac) — applied through newPlatform::{set_os_proxy, clear_os_proxy}hooks that theServicecalls around the lifecycle;RemotePlatformanswers them locally instead of forwarding: the OS proxy lives in the logged-in user's session (gsettings / D-Bus / HKCU), which the root helper's isn't. Linux applies a layered stack (gsettings guarded by schema presence; KDEkioslaverc+reparseSlaveConfigurationso KIO apps reload;environment.d+ a livesystemctl --user/dbus-update-activation-environmentpush — the only layer reaching CLI tools and XFCE/LXQt). Windows writes the WinINET registry keys +InternetSetOptionrefresh. Cleared on stop, on mode switch, on a failed bring-up, and on app exit.Why the helper stays in non-tun modes
Skipping the helper for non-tun modes (the original PR's elevation gating) is deliberately not ported: the helper writes the data-path runtime files as root/LocalSystem, so alternating a privileged and an unprivileged data-path owner over the same state files turns mode switches into silent EACCES misbehaviour. On packaged installs the helper is promptless anyway (caps wrapper / installed service); a lazy helper bring-up gated on the saved mode is a follow-up that needs a helper-side file-ownership sweep first.
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
Web UI (
frontend/):bun run check— Biome lint + format cleanbun run test— vitest green (69 passed)bun run build—tsc -b+ vite build succeedbun run check:i18n— locale dictionaries in sync (651 keys × 8 locales)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, the known environment flake (an active VPN tun on the dev machine captures the TEST-NET-1 connect); fails identically onmaincargo run -p kasumi-desktop --bin codegenleavesgitcleanNew tests: non-tun modes build sing-box socks-only (
non_tun_proxy_mode_is_socks_only), theno-tunmarker suppresses the helper expectation incl. the xray fallback (running_external_engine_*), mode normalization for non-supporting platforms (resolve_and_write_config_*), proto round-trip with the new field, and the PAC server serves/stops/re-binds (serves_the_pac_and_stops).Windows
#[cfg(windows)](registry/WinINET) compiles only under the Windows CI job — the windows-sys 0.59 signatures match the vendored crate (unchanged from the original PR's check).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
Serviceowns — start success, start failure, stop, resume-restart (it re-entersrun_lifecycle), andRunEvent::Exit— so no path leaves the OS pointed at a dead port. The uncovered case is a hard GUI crash: the helper reaps the data-path (GUI-gone teardown), but the OS proxy stays until the next start/stop. Same class of gap as v2rayN; fixing it needs a boot-time "did we set this" record.clear_system_proxydoesn't save/restore a proxy the user had configured before the app ran — the app assumes it owns the OS proxy setting while insystem/pacmode.mixedsocks inbound, sosystem/pacusesocks_portfor both there; xray keeps its separate http inbound port. Decided desktop-side inset_os_proxy, not in the sharedService.