Port forwarding to access a Hyper-V VM

Angel Mercado
9 min readMay 7, 2024

--

· Why Bother setting this up?
Setup
The goal
· The LAN problem
Default switch limitations
· Figuring out a solution
· Putting it all together
Initial VM setup
PowerShell script
Scheduled Task setup
Setup firewall rule
Configuring SSH and key based authentication
Configuring WAN access (optional)
· Neat tricks
Accessing VM services
Accessing the Hyper-V host
· Troubleshooting

Why Bother setting this up?

I am currently studying for the RHCSA (Red Hat Certified Systems Administrator) exam and wanted to create a RHEL (Red Hat Enterprise Linux) virtual machine that I could access during my down time at work to get some on-hands studying done. I quickly realized that with my limited setup it would be challenging to gain direct access to this VM in its current configuration. Let’s start by first taking a look at my current set-up.

Setup

I have two laptops, one acts as a server with windows installed which stays at home and runs Hyper-V with a RHEL VM installed. The other laptop I use daily runs Manjaro Linux. I regularly bring this laptop to work and use it for my studies.

The goal

To be able to access the RHEL VM from both my LAN and WAN.

The LAN problem

The underlying problem is that in it’s default state the VM cannot be accessed via the LAN. The reason for this is that I am using the default switch in Hyper-V. With two ethernet adapters this would not be an issue as you can easily pass the second adapter to the VM, configure an external switch and allow it to access the LAN. Sure, I could get a cheap USB ethernet/Wi-Fi adapter from amazon, but I figure other people may not be as lucky so let’s work with what we have.

Default switch limitations

First and foremost the default switches main purpose is to facilitate communications between the host systems and any underlying virtual machines. Meaning that when using this switch, LAN devices will be unable to directly communicate with my VM. The default switch also contains limited configuration options, such as the inability to configure static IP’s that remain persistent across reboots. This will be addressed later in my configuration.

Please note: there are many other (and likely better) ways to accomplish this same task, however I liked the challenge of getting this to work and love the process of figuring stuff like this out.

Figuring out a solution

Windows luckily has a feature called netsh short for Network shell. netsh allows us to modify the network configuration and much more from the command line. The feature of netsh I am most interested in is portproxy which allows us to proxy between IPv4/IPv6 networks.

Lets look at the syntax:

netsh interface portproxy delete v4tov6 listenport= {Integer | servicename} [[listenaddress=] {IPv4Address| hostname}] [[protocol=]tcp]

With this tool we should be able to open a port on the Windows host and use that port to forward traffic over to our VM.

The problem with simply setting this up is that when a reboot happens the VM IP address will change. This means we would need to set up another netsh command each time a reboot happens. This is not feasible especially if I am not home to make any configuration changes.

The solution to this is to create a simple PowerShell script that is executed via a scheduled task on reboot. The script should do the following:

  • First wait a predetermined amount of time to allow the Hyper-V service and VM to start
  • Next we remove old netsh configurations
  • Gather the new IP address of the VM
  • Create a new netsh configuration by inserting the new VM IP address into the command

Additional tasks:

  • create firewall rule to allow TCP traffic to configured port (this setup will not work without this step)
  • Set up a scheduled task to execute the script at startup
  • Setup Port forwarding on router to allow access over WAN (Optional)

Putting it all together

Initial VM setup

One of the first things I realized is that I needed this VM to start automatically incase Microsoft randomly chose to push an update which required a restart. Setting this up is very simple as shown in the screenshots below:

Note: For RHEL I ran into issues when the “Automatic Stop Action” was not set to “Power Off” It for some reason refuses to receive the new IP which stops this entire process from working.

PowerShell script

Change the ‘listenport’ to whichever port you would like to use.

# Configures port forwarding to RHEL VM on local port 777

#Give Hyper-V time to start-up
Start-Sleep -Seconds 60
# Return RHEL IP
$vm_ip = Get-VM | Where-Object {$_.State -eq "Running"} | Get-VMNetworkAdapter | ForEach-Object { $_.IPAddresses -split ',' | Where-Object {$_ -like '*.*'} }
# Remove old netsh config
netsh interface portproxy delete v4tov4 listenport=777 listenaddress=0.0.0.0
# Add new route with new RHEL IP
netsh interface portproxy add v4tov4 listenport=777 listenaddress=0.0.0.0 connectport=22 connectaddress=$vm_ip

Scheduled Task setup

  • Create a new scheduled task and name it something appropriate
  • Ensure the task is executed as your windows user
  • Check the box “run with highest privileges”
  • On the triggers tab set it to run at system startup
  • On the actions tab, have it start a program
  • Program/script should be: PowerShell
  • Arguments should be: -ExecutionPolicy Bypass -command C:\users\bt\desktop\vm-ip.ps1
  • Finally on the conditions tab check the box “Start only if the following network connection is available” and select “any network”

Setup firewall rule

From an elevated PowerShell shell enter the following:

New-NetFirewallRule -DisplayName "RHEL Access" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 777

Configuring SSH and key based authentication

Note it is not necessarily important to configure key based authentication if you are only accessing from the LAN, however it is highly recommended that you do so if accessing from WAN.

Now log in to your VM and ensure your OpenSSH-Server is installed.
Starting SSH:

sudo systemctl enable sshd
sudo systemctl start sshd

Now that SSH service has started you should reboot your windows machine. Upon startup the PowerShell script will be executed and you should now have the ability to access the VM from the LAN to make the final SSH configurations.

Once the reboot is completed test the connection to your VM from the computer you plan on accessing the VM with (must be on same LAN):

ssh -p 777 [VMuser]@[HyperVServerIP]

If this is successful you should exit the SSH connection by typing “exit”.

Now that you are back on your machine we will create SSH keys:

ssh-keygen

Follow the steps, name and add a strong passphrase to use with your key.

Now let’s copy our public key to the server :

ssh-copy-id -p 777 -i .ssh/[KeyName.pub] [VMuser]@[HyperVServerIP]

You should now be able to login with public key authentication:

ssh -p 777 -i .ssh/[PrivateKey] [VMuser]@[HyperVServerIP]

Now we will edit the SSH server configuration to ensure users can only authenticate using key based authentication.

sudo vim /etc/ssh/sshd_config

Change and uncomment the following line:

PasswordAuthentication no

Restart the SSH service:

sudo systemctl restart sshd

Exit the SSH shell and attempt to authenticate without a key:

ssh -p 777 [VMuser]@[HyperVServerIP]

You should get an error.

Configuring WAN access (optional)

This step will be different depending on what kind of router you use and whether or not your ISP blocks port forwarding to the WAN. Either way you should be able to look through your router settings and figure it out quite easily.

First look for a DHCP server tab or setting page. Once you find it create a static IP binding for the Hyper-V server to ensure it remains persistent. Once that is configured look for a port forwarding option, sometimes this is hidden under an advanced settings tab or similar. Ensure you are port forwarding the port you configured in the script (777 in my case). You may need to restart your router for the changes to be applied. After it’s all setup you need to find your public IP address. You can do this by simply googling “what's my IP” or via curl:

curl -4 icanhazip.com

Now let’s test the connection:

ssh -p 777 -i .ssh/[privkey] [VMuser]@[PubIP]

Neat tricks

Using some cool tools available on Linux we are able to truly get the most out of this configuration.

Accessing VM services

SSHuttle allows you to get a VPN-like connection to your VM. This allows us to access any services it is providing (as well as it’s host)

You can find SSHuttle here: https://github.com/sshuttle/sshuttle

Establishing the tunnel:

To set this up properly you are first going to need the IP address and subnet information from your VM. You will need to do this each time the IP changes, although you could automate the retrieval of the IP if you were so inclined (I provide an easy example further below). The command to initiate the tunnel is:

sshuttle -r [VMUser]@[PubIP]:[port] -e 'ssh -i .ssh/[privkey]' [VMip]/20

Leave this terminal up and running and let’s test to see if we are able to access any of the VM’s services.

SSH back into your VM from another terminal and type:

python3 -m http.server 8000

From your remote device run the following:

curl http://[VMIP]:8000

You should see the output of the current directory you started the python http server in.

If you want to easily retrieve the IP information required for SSHuttle you can edit the variables in the script below and run it to get the command you will run:

#!/bin/bash

# Enter appropriate variables for your use case

pubip="8.8.8.8"
port="777"
username="sshuser"
keyloc="~/.ssh/id_rsa"

# get private ip and cidr
# note you may need to change interface if yours is not eth0
ssh -p $port $username@$pubip -i $keyloc "ip a | grep eth0 | grep inet | awk '{print \$2}'" > ~/.ipinfo.txt

privip=$(cat ~/.ipinfo.txt)


printf "Your sshuttle command will be: \nsshuttle -r $username@$pubip:$port -e 'ssh -i $keyloc' $privip\n"

rm -rf ~/.ipinfo.txt

Accessing the Hyper-V host

To access the server we will again use SSHuttle to establish the tunnel, then use RDP to access the server. We will once again need some information that can be retrieved via the VM. In this case we will need the server IP address. Note that this is not the LAN address your server has but rather the IP on the default switch interface which will be different. To get this IP SSH to the VM again and type:

arp -a

You should see the computer name of the server along with it’s IP address. Now ensure the SSHuttle tunnel is established and use the following command to RDP to the server:

xfreerdp /u:[windowsUser] /v:[windowsIP] /smart-sizing:1920x1080

You can also chose to use a different application for RDP such as Remmina or similar.

Troubleshooting

There are a ton of things that can go wrong when setting this up, however it isn’t too difficult to troubleshoot.

Ensure your VM is powered on and set to auto start on reboot.

Ensure your VM is actually getting an IP address from the default switch. I had an issue where my VM did not get an IP after reboot. I discovered that my issue was due to the saved state configuration of the VM itself.

Ensure that the netsh rules are being added correctly. You can view a list of the rules by typing the following:

netsh interface portproxy show all

Ensure that your firewall rule is set up correctly. You can press the windows button and type “firewall” and select “firewall and network protection” You should see your rule by name under the Inbound tab.

Check to see if the script is working correctly. You can open up the script in an administrator PowerShell ISE session and execute it to see if you get any errors. Make sure you check netsh rules to make sure everything is configured correctly.

Check the scheduled task. Scheduled tasks offers a history feature which shows you the status of each run and any errors that occurred. This feature was turned off for me. Turn it on to get more detailed information on any failures.

Ensure the scheduled task is configured correctly. It is important that each step is configured exactly like in the guide above.

Lastly use google, you will likely run into someone else that has had the exact issue you did.

--

--