Fix Linux split-tunneling exclude mode for Steam games - #363
Open
MAST1999 wants to merge 1 commit into
Open
Conversation
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
force-pushed
the
fix/linux-split-tunneling-steam-games
branch
from
September 4, 2026 08:14
0130654 to
df8bcab
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.
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: FAILEDeven though the game had been added to the exclude list.Root causes
Helper process matching (
src/helper/linux/split_tunneling/process_monitor.*): processes were matched by comparingreadlink(/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>/exenever 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.App-picker enumeration (
linuxutils.cpp, newsteamgames.*): everysteam://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, andXDG_DATA_HOME, and are offered as their install-directory entries.Firewall reply path (
firewallcontroller.cpp): exclude-mode accepted reply packets only viameta cgroup, which cannot match on the input path for unconnected UDP sockets (no early demux —skb->skis 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.