34

I am running an SSH server within WSL2 on a WIN10 machine. To make that work I am using:

netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=22 connectaddress=172.19.237.178 connectport=22

This works fine initially. 172.19.237.178 is the IP of the WSL2 VM.

There is just one problem. I have the sshd set to run when the PC boots, and every time I boot the machine WSL2 has a different IP. Is there any way to configure WSL2 to use a static IP?

Edit: See this question for a workaround to determine the WSL machine's IP.

Nick
  • 1,403
  • 2
  • 13
  • 15
  • I just took a look through this GitHub issue thread about people having the same issue as you. Looks like it's just not supported right now. It looks like some people on that thread were able to get results that were acceptable by forwarding some of the VM's ports to the host, but that may not work in your situation. – Sam Forbis Sep 01 '20 at 14:46
  • @SamForbis I saw the same thread, and agree, I don't think the workaround will do what I need. I am trying to write scripts to RSYNC files from a remote computer into WSL.A workaround might be to determine when WSL has booted, figure out its IP, and then do the routing. The host's IP is static, so if I could just make the v4tov4 routing always work at boot then it doesn't matter that WSL's IP isn't static. – Nick Sep 01 '20 at 17:11

11 Answers11

27

The IP address of a WSL2 machine cannot be made static, however it can be determined using wsl hostname -I

Based on this I was able to create the following powershell script that will start sshd on my WSL machine and route traffic to it.

wsl.exe sudo /etc/init.d/ssh start
$wsl_ip = (wsl hostname -I).trim()
Write-Host "WSL Machine IP: ""$wsl_ip"""
netsh interface portproxy add v4tov4 listenport=22 connectport=22 connectaddress=$wsl_ip

I added the following to my sudoers file via visudo to avoid needing a password to start sshd

%sudo ALL=(ALL) NOPASSWD: /etc/init.d/ssh

Finally, from an administrative powershell terminal, I scheduled my script to run at startup

$trigger = New-JobTrigger -AtStartup -RandomDelay 00:00:15
Register-ScheduledJob -Trigger $trigger -FilePath C:\route_ssh_to_wsl.ps1 -Name RouteSSHtoWSL
Nick
  • 1,403
  • 2
  • 13
  • 15
  • Why don't we use set v4tov4 instead of add v4tov4? – Hunkoys Oct 29 '21 at 18:34
  • I didn't think to try it. Is there an advantage to using set rather than add? I'm always running this script at startup so there are no existing portproxies when I run it so I never encounter an issue with using add. – Nick Nov 02 '21 at 13:50
  • @Nick Hello sir, your answer was really helpful to me. It got me closer to what I'm looking for, but I haven't been able to completely solve my problem yet. I won't mind if you could help me take a look at this question that I posted over here https://superuser.com/questions/1685689/update-windows-host-file-with-wsl-ip-on-start-up – Ikechukwu Nov 05 '21 at 07:26
  • 1
    @Nick I just thought add might actually create a new set of rules everytime the ip changes. I haven't tried add though, so I can't prove it. set seems to work. It updates existing rules. – Hunkoys Nov 10 '21 at 05:49
  • 1
    Thanks to an upvote of my answer I can now comment, this answer is almost good for me, except wsl hostname -I returns two IP ("IP1 IP2") and I want first one, I then do: $wsl_ip = (wsl hostname -I).split(" ")[0] – gluttony Jun 16 '22 at 09:20
  • You can skip sudo by using this instead of your first line: wsl.exe -u root -e /etc/init.d/ssh start (or if you use systemd, wsl.exe -u root -e systemctl start sshd.service) – Arle Camille Jan 29 '23 at 14:19
  • thanks @Nick this was and worked for me – lux Feb 11 '23 at 11:09
  • If your wsl system has several addresses, this command can help determine the one used to connect to outside world: ip -o route get 8.8.8.8|awk '{print $3}' – Jose Manuel Gomez Alvarez Nov 25 '23 at 12:45
11

This solution helped me to set up a static ip of my wsl, try:

Run this on your windows host machine:

netsh interface ip add address "vEthernet (WSL)" 192.168.99.1 255.255.255.0

And this on your wsl linux machine:

sudo ip addr add 192.168.99.2/24 broadcast 192.168.99.255 dev eth0 label eth0:1;

But to keep this IP after the rebooting your sytem you need to set up those commands in the startup scrip.

  • 2
    This is perfect. This solved my problem. Thank you. – Rahmani Jul 17 '22 at 17:18
  • How do you run a sudo command on bootup inside WSL? For netsh I found https://social.technet.microsoft.com/Forums/office/en-US/cdf96a00-f0d1-4ba1-b887-3f0035fb01af/running-netsh-commands-on-start-up?forum=win10itprogeneral – chx Sep 15 '22 at 18:28
  • Hm, It just works inside my WSL) And netsh works on windows too (if you launch cmd like an Admin user) – Artemius Pompilius Sep 18 '22 at 17:20
  • Answer: wsl.exe -u root. Last line of my .zshrc: wsl.exe -u root service mysql status >/dev/null || wsl.exe -u root /etc/rc.local where rc.local starts mysql, apache, redis and adds this IP address. – chx Oct 25 '22 at 06:54
4

No need to use scripts to get the ip, just use Openssh server for windows and change the default shell from c:/system32/cmd.exe to c:/system32/bash.exe:

https://docs.microsoft.com/en-US/windows-server/administration/openssh/openssh_server_configuration

2

Using wsl and wsl2 at the same time caused problems for me. Could not get correct wsl hostname from the powershell command:

wsl hostname -I

Building on the answer from Nick, I needed to forward web 80 and 443, along with some other app ports.

ubuntu2004.exe -c "sudo /etc/init.d/ssh start"
$wsl_ip = (ubuntu2004.exe -c "ifconfig eth0 | grep 'inet '").trim().split()| where {$_}
$regex = [regex] "\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b"

$ip_array = $regex.Matches($wsl_ip) | %{ $_.value }

$wsl_ip = $ip_array[0]

Write-Host "WSL Machine IP: ""$wsl_ip"""

netsh interface portproxy add v4tov4 listenport=443 listenaddress=0.0.0.0 connectport=443 connectaddress=$wsl_ip netsh interface portproxy add v4tov4 listenport=8080 listenaddress=0.0.0.0 connectport=8080 connectaddress=$wsl_ip netsh interface portproxy add v4tov4 listenport=80 listenaddress=0.0.0.0 connectport=80 connectaddress=$wsl_ip netsh interface portproxy add v4tov4 listenport=3001 listenaddress=0.0.0.0 connectport=3001 connectaddress=$wsl_ip netsh interface portproxy add v4tov4 listenport=2222 listenaddress=0.0.0.0 connectport=2222 connectaddress=$wsl_ip netsh interface portproxy add v4tov4 listenport=22 listenaddress=0.0.0.0 connectport=22 connectaddress=$wsl_ip

DanielV
  • 21
  • Nice, thanks for sharing. You might be able to get rid of the regex if you do some cutting in the bash command. But then again, if the output of ifconfig changes this will break. bash -c "ifconfig eth0 | grep 'inet ' | cut -d ' ' -f 10" – Nick Feb 15 '21 at 20:56
2

I have not enough reputation to comment then I put my own answer but approved answer by Nick (https://superuser.com/a/1619390/1023342) is the good one for me, expect for one small detail, for me wsl hostname -I returns two IP ("IP1 IP2") and I want first one, I then do:

$wsl_ip = (wsl hostname -I).split(" ")[0]
netsh interface portproxy add v4tov4 listenport=22 connectport=22 connectaddress=$wsl_ip
gluttony
  • 224
1

A no-brainer solution, I run this script manually(run as administrator) everytime I need to proxy a port to WSL.

proxyport.bat

wsl hostname -I
@echo off
set /p WSLIP=What is the current WSL IP address?
netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=2222 connectaddress=%WSLIP% connectport=2222

Then one more step in the WSL terminal:

sudo service ssh start
  • 1
    If you take a part of my powershell script: $wsl_ip = (wsl hostname -I).trim() netsh interface portproxy add v4tov4 listenport=22 connectport=22 connectaddress=$wsl_ip It will do the same thing as your batch file, but it will automate the step of reading/writing the IP address. You can still run it manually as an administrator. Just call it portproxy.ps1. – Nick Dec 08 '21 at 15:13
  • @Nick, I worked your quick trim into the script I use, it also deals with the firewall if you have that need: https://gist.github.com/axonxorz/612865c294df5b87ced06fc2717c1ffc – axon Dec 13 '21 at 17:50
1

netsh interface portproxy add ...

every time I boot the machine WSL2 has a different IP

A great question at the time, and some great workarounds that are still valid.

However, note that the latest WSL2 releases (as of 1.1.0 in January 2023), will always attempt to reuse the same IP address.

This makes most of the shenanigans we used to have to jump through (e.g., the other answers) unnecessary now. A single netsh interface portproxy add ... should work across multiple reboots.

If you have not updated WSL2 in a while, make sure you install the latest from the Microsoft Store and you should gain access to this feature.

NotTheDr01ds
  • 21,923
0

Here's a very compact solution for WSL2 that will auto-start the SSH server. It eliminates having to deal with Powershell signing/execution policies and having to run it on a schedule.

  1. Run wsl sudo nano /etc/wsl.conf and add these lines:

    [boot]
    command="service ssh start"
    

    This will auto-start SSH server on every WSL startup.

  2. (Optional) If you'd like to use a custom port (like 2022) for SSH (for example, if you use multiple WSL distros), run:

    wsl sudo sed -i 's|.*Port.*|Port 2022|' /etc/ssh/sshd_config
    

This works because WSL2 maps ports from its distros to the Windows' localhost. Now you can just connect to your host using localhost:22 (or a custom port).

dinvlad
  • 131
0

I've insignificantly improved the script in Nick's answer because i needed to wait for docker desktop started, so here it is:

while ($true) {
    $wsl_ip = (wsl hostname -I).trim()
    $regex = [regex]"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"
    if ($regex.IsMatch($wsl_ip)) {
        Write-Host "IP Address: $wsl_ip"
        break
    }
    else {
        Write-Host "Invalid IP Address"
        Start-Sleep -Seconds 10
    }
}
netsh interface portproxy add v4tov4 listenport=2222 connectport=2222 connectaddress=$wsl_ip
0

Thanks to all who have responded here, it helped me cobble together my own take on this.

For context: Probably due to domain group policy, use of static IP addresses with WSL permission inbound/outbound rules on Windows Firewall did not work for me reliably. On some boots the static Linux IP address and SSH listener on it work, on other boots - not even pinging (from Windows host). Using powershell in windows explicitly to poke the linux VM is clumsy (gotta remember to do it on all reboots, or use task scheduler or some such...)

Ergo - let Linux guest do all its magic!

  • Probably need to get PowerShell installed into Windows, as for other solutions (in particular allows to elevate the call for port forwarding on host OS side) :)
  • Create /root/setup-network.sh:
#!/bin/sh

https://superuser.com/a/1723531/433945

Add to /etc/wsl.conf boot/command=... for autostart?

PATH="/mnt/c/WINDOWS/System32/WindowsPowerShell/v1.0:$PATH" export PATH

Fenced with a separate touch-file, an IP address might be not

available when this first runs - allow reentry until success!

Assumes first IP is the one generated by WSL anew for each VM boot.

AUTOIP="hostname -I | awk '{print $1}'" if [ -n "$AUTOIP" ] && [ ! -f /dev/shm/network-initialized-fwd ] ; then powershell.exe -Command "Start-Process netsh.exe -ArgumentList "interface portproxy add v4tov4 listenport=22099 connectport=22 connectaddress=$AUTOIP" -Verb RunAs" # Consider similar forwards for X11, VNC, etc. touch /dev/shm/network-initialized-fwd fi

(Optionally) shield the rest of such init (assuming SSHD listens

on IP_ANY, so starting it once suffices)

[ -f /dev/shm/network-initialized ] && exit

ps -ef | grep -v grep | grep sshd || sudo service ssh start

I've also had problems resolving Internet DNS, so this settled in

(also works if /etc/resolv.conf is a dangling symlink to non-existent

dir below, by default). Specifically, my problem was having the

resolver re-generated from Windows host settings, and not having

VPN etc. access to the domain DNS server to actually resolve stuff.

Per my notes, https://github.com/microsoft/WSL/issues/6977 might

also help: /etc/wsl.conf => [network] => generateResolvConf=false

grep 8.8.8.8 /etc/resolv.conf || { sudo mkdir -p /run/resolvconf/ &&
echo 'nameserver 8.8.8.8' | sudo tee -a /etc/resolv.conf }

touch /dev/shm/network-initialized

  • And add this into Linux guest's /etc/wsl.conf as
[boot]
command="/root/setup-network.sh"
  • Alternately, call it in your user's ~/.profile (maybe place in its homedir to be accessible) - it does work, but only after you first open a terminal to WSL. Some may deem it more secure and better facilitating host OS boots vs. UAC pop-up noted below.

Upon first start of the guest/session after host boot (or wsl --shutdown and subsequent start) Windows may ask for UAC permissions for that netsh command called from the guest system. But now it is all constrained in the guest and tied to using it.

-1
  1. Start wsl

  2. En terminal (wsl)

    sudo ifconfig eth0 172.27.100.100 netmask 255.255.255.0 broadcast 172.27.255.255

  3. En Powershell

    netsh interface ip set address name="vEthernet (WSL)" static 172.27.100.99 255.255.255.0 172.27.255.255

Access to wls to windows 172.27.100.99 Access to windows to wls 172.27.100.100