/logs - WireGuard | split-tunneling | Windows

WireGuard doesn't support application-based split tunneling on Windows. Time to glue stuff together!

WireGuard doesn't support application-based split tunneling out of the box, at least not on Windows (the Android client has built-in app exclusion/inclusion). Thankfully, it's fairly extensible and well documented. Another win for FOSS tools, albeit hard-earned!

TL;DR: SOCKS proxy -> bind its outbound to WireGuard -> appln. uses proxy -> SOCKSify unsupported applications

On Linux, WireGuard is a first-class application; it's been included in the kernel tree itself. In short, such a setup could be put together using network namespaces (netns): create a network namespace, attach only the WireGuard interface to it, run applications inside it, and just like that, those applications are tunneled.

Unfortunately, to my knowledge, Windows doesn't have network namespaces. So, down the Internet rabbit hole I went, to find mostly despair. Here's a list of alternative solutions which I don't prefer:

  • Lots of big-name VPNs offer this feature out of the box, such as ProtonVPN, Mullvad, or even Wiresock which is specifically made for WireGuard but they're proprietary and/or paid. Also, I already have my own WireGuard tunnel setup for regular use.
  • Extreme solution - spin up a VM, make its network use only the WireGuard interface (see here)? But there's way too many issues with that: performance impact, inconvenient, applications may not work, etc.
  • Bind applications to specific network interfaces.
    • Linux? Sure, done. The linked post describes how to achieve this using the aforementioned netns namespaces. But I'm on Windows (:
    • Windows? Not really. There are programs that achieve this though, most notably ForceBindIP, which gets mentioned in nearly every thread I come across. However, even those are proprietary or worse, unmaintained/dead (last update for ForceBindIP was nearly a decade ago)
    • Some applications do support it out of the box, such as qbittorrent, and even curl itself. Again, very limited use-cases.

Then I came across a great post on r/WireGuard. The idea:

  • run a SOCKS5 proxy on the local machine itself, that can passthrough TCP and UDP traffic
  • set its outbound gateway as the WireGuard tunnel's virtual network interface
  • deprioritize WireGuard's routes in the machine's routing table
  • configure applications to use this proxy
  • applications that don't use the proxy will use the higher priority default routes, thus bypassing the VPN tunnel.

Obviously, not the best of ideas:

  • Compatibility - Most applications don't support proxies out of the box, thus requiring some kind of third-party software to SOCKSify their network calls.
    • Proxifier sounds perfect but it's proprietary
    • Proxifyre might be a solution, yet to check it out.
  • Performance - yet to benchmark it. Hopefully it's only an insignificant hit.
  • Security!!!
    • No idea how this affects it, I'm not very knowledgeable in this regard.
    • In terms of the OSI model, WireGuard operates at Layer 3 but SOCKS operates at Layer 5, on top of TCP/UDP. Possible leakage?
  • Not a very convenient setup, too many moving parts to not automate.

Shoutout to the OP (u/Deep_Mind) of that comment; they later helped me out with a detailed document describing their setup: https://drive.google.com/file/d/1_nlsVoy4-KrfruWVqr7gF_28iiesUFms/view?usp=drive_link

Here is the similar setup that I ended up with:


  • Install 3proxy (really, any proxy that supports binding to an interface)
  • WireGuard:
    • Registry key to enable WireGuard scripts
      • Scripts are not enabled in Wireguard Windows by default
      • Go to Computer\HKEY_LOCAL_MACHINE\SOFTWARE.
      • Right-click SOFTWARE in the navigation pane, click New -> Key, name it WireGuard.
      • Create a new DWORD (32-bit) Value in the new created key named DangerousScriptExecution.
      • Set its value to 1.
      • Now you can add PreUp, PostUp, PreDown, PostDown scripts to your WireGuard tunnels.
    • Disable automatic route creation for the tunnel
      • In the [Interface] section of your client's tunnel configuration, add Table = off - This informs WireGuard not to create a default route automatically
      • Note that this also disables blocking of untunneled traffic (kill-switch functionality), which is what we want in order to achieve split-tunneling
      • This is a must; direct route manipulation through scripting is not permitted when the kill-switch functionality is active. It will simply drop traffic IIRC.
    • PostUp script
      • Since we disabled automatic default route(s) addition to table, we have to add the necessary routes
      • So, modify routes according to allowedIPs. My use case is 0.0.0.0/0 i.e. I want to route everything
      • For other cases, I think you could see what routes WireGuard generates on its own normally, and add the missing route(s) from those?
'postup start' | Out-File -FilePath "${PSScriptRoot}\PostUp.log"

# Get WireGuard tunnel interface details
$wgInterface = Get-NetAdapter -InterfaceAlias $env:WIREGUARD_TUNNEL_NAME
$wgAddress = (Get-NetIPAddress -InterfaceAlias $env:WIREGUARD_TUNNEL_NAME -AddressFamily IPv4 ).IPAddress

# add default 0.0.0.0/0 route with low priority (higher value)
route add 0.0.0.0 mask 0.0.0.0 0.0.0.0 IF $wgInterface.ifIndex metric 999

# Set the WireGuard interface's metric to low priority (higher value)
Set-NetIPInterface -InterfaceIndex $wgInterface.ifIndex -InterfaceMetric 999

# Navigate to the 3proxy directory
Set-Location "X:\path\to\3proxy-0.9.4-x64\bin64\"
$cfg_file = "3proxy-wireguard.cfg"

# Create temporary 3proxy configuration file and add settings
'auth none' | Out-File -FilePath $cfg_file -Encoding ASCII # disable proxy authentication
'internal 127.0.0.1' | Out-File -FilePath $cfg_file -Append -Encoding ASCII # proxy's bind address. DO NOT bind all, only localhost
"external ${wgAddress}" | Out-File -FilePath $cfg_file -Append -Encoding ASCII # send proxy's traffic to WireGuard tunnel

# proxy type configuration
'socks' | Out-File -FilePath $cfg_file -Append -Encoding ASCII # SOCKS5

# logging
'log "X:\path\to\logs\%Y%m%d.log" D' | Out-File -FilePath $cfg_file -Append -Encoding ASCII
'rotate 30' | Out-File -FilePath $cfg_file -Append -Encoding ASCII

# Start 3proxy in the background
Start-Process -FilePath '.\3proxy.exe' -ArgumentList $cfg_file -NoNewWindow

'postup end' | Out-File -FilePath "${PSScriptRoot}\PostUp.log" -Append

PostUp.ps1

    • PreDown script
      • make sure to specify all routes created in the PostUp script
'predown start' | Out-File -FilePath "${PSScriptRoot}\PreDown.log"

# WireGuard tunnel details
$wgInterface = Get-NetAdapter -Name $env:WIREGUARD_TUNNEL_NAME

# Delete the default 0.0.0.0/0 route using the interface index
route delete 0.0.0.0 mask 0.0.0.0 0.0.0.0 if $wgInterface.ifIndex

# Terminate any running instances of 3proxy.exe
Set-Location "X:\path\to\3proxy-0.9.4-x64\bin64\"
Stop-Process -Name "3proxy.exe" -Force

'predown end' | Out-File -FilePath "${PSScriptRoot}\PreDown.log" -Append

PreDown.ps1