feat: Add Split Tunnel feature (Windows PoC)

Implement initial proof-of-concept for split tunnel functionality on Windows,
allowing applications to route traffic through a designated network interface
while bypassing default system routing.

Features:
- Split tunnel module with TCP/UDP proxy infrastructure
- Firewall integration with split tunnel verdict handling
- SplitTunneling context attached to connections
- Configuration options: enable toggle, interface selection, and policy rules
- UI display of split tunnel connection details in connection info panel
- Subsystem configuration for user-level access

Windows-specific implementation:
- Uses proxy-based interface routing on Windows
- Automatic or manual interface detection and binding
- Support for IPv4 and IPv6 traffic

Note: Linux implementation is under development. SPN takes precedence over
split tunnel when both are enabled, ensuring SPN connections bypass this feature.
This commit is contained in:
Alexandr Stelnykovych 2026-04-24 18:04:01 +03:00
parent 29cc58fecb
commit ee8cde31f6
17 changed files with 682 additions and 7 deletions

View file

@ -25,6 +25,7 @@ import (
"github.com/safing/portmaster/service/network/netutils"
"github.com/safing/portmaster/service/network/packet"
"github.com/safing/portmaster/service/process"
"github.com/safing/portmaster/service/profile"
"github.com/safing/portmaster/service/resolver"
"github.com/safing/portmaster/spn/access"
)
@ -571,6 +572,11 @@ func FilterConnection(ctx context.Context, conn *network.Connection, pkt packet.
// Check if connection should be tunneled.
if checkTunnel {
checkTunneling(ctx, conn)
if conn.Verdict != network.VerdictRerouteToTunnel {
// SPN takes precedence over Split Tunnel, so only check split tunneling if not already set to tunnel.
checkSplitTunneling(ctx, conn)
}
}
// Request tunneling if no tunnel is set and connection should be tunneled.
@ -585,6 +591,12 @@ func FilterConnection(ctx context.Context, conn *network.Connection, pkt packet.
// connection and the data will help with debugging and displaying in the UI.
conn.Failed(fmt.Sprintf("failed to request tunneling: %s", err), "")
}
} else if conn.Verdict == network.VerdictRerouteToSplitTun {
// Request split tunneling
err := requestSplitTunneling(ctx, conn)
if err != nil {
conn.Failed(fmt.Sprintf("failed to request split-tunneling: %s", err), profile.CfgOptionSplitTunUseKey)
}
}
}