Feature/split tunneling - #2182
Merged
Merged
Conversation
Introduces a standalone Go module with a minimal Layer-4 proxy used by the split-tunnelling subsystem: - DeciderFunc injects routing and optional source-address binding per session, enabling per-connection traffic steering. - TCPProxy: accept loop, bidirectional pipe with pooled 32 KiB buffers, rolling read/write deadlines, half-close propagation, and graceful shutdown via context cancellation. - UDPProxy: single listen socket with a NAT-like session table keyed by client address, double-checked locking for burst safety, idle eviction loop, and per-session upstream sockets. - Shared Config (MaxSessions, ReadTimeout, WriteTimeout, BufferSize, DialTimeout), ConnContext with atomic byte/packet counters, and a sessionCache with aggregate Metrics. - Full test suite (functional + race) and benchmarks for throughput and session creation cost.
…cache - Change DeciderFunc signature to return (remoteIP net.IP, remotePort uint16, localAddr string, extraInfo any, err error) instead of a single "host:port" dest string - Extract ConnContext, Metrics, sessionCache, and idCounter into a new cache.go file - Add a secondary destKey index to sessionCache for O(1) FindProxiedEgressConnection lookups by upstream destination - Attach per-session extraInfo and atomic byte/packet counters to ConnContext - Update TCP and UDP proxies, tests, and README accordingly
Add a new verdict (value 8) for routing connections through the split tunnel. This prepares the infrastructure for the upcoming split-tunneling feature without implementing the full feature yet. Changes: - Define VerdictRerouteToSplitTun in network/status.go with String() and Verb() - Add RerouteToSplitTun() to the Packet interface and InfoPacket stub - Implement RerouteToSplitTun() for windowskext (v1) and windowskext2 (v2) packets - Map VerdictRerouteToSplitTun to KextVerdict 11 in kextinterface and kext2 - Handle the verdict in packet_handler.go dispatch, connection.go, api.go, metrics.go and nameserver.go - Add VerdictRerouteToSplitTun = 8 to Angular Verdict enum and update stats counting, filter queries and verdict CSS class (WIP) Note: Linux (nfq) implementation not updated yet. Therefore Linux build will fail.
Add interfaces.go with GetInterface, GetInterfaceByIP, GetInterfaceByMAC and GetInterfaceByName for resolving local network interfaces by IP, MAC, or name. - Lazy init: no work until first call - sync.RWMutex with double-checked locking for concurrent read throughput - Refresh throttled to once per second to absorb rapid interface churn (same NetworkChangedFlag pattern used across netenv) - Only live, routable interfaces cached: FlagUp required; link-local and address-less interfaces excluded as unsuitable for TCP/UDP tunneling
- Refactor GetInterface* functions to return InterfaceInfo with IPv4/IPv6
addresses instead of just net.Interface
- Add pre-caching of first routable IPv4/IPv6 per interface to avoid repeated
address list scans
- Skip loopback interfaces in cache refresh
- Add GetBestPhysicalDefaultInterfaces() to detect which physical adapters
carry the default route per IP family, excluding VPNs/tunnels
- Implement platform-specific physical interface detection:
* Linux: reads /proc/net/route and /proc/net/ipv6_route, uses
/sys/class/net/*/device to identify real hardware
* Windows: uses GetAdaptersAddresses with IfType filtering
* Other platforms: returns not-supported error
- Add helper functions: buildInterfaceInfo, interfaceToInfo, buildInterfaceInfoDirect,
hasRoutableIPv4, hasRoutableIPv6
- Update tests to work with new InterfaceInfo return type and add coverage
for new features
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.
…l-manager panic - proxies: shut down partially-started proxies on startup failure via deferred cleanup; avoid nil-manager panic in stopProxies by falling back to context.Background(); start UDP4 unconditionally and gate TCP6/UDP6 on IPv6Enabled() - requests: add 30s TTL to pending requests to prevent memory leaks when OS drops a redirected connection before it reaches the proxy; schedule deferred cleanup via module.mgr.Go so the goroutine only runs when entries are registered and exits cleanly on module stop; add expiry check in consumeRequest as a safety net; clear map on Stop - requests: guard against nil LocalIP on public AwaitRequest API
Adds a standalone bash script to build the Angular UI project and package it into a distributable zip. Supports --development and --interactive flags.
Introduces mark 1719 for split-tunnel rerouting, mirroring the existing SPN mark (1717). Adds FILTER RETURN and NAT DNAT rules for both IPv4 and IPv6 targeting port 719.
Replaces scattered link-local exclusion checks with the new isRoutableUnicastIP predicate (site-local or global scope only), consistently applied in refreshIfaceCache, buildInterfaceInfoDirect, hasRoutableIPv4, and hasRoutableIPv6. Updates tests accordingly.
Introduces LocalBinding{IP, Interface} to carry both source-address
and device binding in a single DeciderFunc return value. On Linux,
SO_BINDTODEVICE is applied via net.Dialer.Control before connect(2),
forcing traffic through the specified interface regardless of the
routing table. Non-Linux platforms get a no-op stub.
Wires LocalBinding through TCPProxy, UDPProxy, and splittun's
proxyDecider/AwaitRequest so split-tunnelled connections are bound
to the correct physical interface.
…amic - SetLogLevel now writes a log line via writeLogLevelChange() so level transitions are always visible regardless of old/new level - slogLevel is now a shared *slog.LevelVar; all derived loggers pick up changes instantly without recreating the handler - slog.SetDefault is called only once (sync.Once) so handlers are stable
…rameter - Logger interface changes from Debugf/Infof/Warnf/Errorf to Debug/Info/Warn/Error with key-value args (slog-compatible) - NewTCPProxy, NewTCPProxyWithConfig, NewUDPProxy, NewUDPProxyWithConfig all gain a logPrefix string parameter - noopLogger updated; resolveLogPrefix helper added - README, tests, and benchmarks updated accordingly - proxies.go: remove proxyLogger wrapper now that mgr.Manager satisfies the new structured Logger interface directly
- New config.go registers the "splittun/enable" boolean option - subsystems.ts: change ToggleOptionKey from splittun/use to splittun/enable - Module Start/Stop replaced with enable()/disable() helpers driven by the config option; a callback on EventConfigChange toggles state at runtime
…d 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
Rename ensureSPNCompatibility to reconcileCompatibilityState and extract the implementation logic to improve code clarity and maintainability across all platform implementations.
…ements Use the selected conntrack family for delete operations so IPv6 entries are removed correctly too.
…ctivation Add DeleteUnmarkedConnections() to purge conntrack entries with mark=0 when firewall is activated. This forces applications with existing connections to reconnect, allowing DNAT rules (like SPN) to apply. Without this, connections established while Portmaster was paused or stopped would bypass DNAT because netfilter's nat table is only traversed for new connections. Loopback connections are excluded from deletion to avoid disconnecting local services. safing/portmaster-shadow#42
Add isOwnSplitTunnelProxyConnection to detect outbound connections from Portmaster's own split-tunnel proxies. Replace the slice-returning FindProxiedEgressConnection with a boolean HasProxiedEgressConnection to avoid unnecessary allocations on each lookup.
Add PM_SPLIT_TUN_PORT (719) to fast_track_pm_packets so that redirected packets arriving at the local split-tunnel proxy are permitted immediately by the kext, matching the existing behaviour for the SPN port (717) and the DNS port (53). This prevents internal proxy connections from being reported to Portmaster and appearing in the connection monitor UI. Also simplify fast_track_pm_packets by removing the redundant match-on-direction branches, which were identical for Outbound and Inbound. Bump kext interface patch version to 2.1.1.0.
…he SPN" The SPN Tunnel information is visible only when the connection has been routed through it.
- Replace "Safing Support" feature with Split Tunneling in features.go, using a dedicated config key/scope and free package tier - Fix feature-card component to prioritize ConfigKey over ConfigScope when resolving the config lookup key
Adds a "Split Tunnel" toggle to the app profile quick-settings bar, mirroring splittun/use per-app setting. Shows an interference dot when: - splittun/usagePolicy has Exclude rules (yellow) - SPN is active and routes all traffic, fully bypassing Split Tunnel (red) - SPN is active and partially bypasses Split Tunnel (yellow) Dot and interference checks are suppressed when the Split Tunneling or SPN module is globally disabled.
…nel options - Add validation to Network Interface config to reject whitespace-only values - Improve "Use Split Tunnel" description to clarify default physical interface detection behavior
…ions Proxied egress connections from ownPID were still running through checkTunneling(), causing them to be routed via SPN if Portmaster's own profile had SPN enabled. Add a checkTunnel flag that is set to false for isOwnSplitTunnelProxyConnection to preserve the original app's routing decision.
Proxy split tunnel connections bypassed both filter and tunnel checks, leaving no code path to trigger the GeoIP lookup, so Country, ASN and AS Org showed as N/A in the UI. Add Entity.FetchLocation (GeoIP only, no filter lists) and call it unconditionally at the start of FilterConnection.
|
Alexandr Stelnykovych seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
Contributor
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughThis PR implements a comprehensive split tunnel feature enabling selective per-connection routing through a local TCP/UDP proxy to alternative network interfaces, alongside configuration management, firewall integration, UI controls, and network interface discovery across multiple platforms. ChangesSplit Tunnel Feature
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
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.
https://github.com/safing/portmaster-shadow/issues/45
Summary by CodeRabbit
New Features
Improvements