Dynamic routing of a DCHP VPN connection
One of my clients is setting up two new VPNs — one is a connection between two office locations, and the other is the ability for users to dial in from home.
So, in either of the offices, we don’t care about reaching the single-PC VPN machines, but those machines do need to be able to reach computers at both offices. This article covers the routing situation for single-PC clients.
For hardware, we chose the SonicWall TZ100 as our appliances; one in each office. They’re pretty nice — they do VPNs,firewall, Antivirus, Spam, automatic failover/load balancing. They also provide L2TP Services, which work pretty well with the Windows Native VPN client.
We will have a couple of subnets, and a couple of VPN users; that’s a somewhat more complicated IP situation than the default situations provide. Our active subnets are:
192.168.1.x (main office),
192.168.2.x (remote office), and
192.168.4.x (VPN DHCP pool).
So, on a client PC, we want to route any traffic intended for those subnets to go through the VPN, any other network traffic we want to go out through the regular internet connection.
I was quite surprised to find out how difficult this is to pull off!
I finally settled on using WSCRIPT to accomplish that. You have to create the VPN connection item yourself, but then you can run a script connect the VPN, query the system for the IP address and set the routing.
Of course, you first need to create a stock Windows VPN connection. Then you have to set the Windows VPN item to disable the “make this gateway the default”. Then, you’ll need to use a bunch of ROUTE ADD commands to manually add the routes to the routing table.
However, the catch to that is that you need the IP address that the VPN provides, so that you can tell the computer which IP address to use as the route. There is no elegant way to get this information. There are a few WMI classes that look promising: Win32_NetworkAdapterConfiguration and Win32_NetworkAdapter. However, they only return the hard-coded IP address (or 0.0.0.0 if it’s DHCP) of the adapter, so that’s not useful.
My next thought was to fetch that information out of the registry. When the connection is made, that IP address is stored in the Registry, but only in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Tcpip\Parameters\Interfaces\{someGUID}. That key is identified with a GUID; you’d think that GUID would match up with something else in the system, but I sure couldn’t find it. I don’t mind using GUIDs in scripts, but trying to write a script that you can give to an end-user that requires them to locate and enter a GUID is a bad idea.
SO, there’s a lot of non-attractive options out there. I did find a piece of code on the web that ran IPConfig and piped it into an array, parsing it to pull out the IP addresses returned for any adapters connected to the machine. Piping the output of a utility into a string and parsing it is error-prone — who knows if Windows 8 will change out IPConfig works. But I don’t think there were any other options. I modified it somewhat to pull the IP address for a specified adapter:
-
[edited for presentation]
-
Function GetIPAddress_Name(AdapterName)
-
workfile = fso.gettempname
-
sh.run "%comspec% /c ipconfig > " & workfile,0,true
-
set ts = fso.opentextfile(workfile)
-
data = split(ts.readall,vbcrlf)
-
strIPAddress = ""
-
for n = 0 to ubound(data)
-
if instr(data(n)," adapter ") then ‘ we’re going to get the adapter name
-
curAdapter = mid(data(n), instr(data(n)," adapter ")+9)
-
if instr(curAdapter,":") then
-
curAdapter = left(curAdapter,instr(curAdapter,":")-1)
-
end if
-
end if
-
if ucase(curAdapter) = ucase(AdapterName) then
-
if instr(data(n),"IPv4 Address") or instr(data(n), "IP Address") then ‘after IPv6 came out, this was the title for IPv4.
-
parts = split(data(n),":")
-
if trim(parts(1)) <> "0.0.0.0" then
-
strIPAddress= trim(cstr(parts(1)))
-
end if
-
end if
-
end if
-
next
-
GetIPAddress_Name = strIPAddress
-
End Function
That was the hard part. Now for the rest of the code.
-
strUsername="secret"
-
strPassword = "Secret!"
-
ConnectionName = "PrimarySonicwall"
-
-
set sh = createobject("wscript.shell")
-
sh.run "%comspec% /c route delete 192.168.1.0",0,true
-
sh.run "%comspec% /c route delete 192.168.2.0",0,true
-
sh.run "%comspec% /c route delete 192.168.4.0",0,true
-
sh.run "%comspec% /c rasdial """ & ConnectionName & """ " & strUsername & " " & strPassword,0,true
-
myVPN_IP = GetIpAddress_Name(ConnectionName)
-
wscript.echo ConnectionName & " IP:" & myVPN_IP
-
-
if myVPN_IP <> "" then
-
sh.run "%comspec% /c route add 192.168.1.0 mask 255.255.255.0 " & myVPN_IP,0,true
-
sh.run "%comspec% /c route add 192.168.2.0 mask 255.255.255.0 " & myVPN_IP,0,true
-
sh.run "%comspec% /c route add 192.168.4.0 mask 255.255.255.0 " & myVPN_IP,0,true
-
wscript.echo "VPN connected!"
-
else
-
wscript.echo "Connection Failed."
-
end if
As you can see, it calls the “rasdial” command, which is the command that loads and runs a VPN connection. (It sure would be nice if such a command could be modified with a parameter to return the IP address, but no such luck.) Then once the connection is established, we fetch the IP address, clear the existing route table entries, and add the new ones.
And that’s it. Feel free to use this; hopefully it’ll save you a little time somewhere.
If anyone feels like adding, or pointing people to, a script to create the VPN connection in the first place, I’d be delighted to see it.
Full Source:
-
strUsername="secret"
-
strPassword = "Secret!"
-
ConnectionName = "PrimarySonicwall"
-
-
set sh = createobject("wscript.shell")
-
sh.run "%comspec% /c route delete 192.168.1.0",0,true
-
sh.run "%comspec% /c route delete 192.168.2.0",0,true
-
sh.run "%comspec% /c route delete 192.168.4.0",0,true
-
sh.run "%comspec% /c rasdial """ & ConnectionName & """ " & strUsername & " " & strPassword,0,true
-
myVPN_IP = GetIpAddress_Name(ConnectionName)
-
wscript.echo ConnectioName & " IP:" & myVPN_IP
-
-
if myVPN_IP <> "" then
-
sh.run "%comspec% /c route add 192.168.1.0 mask 255.255.255.0 " & myVPN_IP,0,true
-
sh.run "%comspec% /c route add 192.168.2.0 mask 255.255.255.0 " & myVPN_IP,0,true
-
sh.run "%comspec% /c route add 192.168.4.0 mask 255.255.255.0 " & myVPN_IP,0,true
-
wscript.echo "VPN connected!"
-
else
-
wscript.echo "Connection Failed."
-
end if
-
-
Function GetIPAddress_Name(AdapterName)
-
‘=====
-
‘ Returns single of IP Address bound to AdapterName
-
‘ by ipconfig or winipcfg…
-
‘
-
‘ Win98/WinNT have ipconfig (Win95 doesn’t)
-
‘ Win98/Win95 have winipcfg (WinNt doesn’t)
-
‘
-
‘ Note: The PPP Adapter (Dial Up Adapter) is
-
‘ excluded if not connected (IP address will be 0.0.0.0)
-
‘ and included if it is connected.
-
‘=====
-
set sh2 = createobject("wscript.shell")
-
set fso = createobject("scripting.filesystemobject")
-
-
Set Env = sh.Environment("PROCESS")
-
if Env("OS") = "Windows_NT" then
-
workfile = fso.gettempname
-
sh.run "%comspec% /c ipconfig > " & workfile,0,true
-
else
-
‘winipcfg in batch mode sends output to
-
‘filename winipcfg.out
-
workfile = "winipcfg.out"
-
sh.run "winipcfg /batch" ,0,true
-
end if
-
set sh2 = nothing
-
set ts = fso.opentextfile(workfile)
-
data = split(ts.readall,vbcrlf)
-
ts.close
-
set ts = nothing
-
fso.deletefile workfile
-
set fso = nothing
-
strIPAddress = ""
-
for n = 0 to ubound(data)
-
if instr(data(n)," adapter ") then ‘ we’re going to get the adapter name
-
curAdapter = mid(data(n), instr(data(n)," adapter ")+9)
-
if instr(curAdapter,":") then
-
curAdapter = left(curAdapter,instr(curAdapter,":")-1)
-
end if
-
end if
-
if ucase(curAdapter) = ucase(AdapterName) then
-
if instr(data(n),"IPv4 Address")then
-
parts = split(data(n),":")
-
if trim(parts(1)) <> "0.0.0.0" then
-
strIPAddress= trim(cstr(parts(1)))
-
end if
-
elseif instr(data(n),"IP Address") then
-
parts = split(data(n),":")
-
if trim(parts(1)) <> "0.0.0.0" then
-
strIPAddress= trim(cstr(parts(1)))
-
end if
-
end if
-
end if
-
next
-
GetIPAddress_Name = strIPAddress
-
End Function