Adding a static route to the Windows routing table

This recipe follows right on the heels of our previous recipe, Multi-homing your Windows Server 2019. If you have never worked on a server that is making use of more than one NIC, then you have probably never had a reason to poke around in the Windows routing table. The minute that you are tasked with setting up a new server that needs to be connected to multiple networks, or you get thrown into a situation where you need to troubleshoot such a system, this suddenly becomes critical information to have in your back pocket.

On a server that is connected to multiple networks, you only have one Default Gateway address defined. This means any subnets that need to be reached by flowing through one of the other NICs – the ones that do not contain the Default Gateway – need to be specifically defined inside the routing table. Otherwise, Windows simply does not know how to get to those subnets and it will attempt to push all traffic through the Default Gateway. This traffic will never make its way to its destination and communications will fail.

Today, we are setting up a new VPN server. This server has a NIC plugged into the internet where remote clients will come in, and another NIC plugged into the internal network so that the client traffic can make its way to the application servers that the users need to access. In this scenario, the Default Gateway must be populated on the external NIC. There will be no Default Gateway address defined on the internal NIC, and without some assistance, Windows will have no idea how to properly route traffic toward the servers inside the network.

For our example, the internal NIC is plugged into the 172.16.97.x network. Since it has a direct physical connection to this network, it is automatically able to properly contact other servers that reside on this subnet. So, if the VPN server was 172.16.97.5 and we had a Domain Controller running on 172.16.97.1, we would be able to contact that Domain Controller without any additional configuration. But most companies have multiple subnets inside their network. So, what if our VPN users needed to contact a web server that is sitting on the 172.16.120.x network? When traffic comes into the VPN server looking for a destination of 172.16.120.10 (the web server), the VPN server will check its local routing table and find that it does not have an entry for the 172.16.120.x network. Since it doesn't know what to do with this request, it sends it to the Default Gateway, which sends the packets back out the external NIC. Those packets don't have a valid destination to reach through the external NIC, which is plugged into the internet, and so the traffic simply fails.

We need to define a static route in the routing table of our VPN server so that when VPN clients request resources inside the 172.16.120.x network, that traffic makes its way to the destination network successfully. We need to bind this route to our internal NIC so that the VPN server knows it must send these packets through that physical network interface.

Getting ready

We are setting up a new Windows Server 2019 VPN server. This server has two NICs installed: one plugged into the internet and the other plugged into the internal network. Inside our corporate network, there are two subnets: 172.16.97.x (/24), which our internal NIC is plugged into, and 172.16.120.x (/24), where our web server resides. There is, of course, a router between the two internal subnets, which is how traffic physically flows between the two. The IP address of that router is 172.16.97.1. If we were able to configure a Default Gateway on the internal NIC of our VPN server, it would be set to 172.16.97.1, and all traffic would work without any further input. However, since our VPN server is multi-homed and there can only be a Default Gateway configured on the external NIC, we need to tell the server that it must push 172.16.120.x traffic through 172.16.97.1 by using the internal NIC.

How to do it…

We need to do the following to create a static route in our VPN server:

  1. Identify the subnet that we want to contact. In our example, this is 172.16.120.0.
  2. Identify the subnet prefix length, which is 24 (equivalent to the subnet mask of 255.255.255.0).
  3. Identify the IP address of the router that will get us to that network, which is 172.16.97.1.
  4. Identify the Interface ID number of the physical NIC that needs to carry this traffic. You can determine this by running Get-NetIPInterface -AddressFamily IPv4 and looking at the ifIndex field. This could be different for every server, but for this example, it is 4.
  5. We now have all the information needed to put our route statement together and bind it to our internal NIC. The command we would run for our example route is as follows:

    New-NetRoute -DestinationPrefix '172.16.120.0/24' -InterfaceIndex 4 -NextHop 172.16.97.1

    The non-PowerShell equivalent would be as follows:

    route add -p 172.16.120.0 mask 255.255.255.0 172.16.97.1 if 4

See how cryptic the non-PowerShell command is versus the nicely labeled PowerShell command? Note that the PowerShell command will look like it is outputting the new route twice. This is because the route is actually added twice, but to two different parts of the system (the currently active routing table and the routing table that is loaded when the server reboots). You can verify that it was added correctly with the Get-NetRoute command:

Figure 3.9 – Example of adding a new route and reading the existing routing table

How it works…

With a multi-homed server, only one NIC will have a Default Gateway. Therefore, any subnets that we need to access through the other interfaces have to be specifically defined. Before we added this new route, the server was completely unable to contact the 172.16.120.x network. This is because the routing table did not have any information about this subnet, so any traffic trying to get there was being sent out the Default Gateway, which is on the external NIC plugged into the internet. By adding a static route to our server, we have now defined a routing path for the server to take whenever it has traffic that needs to get to 172.16.120.x.

If you have many subnets in your network, you may be able to cover them all with a blanket route statement. A blanket route is also known as an aggregate or supernet route. This could save you the time of having to set up a new route statement for each and every one of your networks. For example, if we had many 172.16.something networks and we wanted to flow all of them through our internal NIC, we could do that with a single route statement, as follows:

New-NetRoute -DestinationPrefix '172.16.0.0/16' -InterfaceIndex 4 -NextHop 172.16.97.1

This route would send any 172.16.x.x traffic through the internal NIC. Whether you aggregate your routes like this or set each one up inpidually doesn't make a difference to the server, as long as its routing table contains information about where to send the packets that it needs to process.

The other option, which we will look at in the next recipe, is that your server can learn these routes from the actual router itself if the router has been configured to permit this.