Why routing scripts are needed
When you enable a full-tunnel WireGuard VPN (with AllowedIPs = 0.0.0.0/0), all traffic — including packets destined for the TURN server — is sent through the tunnel. This creates a loop: the proxy client tries to reach the TURN server, the OS routes that traffic into the VPN, and the traffic never reaches the TURN server over the real network.
The routing scripts solve this by adding a direct host route to the TURN server’s IP via your physical default gateway before you activate the VPN. With that route in place, the OS sends TURN-bound packets directly to the gateway, bypassing the tunnel.
The proxy client prints each TURN server IP it resolves to stdout. The scripts read those IPs from stdin and add the corresponding routes.
Routes added by these scripts are not persistent across reboots. If the machine restarts, run the proxy client and routing script again before enabling the VPN.
Scripts
routes.sh — run with sudo or as root.#!/bin/bash
gateway="$(ip -o -4 route show to default | awk '/via/ {print $3}' | head -1)"
while read -r remote; do
sudo ip r add $remote via $gateway
done
How it works:
- Reads the current IPv4 default gateway using
ip route.
- Loops over every line from stdin. Each line is expected to be a plain IP address printed by the proxy client.
- Adds a host route (
ip r add <ip> via <gateway>) for each IP.
Usage:./client-linux -peer <vps-ip>:56000 -vk-link <vk-link> -listen 127.0.0.1:9000 | sudo routes.sh
routes-macos.sh — run with sudo or as root.#!/bin/zsh
set -u
default_info="$(route -n get default 2>/dev/null || true)"
default_if="$(printf '%s\n' "$default_info" | awk '/interface:/{print $2; exit}')"
gateway="$(printf '%s\n' "$default_info" | awk '/gateway:/{print $2; exit}')"
if [[ -n "${default_if:-}" && "$default_if" == utun* ]]; then
echo "Default route is currently $default_if. Disconnect WireGuard/VPN first." >&2
exit 1
fi
if [[ -z "${gateway:-}" ]]; then
echo "Could not determine normal default gateway." >&2
exit 1
fi
while IFS= read -r line; do
line="${line//$'\r'/}"
# Try to extract:
# - plain IPv4
# - IPv4/CIDR
# - relayed-address=IPv4:port -> use only IPv4
remote="$(printf '%s\n' "$line" | sed -nE '
s/.*relayed-address=(([0-9]{1,3}\.){3}[0-9]{1,3}):[0-9]+.*/\1/p
t done
s/^(([0-9]{1,3}\.){3}[0-9]{1,3}\/[0-9]{1,2})$/\1/p
t done
s/^(([0-9]{1,3}\.){3}[0-9]{1,3})$/\1/p
:done
')"
[[ -z "$remote" ]] && continue
if [[ "$remote" == */* ]]; then
sudo route -n delete -net "$remote" >/dev/null 2>&1 || true
sudo route -n add -net "$remote" "$gateway" || true
else
sudo route -n delete -host "$remote" >/dev/null 2>&1 || true
sudo route -n add -host "$remote" "$gateway" || true
fi
done
How it works:
- Reads the default route using
route -n get default to determine both the gateway IP and the outgoing interface.
- VPN detection: if the default interface name starts with
utun (the prefix macOS uses for WireGuard/VPN tunnels), the script exits with an error. You must disconnect the VPN before running the proxy.
- Reads each line from stdin and extracts an IP or CIDR using
sed. It handles three formats:
- Plain IPv4 address (e.g.,
5.255.211.241)
- IPv4 with prefix length (e.g.,
5.255.211.0/24)
relayed-address=IP:port log lines emitted by the proxy client — the script strips the port and uses only the IP
- Adds a host route (
route add -host) for plain IPs, or a network route (route add -net) for CIDR ranges. Any pre-existing conflicting route is silently deleted first.
Usage:./client-macos -peer <vps-ip>:56000 -vk-link <vk-link> -listen 127.0.0.1:9000 | sudo zsh routes-macos.sh
routes.ps1 — run PowerShell as Administrator.# Get the default IPv4 gateway
$gateway = Get-NetRoute `
-DestinationPrefix "0.0.0.0/0" `
| Sort-Object RouteMetric `
| Select-Object -First 1 -ExpandProperty NextHop
if (-not $gateway) {
Write-Error "Could not determine default gateway"
exit 1
}
Write-Host "Default gateway: $gateway"
# Read addresses from stdin
$input | ForEach-Object {
$addr = $_.Trim()
if ($addr -eq "") { return }
Write-Host "Adding route to $addr via $gateway"
New-NetRoute `
-DestinationPrefix "$addr/32" `
-NextHop $gateway `
-PolicyStore ActiveStore `
-ErrorAction Stop
}
How it works:
- Finds the default IPv4 gateway by querying
Get-NetRoute for the 0.0.0.0/0 route and selecting the one with the lowest metric.
- Reads each IP address from stdin (one per line, as printed by the proxy client).
- Adds a
/32 host route via New-NetRoute with PolicyStore ActiveStore, meaning the route is active immediately but is not written to the persistent routing table and will not survive a reboot.
Usage (in an Administrator PowerShell window):.\client.exe -peer <vps-ip>:56000 -vk-link <vk-link> -listen 127.0.0.1:9000 | .\routes.ps1
PowerShell must be running as Administrator. New-NetRoute will fail with an access denied error if run without elevated privileges.