Selective VPN routing in Windows
I mostly work remotely and to gain access to many work resources, I need a VPN connection to my office. When you connect, all of your network traffic is routed through the VPN connection by default. This can be reconfigured so that only the traffic to your office network is routed through the VPN. If you also need to access other resources through your VPN, e.g., cloud-hosted services whitelisted for your office IP, you need to properly route traffic for those as well.
For me, it all started pretty simple, with one such service. I got its IP using the ping
command and added a new network route. I could have used the route
command, but I found it easier to use the New-NetRoute
PowerShell cmdlet because it can resolve the network interface by its alias and I don't have to search for the index of my VPN interface on the route print
output:
New-NetRoute -DestinationPrefix 13.227.180.4/32 -InterfaceAlias MyVPN -NextHop 192.168.2.1
With time, it got more complicated:
- There were domain names with more than one IP address. I could get all of those with a single call to the
Resolve-DnsName
cmdlet instead of multipleping
calls. But I still had to callNew-NetRoute
for each IP. - The IP addresses of some domains changed with time. Now I had to keep track of those and every time, it happened, I had to remove the routes for the old IPs using the
Remove-NetRoute
cmdlet before adding new ones.
It all required too much effort to maintain and became error prone. It was time to write some scripts to simplify my life. After some thought and experimentation, I wrote the following PowerShell script and named it New-NetRouteForHostname.ps1
:
param(
[Parameter(Mandatory)]
[string]$Hostname,
[bool]$Persist = 0,
[string]$InterfaceAlias = "MyVPN",
[string]$NextHop = "192.168.2.1"
)
$addresses = Resolve-DnsName $Hostname | Where-Object {$_.Type -eq "A"}
foreach ($address in $addresses) {
if ($Persist) {
New-NetRoute -DestinationPrefix "$($address.IPAddress)/32" `
-InterfaceAlias $InterfaceAlias -NextHop $NextHop
} else {
New-NetRoute -DestinationPrefix "$($address.IPAddress)/32" `
-InterfaceAlias $InterfaceAlias -NextHop $NextHop -PolicyStore ActiveStore
}
}
It first calls Resolve-DnsName
to get all addresses, i.e., A
records, for a domain name. It then calls New-NetRoute
for each one to add a new network route for it. The script outputs the routes it added.
The $Persist
parameter allows different handling for domain names based on whether their IPs are static or not:
- For domain names with static IPs, I set it to
$true
, so thatPolicyStore
is not set and the route is persisted across reboots and VPN reconnections. - For domain names with IPs that change, I set it to
$false
. This sets thePolicyStore
toActiveStore
so that the route isn't persisted.
The $InterfaceAlias
and $NextHop
parameters allow me to call the script for a different VPN connection. Their default values match my office VPN, so I can usually simply omit them:
New-NetRouteForHostname dynamic-ip.mydomain.net
Since the New-NetRoute
requires admin privileges to run, the script must be run in an elevated PowerShell prompt, or using the sudo
command:
sudo pwsh -Command New-NetRouteForHostname dynamic-ip.mydomain.net
I know that the script is far from perfect, but it's good enough for now. If needed, I can always improve it further.