From 4e67768927bf912cee5d5fa47e914d2f34db7669 Mon Sep 17 00:00:00 2001 From: Alexandr Stelnykovych Date: Thu, 30 Apr 2026 18:26:34 +0300 Subject: [PATCH] splittun on Linux: generalize WireGuard compatibility rule for SPN and Split Tunnel - Rename ensureWgSpnCompatRule to ensureWgCompatRule to reflect that it now handles both SPN and Split Tunnel compatibility with WireGuard - Add split tunnel configuration check alongside SPN check - Update comments to clarify the rule applies to both SPN and Split Tunnel - Ensure compatibility rule remains active when either SPN or split tunneling is enabled --- service/interop/ivpn/hook_linux.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/service/interop/ivpn/hook_linux.go b/service/interop/ivpn/hook_linux.go index fdd8202b..dfd0e142 100644 --- a/service/interop/ivpn/hook_linux.go +++ b/service/interop/ivpn/hook_linux.go @@ -33,7 +33,7 @@ const ( ) func (i *InteropIvpn) spnConnectingHook(wc *mgr.WorkerCtx, homeHub hub.Announcement) (cancel bool, retErr error) { - err := i.ensureWgSpnCompatRule(wc) + err := i.ensureWgCompatRule(wc) if err != nil { // Could happen, for example, if IVPN Client is paused wc.Warn(fmt.Sprintf("IVPN: failed to ensure WireGuard compatibility rule: %v", err)) @@ -47,7 +47,7 @@ func (i *InteropIvpn) spnConnectingHook(wc *mgr.WorkerCtx, homeHub hub.Announcem } func (i *InteropIvpn) ensureSPNCompatibility(wc *mgr.WorkerCtx) error { - err := i.ensureWgSpnCompatRule(wc) + err := i.ensureWgCompatRule(wc) if err != nil { wc.Warn(fmt.Sprintf("IVPN: failed to ensure WireGuard compatibility rule: %v", err)) } @@ -59,15 +59,15 @@ func (i *InteropIvpn) ensureSPNCompatibility(wc *mgr.WorkerCtx) error { return nil } -// SPN compatibility workaround for WireGuard kill-switch rules. +// SPN and SplitTunnel (ST) compatibility workaround for WireGuard kill-switch rules. // // WireGuard (wg-quick) installs a prerouting/raw kill-switch rule that drops // packets destined to the WG local address when they arrive from non-WG interfaces. -// Portmaster SPN reverse-NAT replies are delivered via loopback (iif lo) with a +// Portmaster SPN/ST reverse-NAT replies are delivered via loopback (iif lo) with a // non-local source, which matches that drop pattern and breaks the TCP handshake // (SYN-SENT/SYN-RECV). // -// To preserve the kill-switch behavior while allowing SPN reverse-NAT, Portmaster +// To preserve the kill-switch behavior while allowing SPN/ST reverse-NAT, Portmaster // inserts a narrow exception rule before the wg-quick drop: // - nft path (preferred): // `iifname "lo" ip daddr fib saddr type != local accept` @@ -76,8 +76,8 @@ func (i *InteropIvpn) ensureSPNCompatibility(wc *mgr.WorkerCtx) error { // // Rule lifecycle is managed here: // - Remove previously managed rule (nft/iptables) first. -// - Recreate only when WireGuard is connected and SPN is enabled. -func (i *InteropIvpn) ensureWgSpnCompatRule(wc *mgr.WorkerCtx) error { +// - Recreate only when WireGuard is connected and SPN/ST is enabled. +func (i *InteropIvpn) ensureWgCompatRule(wc *mgr.WorkerCtx) error { status := i.getStatus() connectedInfo := status.connectedInfo @@ -123,7 +123,8 @@ func (i *InteropIvpn) ensureWgSpnCompatRule(wc *mgr.WorkerCtx) error { // If SPN not enabled -we do not need the rule cfgSpnEnabled := config.GetAsBool("spn/enable", false) - if !cfgSpnEnabled() { + cfgSplittunEnabled := config.GetAsBool("splittun/enable", false) + if !cfgSpnEnabled() && !cfgSplittunEnabled() { return nil }