Build an isolated network with Hyper-V for a virtual lab

Occasionally, I need to spin up a few local virtual machines on my Windows 10 laptop to test a networking concept. Building a practical multi-machine configuration can be challenging. For me, this typically means:

  • The VM host can connect to the guest VMs.

  • The guest VMs can connect to the host.

  • The guest VMs can connect to each other.

  • The guest VMs can access the Internet.

  • And other network devices can connect to my guest VMs.

A simple solution may be to place the guest VMs on the same external network as the VM’s host network and obtain the IP addresses dynamically or to set them statically. Although this satisfies the listed requirements, it also means you don’t you have full control over the network configuration.

A particular issue for me is when I move between home and office networks, the IP ranges differ and require new IP addresses for my VMs. 😢 It also means certain tests, like running a DHCP server, is not a good idea or even possible.

The Solution Design

The image below shows the network topology with two networks, the home or office network (192.168.1.0/24) and the VM network (192.168.2.0/24).

Solution Design

The critical piece in this architecture is the Router with two virtual Network Interface Cards (NICs). It connects the two networks by routing traffic between them. Although connected, the virtual and physical network are still isolated from each other and won’t allow network traffic such as DHCP broadcasts.

Although the Router is, in a sense "bridging" traffic between the two networks, the term Bridging in networking is different from the routing explained here. See Bridging on Wikipedia for more information.
I am sure Bridging can provide similar results. Still, I want to keep an option open to further control the routed traffic with something like iptables.

A successful setup should allow for communication between the following nodes on the network:

Network connectivity from Carol (192.168.2.13)
$ ping 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=62 time=0.776 ms

and also

Network connectivity from Alice (192.168.1.3) or Bob (192.168.1.4)
> ping 192.168.2.13
Pinging 192.168.2.13 with 32 bytes of data:
Reply from 192.168.2.13: bytes=32 time<1ms TTL=63

What you will need

The table below list the components necessary to build the Solution Design; the network address ranges are configurable for your specific needs.

Component Purpose Configuration

Network

Preferably a wired network but WiFi also works.

Nothing special

Your laptop/workstation
(192.168.1.3)

Virtual Machine Host

  • Windows 10 (Pro, Enterprise or Education)

  • Hyper-V enabled

  • Enough memory to host the VMs

VM Guest
Linux Router
(192.168.1.10)
(192.168.2.10)

Virtualised Linux Router

  • Debian 10.5

  • 384 MB memory

  • 1 GB disk space

  • 2x virtual NICs

VM Guest (Optional)
(192.168.2.13)

For testing connectivity

Your choice

Network router (Optional)

Provides Internet access, DHCP server and other network functions

Nothing special

Build instructions

As evident from several guides on the Internet, there are many alternative techniques to build this virtualised network. I selected a configuration that suits my needs, which meant something that plays well with Windows 10, Windows Sandbox, WSL2 and Docker without having to buy or download additional software.

I used VirtualBox in the past but using it with Docker Desktop for Windows and Windows Sandbox creates problems that I wanted to avoid, so Hyper-V it is. 😉

Hyper-V setup

Enable the Hyper-V role on your Windows 10 machine that will serve as the VM host.

Turn Windows features on or off

Next, configure a new Virtual Switch for your network. You can think of the Virtual Switch as a software-defined version of a physical network switch. The Virtual Switch provides network connectivity to the selected VMs in the virtualised network.

Open Virtual Switch Manager…​ from the Hyper-V management console.

Hyper-V management console

Press the Create Virtual Switch button and create a switch named External (LAN) and continue to configure the switch as per the rest of the settings below:

Configuration for Virtual Switch
Name Value Comment

Name

External (LAN)

The name can be anything descriptive

Connection type

External network → Ethernet or WiFi connection

Ethernet is preferred, it is faster, but some people experienced issues with a WiFi configuration

Allow management operating system to share this network adapter

Yes

Share your existing network adapter between the host and guest VMs

At this point, the newly created Virtual Switch is configured but not yet in use as it is not assigned to any VMs yet.

Configuring the Linux Router (192.168.1.10 & 192.168.2.10)

The router is a virtual appliance with two virtual NICs that connects the two networks. I selected Linux as the operating system for this task as it is free, simple to configure and only requires a small amount of system resources.

I tested with Ubuntu 20.04 and it also works. Still, I wanted to reduce the resource footprint a bit and decided to use Debian 10.5 instead. I am sure this can still be further optimised, but 1GB of hard disk space and 256MB of memory was good enough for me.

To create the VM run the Powershell commands below with Admin privileges.

## Settings
# Download settings
$DebianIsoUri = 'https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-10.5.0-amd64-netinst.iso'
$InstallMedia = Join-Path $env:USERPROFILE 'debian.iso'

# New VM settings
$VmName = 'LinuxRouter'
$VmPath = Join-Path $env:PROGRAMDATA Microsoft\Windows\Hyper-V 'Virtual Machines'
$VmVhdPath = Join-Path $env:PUBLIC Documents Hyper-V 'Virtual hard disks' "$VmName.vhdx"
$ExternalSwitch = 'External (LAN)' (1)
$InternalSwitch = 'Internal Switch' (2)
$MemoryStartupBytes = 2GB (3)
$NewVHDSizeBytes = 1GB

## Actions
# Download ISO to the user's download folder
Invoke-WebRequest -Uri $DebianIsoUri -OutFile $InstallMedia (4)

# Create the VM for the Linux router
New-VM -Name $VmName `
    -NewVHDPath $VmVhdPath `
    -MemoryStartupBytes $MemoryStartupBytes `
    -NewVHDSizeBytes $NewVHDSizeBytes `
    -Generation 1 `
    -Switch $ExternalSwitch

Add-VMNetworkAdapter -VMName $VmName -SwitchName $InternalSwitch
Set-VMDvdDrive $VMName -ControllerNumber 1 -ControllerLocation 0 -Path $InstallMedia

## Start and connect to the VM
Start-VM $VmName
vmconnect.exe localhost $VmName
1 The name of the switch must match exactly with the newly created virtual switch, this will be eth0.
2 This is the default Hyper-V switch, this will be eth1.
3 The installation process requires more memory, and we will reduce it later in the process.
4 This is a 360MB download.

After running create-router.ps1 the Router VM should be running and prompting you to start the Debian installation process. The installation is relatively straightforward, except for defining the partition table for a 1GB drive. Unfortunately, the Guided partitioning process fails because of the small 1GB drive. It requires the additional step of manually creating the partition table.

The primary settings during the OS installation process are:

  1. Configure eth0 for networking, eth1 will be configured later.

  2. Manually create a partition with the following configuration:

    1. Primary 1GB, ext4

    2. Logical 71MB, swap

Alternatively view the installation instructions here.

When the installation is done, update the current allocated memory by reducing it to 384MB.

Stop-VM $VmName
Set-VMMemory $VmName -StartupBytes 384MB -DynamicMemoryEnabled $false
Start-Vm $VmName

Next, connect to the newly created Linux VM to set its network settings. I suggest using Hyper-V to connect to the VM and obtain its current IP address after that connect to it with a more convenient terminal.

To obtain the IP address for eth0, run:

ip -br a show dev eth0

Update the Debian networking settings by updating the /etc/network/interfaces file.

su -
vi /etc/network/interfaces

Update the contents of the file according to your network settings:

/etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
allow-hotplug eth0

#iface eth0 inet dhcp (1)
iface eth0 inet static
  address 192.168.1.10 (2)
  netmask 255.255.255.0
  gateway 192.168.1.1 (3)

# The secondary network interface
allow-hotplug eth1
iface eth1 inet static
  address 192.168.2.10  (4)
  netmask 255.255.255.0
1 Commented in case eth0 should be obtained from a DHCP reservation.
2 This should be an available IP address on the physical network.
3 The default gateway on the physical network.
4 The secondary IP address (eth1) for the Linux Router.

Apply the new network settings with the following commands (If you are connected with SSH you will be disconnected when the network settings for eth0 kicks in).

su - (1)
ifup eth1
ifdown eth0; ifup eth0
1 Execute the following commands as the root user, make sure to use the password that you specified during the Debian installation process.

To route packets between the two networks, IP forwarding has to been enabled on the Linux Router. While connected to the Linux Router 192.168.1.10 perform the following steps.

Update /etc/sysctl.conf with the setting below to enable forwarding and also to persist the setting when the VM restarts.

/etc/sysctl.conf
net.ipv4.ip_forward=1

Apply the change by running:

sysctl -p /etc/sysctl.conf
modprobe iptable_nat
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
iptables -A FORWARD -i eth1 -j ACCEPT
apt install iptables-persistent (1)
1 iptables-persistent persist the iptable settings between reboots, when prompted choose to save rules for ipv4.

At this point, it should be possible to run the following commands from the Linux Router successfully:

ping 192.168.1.1
ping 192.168.1.3 (1)
ping 192.168.2.13 (2)
ping www.google.com
1 Make sure Windows 10’s firewall is not blocking traffic.
2 Optional, if the VM already exists.

The hard work has been done, and we are only left with a few small steps on the other network nodes. 😅

A quick note about Network Gateways

To route traffic between different networks, the help of network gateways is needed. Routing tables on each client contains rules that indicates which gateway to use for a given destination.

network alice Alice 192.168.1.3 bob Bob 192.168.1.4 carol Carol 192.168.2.12 dave Dave 192.168.2.13 gwd Default Gateway internet Internet gwd--internet 203.150.52.6 subnet1 192.168.1.0/24 gwd--subnet1 192.168.1.1 gw1 Lab Gateway gw1--subnet1 192.168.1.10     subnet2 192.168.2.0/24 gw1--subnet2 192.168.2.10 subnet1--alice subnet1--bob subnet2--carol subnet2--dave
Gateway example

In this example, when Alice communicates with Dave, the routing rules for Alice contains something like "send all 192.168.2.0/24 traffic to 192.168.1.10".

A slightly more involved scenario is when Carol wants to access the Internet, it requires an additional gateway hop.

  • The first routing rule on Carol’s machine is to send all undefined traffic (0.0.0.0) to 192.168.2.10

  • The second routing rule on the Lab Gateway is to send all undefined traffic (0.0.0.0) to 192.168.1.1

Configuring Alice (192.168.1.3) and Bob (192.168.1.4)

To access network devices on 192.168.2.0/24, a routing rule is required for devices on the 192.168.1.0/24 network. This routing rule can be implemented in two ways:

  • Local routing on each machine, This can be useful if you want to limit the routing to specific machines or if you don’t have access to the routing table on the default gateway 192.168.1.1. For an office network, this is a safe option when you don’t want to route everyone’s traffic to your network lab.

  • Update the routing table on the default gateway. This has the advantage that all clients on the source network gets the benefit of a single route rule entry. This is my configuration for my home network, and I achieve this by updating my Asus router with an additional routing rule.

To add a routing rule on Alice and Bob run the following Powershell commands as Admin:

$adapter = Get-NetAdapter |
Where-Object {$_.Name -like '*External*LAN*'} | (1)
Select-Object -Property ifIndex, Name

New-NetRoute `
-DestinationPrefix '192.168.2.0/24' `
-InterfaceIndex $adapter.ifIndex `
-NextHop 192.168.1.10
1 Make sure the virtual switch is named External (LAN) for this command to find the correct network adapter.

To confirm the entry has been added to the local routing table run:

Get-NetRoute | Where-Object {$_.DestinationPrefix -eq '192.168.2.0/24'} | Format-Table -AutoSize

and it should show the following result (your network interface index might differ)

ifIndex DestinationPrefix NextHop      RouteMetric ifMetric PolicyStore
------- ----------------- -------      ----------- -------- -----------
2       192.168.2.0/24    192.168.1.10         256 25       ActiveStore

At this point, you should be able to ping Carol and Dave and the Linux Router on 192.168.2.10.

Alternatively, you can also perform the routing rules in an elevated Command Prompt. This produces the same results, but Powershell makes it a bit easier to script the commands (😉 use Powershell).
netsh int ipv4 show interfaces (1)
pause
route -p ADD 192.168.2.0 MASK 255.255.255.0 192.168.1.10 METRIC 100 IF 2(2)
route PRINT (3)
1 Lists all the network adapters to obtain its interface index for the next command.
2 Replace the IF value of 2 with the network index for the adapter named vEthernet (External (LAN)) that was returned from the previous command. Also, note the -p switch to persist the rule between system restarts.
3 Confirm that the network setting has taken effect by reviewing the Persistent Routes section if -p was specified.

When your testing is complete and want to remove the routing rules, run one of the following commands:

Powershell: Delete route
Remove-NetRoute -DestinationPrefix '192.168.2.0/24'
Command Prompt: Delete route
route DELETE 192.168.2.0

Configuring Carol (192.168.2.13) and Dave (192.168.2.14)

This step is optional.

Typically, these VMs runs useful workloads for development or testing purposes, and it is up to you what you install on them. For these machines to access the Internet and the 192.168.1.0/24 network, make sure these machines have a default gateway of 192.168.2.10.

Testing the network

At this point, you should be able to communicate between all nodes on either of the networks.

The following tests should all work:

Keep in mind that Windows 10’s firewall will block incoming traffic by default and that you might want to adjust Windows Defender Firewall rules accordingly.
From Alice and Bob (192.168.1.0/24)
ping 192.168.1.10
ping 192.168.2.10
ping 192.168.2.13
From the Linux Router
ping 192.168.1.1
ping 192.168.1.3 (1)
ping 192.168.1.4
ping 192.168.2.13
ping 192.168.2.14
ping 1.1.1.1
ping www.google.com (2)
1 Make sure Windows 10’s firewall is not blocking ICMP traffic.
2 Review /etc/resolv.conf to ensure a valid DNS resolver is configured.
From Carol and Dave (192.168.2.0/24)
ping 192.168.2.10
ping 192.168.1.1
ping 192.168.1.3
ping 192.168.1.4
ping 1.1.1.1
ping www.google.com

What is next?

We are done, and you should have an isolated network environment for your test lab. 👊

It might be useful to automate the process of switching between networks if you move between home and office networks often. This can be accomplished by scripting the required changes on the Linux router and your machine.

comments powered by Disqus