Friday, March 04, 2016

Test connectivity via a specific network interface

Recently while working on a Private cloud implementation, I came across a scenario where I needed to test connectivity of a node to the AD/DNS via multiple network adapters. 

Many of us would know that having multiple network routes is usually done to take care of redundancy. So that if a network adapter goes down, one can use the other network interface to reach out to the node.

In order to make it easy for everyone to follow along, below is an analogy for the above scenario:

My laptop has multiple network adapters (say Wi-Fi and Ethernet) connected to the same network. Now how do I test connectivity to a Server on the network only over say Wi-Fi network adapter?

So let’s get to it and explore some options at hand.

Below are the network adapters that list out on my laptop:

Now I want to test connectivity to a Windows Server 2012R2 running in my network having IP address

Using ping.exe

I can specify the source address to ping.exe using –S switch and verify if the Server (IP is responding to ping/ICMP requests on a specific network interface.
But there is a gotcha with the above approach, what if the server response to ping is disabled? Or the network firewall in place drops ICMP requests.

Using Test-NetConnection.

I initially thought of using Test-NetConnection cmdlet (available on Server 2012 & Windows 8 above with NetTCPIP module) to do a winrm port check to the server (port 5985), but the cmdlet doesn’t let you specify a source address for doing a port query. It will automatically select a network interface to perform the port query (based on the internal windows routing table). See below the output of the cmdlet, it selects the Ethernet interface to perform the port check.

See below the syntax of the cmdlet.

PS>gcm test-netconnection -syntax                                                                                                                                                                                                                           
Test-NetConnection [[-ComputerName] ] [-TraceRoute] [-Hops ] [-InformationLevel ] []                                                                                                                                 
Test-NetConnection [[-ComputerName] ] [-CommonTCPPort]  [-InformationLevel ] []                                                                                                                                   
Test-NetConnection [[-ComputerName] ] -Port  [-InformationLevel ] []                                                                                                                                                 

One could play with route.exe and change the network route to the network where the server lies and then do a Test-NetConnection on the winrm port, complicated way to handle such a small problem.

Or better as my friend Jaap Brasser told me on IM, disable the network adapter and then do the Test-NetConnection.

Using TCPClient

Now let’s talk about how we can do this in PowerShell.
I can create a TCP Client and connect to the server on winrm port but how do we make sure that it gets routed via a specific network interface.

The answer is really simple, we create a local endpoint (IP + port) and bind our TCP Client to it. All the communications then happen via the socket. 

Below is the code snippet and the explanation of it follows:

$SourceIP = [IPAddress]''; # My WiFi Adapter IP address
$Destination = [IPAddress]'' # Destination Server address
$DestinationPort = 5985 # PSRemoting port to connect to over TCP

# get an unused local port, used in local IP endpoint creation
$UsedLocalPorts = ([System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties()).GetActiveTcpListeners() |
                        where -FilterScript {$PSitem.AddressFamily -eq 'Internetwork'} |
                        Select -ExpandProperty Port
do {
        $localport = $(Get-Random -Minimum 49152 -Maximum 65535 )
    } until ( $UsedLocalPorts -notcontains $localport)

# Create the local IP endpoint, this will bind to a specific N/W adapter for making the connection request
$LocalIPEndPoint = New-Object -TypeName System.Net.IPEndPoint -ArgumentList  $SourceIP,$localport

# Create the TCP client and specify the local IP endpoint to be used.
$TCPClient = New-Object -Typename System.Net.Sockets.TcpClient -ArgumentList $LocaIPEndPoint # by default the proto used is TCP to connect.

# Connect to the Destination on the required port.
$TCPClient.Connect($Destination, $DestinationPort)

# Check the Connected property to see if the TCP connection succeeded. You can see netstat.exe output to verify the connection too

In the above code after assigning the source IP, destination IP & destination port, there is code which selects a local port to be used.

We have to be careful while selecting a random local port as it might be already be used in an active TCP connection. There is a clever .NET way of getting a list of already used ports on a local machine by using the GetActiveTcpListeners() method.

$UsedLocalPorts = ([System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties()).GetActiveTcpListeners() |
                        where -FilterScript {$PSitem.AddressFamily -eq 'Internetwork'} |
                        Select -ExpandProperty Port

Once I have a list of all the used local port, I can select a non-used ephemeral port (range) using the code snippet below:

do {
        $localport = $(Get-Random -Minimum 49152 -Maximum 65535 )
    } until ( $UsedLocalPorts -notcontains $localport)

Now it is time to create the local endpoint using the source IP of the network interface and the local unused port.

$LocalIPEndPoint = New-Object -TypeName System.Net.IPEndPoint -ArgumentList  $SourceIP,$localport

Once the Local endpoint is created, construct a TCP client passing the local endpoint as an argument to it. This will ensure that the TCP connection request flows via that specific N/W adapter.

Once that is done, call the Connect() Method on the TCPclient to connect to the destination. Now the TCP connection uses the SourceIP (on a specific network adapter) to reach out to the destination.

Note – One can try specifying a source address which is not assigned to the machine, it will let you create the local endpoint but when you try creating the TCPClient it will throw an error saying that the address is not valid in the context.

Using the above logic and creating an advanced function should be straight enough, that is an exercise left for the reader.