Skip to content

Fix Linux split-tunneling exclude mode for Steam games - #363

Open
MAST1999 wants to merge 1 commit into
Windscribe:masterfrom
MAST1999:fix/linux-split-tunneling-steam-games
Open

Fix Linux split-tunneling exclude mode for Steam games#363
MAST1999 wants to merge 1 commit into
Windscribe:masterfrom
MAST1999:fix/linux-split-tunneling-steam-games

Conversation

@MAST1999

@MAST1999 MAST1999 commented Sep 4, 2026

Copy link
Copy Markdown

Problem

On Linux, adding Steam games to split tunneling in exclude mode did nothing: game traffic stayed inside the VPN tunnel. In Dota 2 every region showed ping: FAILED even though the game had been added to the exclude list.

Root causes

  1. Helper process matching (src/helper/linux/split_tunneling/process_monitor.*): processes were matched by comparing readlink(/proc/<pid>/exe) — always a resolved ELF path — against stored entries by raw string equality. Steam picker entries resolve to /usr/bin/steam (a shell script, which /proc/<pid>/exe never shows), and symlinked paths (~/.steam/steam, usrmerge) never compare equal either. Entries are now preprocessed into raw/canonical forms, script launchers match via their shebang interpreter plus an argv token (precise: no basename or arbitrary-token matching), and directory entries (game install trees) match by path prefix. Matched launchers also pull their already-running process trees into the cgroup.

  2. App-picker enumeration (linuxutils.cpp, new steamgames.*): every steam://rungameid/<id> desktop entry resolved to the Steam launcher script, collapsing all games into one "steam" row; games without desktop entries never appeared. Installed games are now enumerated from the Steam libraries themselves (libraryfolders.vdf, appmanifest_*.acf), covering libraries on other drives/locations, flatpak and snap roots, and XDG_DATA_HOME, and are offered as their install-directory entries.

  3. Firewall reply path (firewallcontroller.cpp): exclude-mode accepted reply packets only via meta cgroup, which cannot match on the input path for unconnected UDP sockets (no early demux — skb->sk is NULL). That is exactly the pattern game server browsers use (Steam SDR pings), so replies to excluded processes were dropped by the always-on firewall. The bypass mark is now saved on the connection at egress, restored from conntrack at ingress, and accepted by mark — mirroring the include-mode reply handling.

Testing

The helper-side matching was exercised with a standalone harness against live processes (symlinked paths, script launchers under their interpreter, directory trees, descendant sweeps, snap/flatpak entry shapes preserved, security guards for non-regular files and shebang-interpreter precision), including a live netlink EXEC-event test.

The fixes have been tested manually on my system (Arch-based, CachyOS): the client was built and installed as a package, Steam game entries on a secondary-drive library are matched and moved into the split-tunnel cgroup, marked traffic routes via the physical gateway, and connected vs. unconnected UDP reply paths were verified before and after the firewall fix (Dota 2 region pings now succeed in exclude mode).

Attribution

Most of the work was done with an AI agent (OpenCode); the fixes have been tested manually on my system and verified that they work.

Three defects kept Steam games from bypassing the VPN tunnel in
exclude mode:

* Helper process matching compared readlink(/proc/<pid>/exe) (always a
  resolved ELF path) against stored app entries by raw string equality,
  so script-wrapped launchers (/usr/bin/steam) and symlinked paths
  (~/.steam/steam, usrmerge) could never match. Entries are now
  preprocessed into raw/canonical/script-interpreter/directory forms:
  script launchers match via their shebang interpreter plus an argv
  token, directory entries (game install trees) match by path prefix,
  and matched launchers pull their already-running process trees into
  the cgroup.

* The GUI app picker resolved every steam://rungameid desktop entry to
  the Steam launcher script, collapsing all games into a single
  "steam" row, and games without desktop entries never appeared.
  Installed games are now enumerated from the Steam libraries
  themselves (libraryfolders.vdf and appmanifest_*.acf, covering
  libraries on other drives, flatpak/snap roots and XDG_DATA_HOME) and
  offered as their install-directory entries.

* The exclude-mode firewall accepted reply packets only by
  "meta cgroup", which cannot match on the input path for unconnected
  UDP sockets (no early demux) - the pattern game server browsers use
  (Steam SDR pings) - so replies were dropped by the always-on firewall
  and every region showed "ping: failed". The bypass mark is now saved
  on the connection at egress, restored from conntrack at ingress, and
  accepted by mark, mirroring the include-mode reply handling.
@MAST1999
MAST1999 force-pushed the fix/linux-split-tunneling-steam-games branch from 0130654 to df8bcab Compare September 4, 2026 08:14
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