microsoft / WSL

Issues found on WSL
https://docs.microsoft.com/windows/wsl
MIT License
17.57k stars 823 forks source link

Select static subnet and pinned IP address for WSL #7820

Open Daniel-Nashed opened 2 years ago

Daniel-Nashed commented 2 years ago

I checked the forum and found multiple requests, which are pending for a while.
WSL is great! But admins and developers need to be able to reliably access services on the WSL instances!
The localhost mapping does not help for all application types -- even just accessing from host to WSL.

It drills down to the following requests

You can add another IP today inside the VM and assigning it via /etc/wsl.conf boot command in Win 11 should work. But if the IP range changes, this doesn't help! (e.g. ip a add 172.21.237.111/20 dev eth0)

The hyper-v local only adapter has no configurable IP range as well. And this might be part of the problem ..

If you cannot fix it soon, maybe we can get an valid work around by changing the WSL hyper-v network adapter?
It is not clear how the adapter gets assigned to WSL.

Thanks

Daniel

Biswa96 commented 2 years ago

There are workarounds like this https://github.com/microsoft/WSL/discussions/7395. Some developers created powershell scripts with it.

Daniel-Nashed commented 2 years ago

Thanks for the inspiration. But it is still tricky. Your JSON didn't work for me. I always got back that the network already exists. Even after start when it wasn't there and also when I first deleted it.

Here is what worked for me. Your link and the additional links I followed gave me inspiration. I needed to add the other parameters as well to the JSON.
My current strategy is to define the sub net and add an alias IP that either is not there yet or if already present, we can ignore the error.

See my fist version without configurable parameters.
What do you think?

Why can't WSL allow to customize the creation of the network and allow a static assignment?

Daniel


/*
    Author: Daniel Nashed (Nash!Com)

    (Re)creates the WSL network with a specific subnet 192.168.22.0/24 and adds an alias 192.168.222.222 for the assigned range.
    In case the IP is already set, the error can be ignored.
    This works on Windows 10+ but requires sudo permissions for the "ip addr " command.
    In Windows 11 /etc/wsl.conf supports a command in the [boot] section executed as root to set the IP.

    References and thanks for the inspiration:
        https://github.com/microsoft/WSL/discussions/7395
        https://github.com/skorhone/wsl2-custom-network

    For testing: Powershell Get current settings :
        Get-HnsNetwork | Where-Object { $_.Name -Eq "WSL" }

    Useful commands:

    Get WSL container IP:
        wsl hostname -I

    Set addtional IP
        wsl sudo /sbin/ip a add 192.168.222.222/24 dev eth0
*/

#include <stdio.h>
#include <computenetwork.h>
#include <Objbase.h>
#pragma comment(lib, "computenetwork")
#pragma comment(lib, "Ole32.lib")

int main(void)
{
    int         ret        = -1;
    HRESULT     hRes       = 0;
    HCN_NETWORK hcnNetwork = NULL;
    PWSTR       errRecord  = NULL;

    const GUID netId = {
        0xB95D0C5E,
        0x57D4,
        0x412B,
        { 0xB5, 0x71, 0x18, 0xA8, 0x1A, 0x16, 0xE0, 0x05 } };

    // Flags = EnableDnsProxy + IsolateVSwitch
    // Type = Static

    PCWSTR netSettings = LR"(
    {
        "Name" : "WSL",
        "Flags": 9,
        "Type": "ICS",
        "IPv6": false,
        "IsolateSwitch": true,
        "MaxConcurrentEndpoints": 1,
        "Subnets" : [
            {
                "ID" : "FC437E99-2063-4433-A1FA-F4D17BD55C92",
                "ObjectType": 5,
                "AddressPrefix" : "192.168.222.0/24",
                "GatewayAddress" : "192.168.222.1",
                "IpSubnets" : [
                    {
                        "ID" : "4D120505-4222-4CB2-8C53-DC0F70049696",
                        "Flags": 3,
                        "IpAddressPrefix": "192.168.222.0/24",
                        "ObjectType": 6
                    }
                ]
            }
        ],
        "MacPools":  [
            {
                "EndMacAddress":  "00-15-5D-52-CF-FF",
                "StartMacAddress":  "00-15-5D-52-C0-00"
            }
        ]
    })";

    hRes = HcnOpenNetwork (netId, &hcnNetwork, &errRecord);

    if (S_OK == hRes)
    {
        printf ("WSL Network already exists\n");
        CoTaskMemFree (errRecord);
        errRecord = NULL;

        if (hcnNetwork)
        {
            hRes = HcnCloseNetwork (hcnNetwork);
            hcnNetwork = NULL;
        }

        hRes = HcnDeleteNetwork (netId, &errRecord);

        if (S_OK == hRes)
        {
            printf ("WSL Network deleted\n");
        }
        else
        {
            printf ("WSL Network cannot be deleted [%ls]!\n", errRecord);
        }

        CoTaskMemFree (errRecord);
        errRecord = NULL;
    }
    else
    {
        printf ("WSL Network does not exist!\n");
    }

    hRes = HcnCreateNetwork (netId, netSettings, &hcnNetwork, &errRecord);

    if (S_OK == hRes)
    {
        printf ("WSL Network created\n");
        ret = 0;
    }
    else
    {
        printf ("WSL Network not created [%ls]!\n", errRecord);
    }

    CoTaskMemFree (errRecord);
    errRecord = NULL;

    /* Set the address we want in the sub-net created as an alias */
    system ("wsl sudo /sbin/ip a add 192.168.222.111/24 dev eth0");

Done:

    if (hcnNetwork)
    {
        hRes = HcnCloseNetwork (hcnNetwork);
        hcnNetwork = NULL;
    }

    return ret;
}
Biswa96 commented 2 years ago

Your JSON didn't work for me.

In that discussion, I just provided the code as an example or template. If you need further customizations in that JSON I can try to help.

Why can't WSL allow to customize the creation of the network and allow a static assignment?

Maybe for security? 🤷

Daniel-Nashed commented 2 years ago

Thanks! the JSON from the other post (referenced in my code example) helped and works.
I don't see a security issue here. We are on a local machine. The port is not exposed outside your local machine -- unless you add a proxy component. That's a different discussion.

Any other virtualization supports to specify fixed IP addresses! And it can be a configuration option.
I am OK with the workaround for now. But not everyone can compile code on their own or want to download additional EXE files from the web.. IMHO this belongs into the product. Not only to specify the network range, but also a statically assigned IP.

I will make the code available once I made it a bit customizable. Probably it would need to allow at least specifying the range and the IP set.

If you have feedback or ideas, I can write it up and publish it. This is really needed when we want to use applications that cannot use the localhost option. It will be probably in one of my existing repositories here I need it -- with Apache 2.0 license.

Thanks again! Your code and the other references I found have been really helpful!
It was still some research and testing to get it working. And the routines needed are only available 64bit.

-- Daniel

Biswa96 commented 2 years ago

Two things I noticed. For a specific pinned IP address, a new network endpoint has to be created using HcnCreateEndpoint and attached with WSL2 VM using HcsModifyComputeSystem. To run a command in a WSL environment, WslLaunch API can be used instead of system function. It handles the I/O of wsl.exe process internally.

Daniel-Nashed commented 2 years ago

Thanks! Hmmm .. The assigned IP works in my Windows 10 environment without creating a new end-point on my own.
What doesn't work for you if you don't create a new end-point?

Do you have a reference to the WslLaunch API? I want to have a look but I think I want to keep it as simple as possible.
There might be other commands needed in future. So having it generic makes sense to me. But I would be interested to look at the API.

The routine now also allows to specify an IP. It assumes it's a class C and calculates network and gateway.
And it checks if WSL is already started printing an info that WSL needs to be restarted.

Biswa96 commented 2 years ago

What doesn't work for you if you don't create a new end-point?

I mean like this. Only AddressPrefix can be configured with HcnCreateNetwork. So, the WSL2 VM has a random IP address in 192.168.222.0/24 range. But you asked for "pinned IP address" which can be configured with HcnCreateEndpoint. It has IPAddress field in JSON endpoint object.

Do you have a reference to the WslLaunch API?

Daniel-Nashed commented 2 years ago

Ohhh now I got it! I have the code in place but I am still looking for the JSON string.

The only reference I found so far is this Example which does not contain an IP.

Do you have a JSON example for me? This will save a lot of time spelunking around ..

Update: meanwhile I found the reference.
But an example would still really helpful!

Thanks!

Daniel-Nashed commented 2 years ago

Found it.. But this will require some more work to get it clean after lunch... I think I should generate the GUIDs beside the one for the network with the specific ID.

Now that I got it working. And I found the PowerShell commands to check it earlier, did you ever try to use New-HnsNetwork could this work as well? Can the ID of the network be specified? Would this be worth trying?

   {
        "SchemaVersion": {
            "Major": 2,
            "Minor": 0
        },
        "Flags" : 0,
        "HostComputeNetwork" : "B95D0C5E-57D4-412B-B571-18A81A16E005",
        "IpConfigurations" : [
            {
                "IpAddress": "192.168.123.124",
                "PrefixLength": 24,
                "IpSubnetId" : "4D120505-4222-4CB2-8C53-DC0F70049696"
            }
        ]

    }
Biswa96 commented 2 years ago

Yes, that one. My test code is in primary state. I need some json parsing library written in C or C++. Can you suggest one which can fit well in this situation?

Daniel-Nashed commented 2 years ago

I am using RapidJSON..

My prints use the %ls format which works in a printf as well.

Now that it works, did you ever try to use PowerShell to set it? I am not a big PowerShell fan. But this would be easier for others to consume. And PowerShell has simple to use JSON parsing.

Look at this example. In this case it is reading from a file. But it should also work form a buffer:

$DominoBackupCfg = Get-Content -Raw -Path $DominoBackupConfigFile | ConvertFrom-Json

foreach ($SearchCfg in $DominoBackupCfg)
{
  if ( $RestoreTarget -eq $SearchCfg.IpAddress) 
  {
    $RestoreVmHost  = $SearchCfg.VmHost
    ...
  }
}
Daniel-Nashed commented 2 years ago

Do you have an idea how the endpoint is now assigned to the WSL container? Right now it still gets a random IP from the subnet. Sounds like the mapping would be by MAC address.
I could specify a MAC in the endpoint, but how do I know which MAC the WSL container has?

If you want to play around, I can share my current code.

Daniel-Nashed commented 2 years ago

@Biswa96 Looks like creating a HNSEndpoint does not work as you expected.
I can create endpoints and assign an IP and a MACs address. But WSL always creates a new network and assigns a VirtualMachine ID. It uses IPs and MACs from the network. But if you create one, it always does see it as an existing other entry and creates a new one.

So it looks like we can't create our own entries ant let WSL use it.
That's probably only something the WSL team can sort out.

I have code working to create the resources and also to enumerate them. Plus also converting GUIDs.
But this doesn't help and I have to stay with my trick to add an alias IP to the WSL stack.

If you have more ideas I would give it another try or share the code.

-- Daniel

Biswa96 commented 2 years ago

In that previous discussion, I have provided the sample code to add specific IP address in WSL2. Direct link https://github.com/microsoft/WSL/discussions/7395#discussioncomment-1803186

Daniel-Nashed commented 2 years ago

Thanks! I haven't looked into the other management components.
I see you are removing the network and adding a new network based on the settings needed.

This needs a bit more code around it like searching the for the right VM getting the ID.
Enumerating ComputeSystems and getting the properties will probably need a bit of JSON magic around it..

Not sure if we really need to add code to compute new random GUIDs. But I have this in place in my code already.
I never worked with those type of APIs, but with your pointers how to start, I can make it work based on what I already have.

To automate the process I would need to find the VM first HcsEnumerateComputeSystems, open it HcsOpenComputeSystem and then get the VM ID via HcsGetComputeSystemProperties.

This sounds like a bit of work and as you say it requires that the VM is already created by WSL.
But this would allow other fun things to do with the VM in future :-)

Daniel-Nashed commented 2 years ago

quick update:
Now we finally need some JSON magic. But I am tempted to just do string operations for now. Here is what you get back from the enumeration for now before I add JSON parsing.

Not that we end up writing up our own WSL management tool .. Hmmm... I just wanted a static IP for my WSL environment ;-)

-- Daniel

[
  {
    "Id": "266A9C21-0EF0-4983-BD9C-CAB2F07D09CA",
    "SystemType": "VirtualMachine",
    "Owner": "WSL",
    "RuntimeId": "266a9c21-0ef0-4983-bd9c-cab2f07d09ca",
    "State": "Running"
  },
  {
    "Id": "98456253-fb5a-41ad-ada8-787f9e01466b",
    "SystemType": "VirtualMachine",
    "RuntimeOsType": "Windows",
    "Owner": "CmService",
    "RuntimeId": "98456253-fb5a-41ad-ada8-787f9e01466b",
    "State": "SavedAsTemplate"
  }
]
hcsdiag.exe list
266A9C21-0EF0-4983-BD9C-CAB2F07D09CA
    VM,                         Running, 266A9C21-0EF0-4983-BD9C-CAB2F07D09CA, WSL

98456253-fb5a-41ad-ada8-787f9e01466b
    VM,                         SavedAsTemplate, 98456253-FB5A-41AD-ADA8-787F9E01466B, CmService

Filtering it by owner looks like the only way to distinct the VMs..

Daniel-Nashed commented 2 years ago

@Biswa96 it's not working for me. Have working code that creates all relevant parts. Just filling in the Virtual machine ID manually for now.

The MAC address shows up in the VM but the IP address is not picked up.

I am using Get-HNSEndpoint in PowerShell to dump the results of the WSL created endpoint and our endpoint. The endpoint was missing the VirtualMachine -- which I added. But still the IP address is not assigned inside the VM. I also added PrefixLength which was also missing. There are some other fields missing in the hand crafted endpoint.

How much is this verified to work on your end? I am happy to beef up prototype code. But I can't spend hours on trying to figure out the right parameters.

How sure is it that it should work and always work? My approach to add the alias IP is much less likely to break. This approach is mocking around with the WSL created endpoint. Maybe it is time to get from feedback from the WSL team?

My current self created endpoint

Get-HNSEndpoint

ID                 : b95d0c5e-57d4-412b-b571-18a81a16e222
Name               : Ethernet
Version            : 47244640267
AdditionalParams   : @{SwitchId=142A47F4-90F2-4C08-AC65-9280EE7C8F2D; SwitchPortId=B9FE0A99-0500-46D2-9AE8-5BF6D469631F}
Resources          : @{AdditionalParams=; AllocationOrder=2; Allocators=System.Object[]; CompartmentOperationTime=0; Flags=0; Health=; ID=3DC35E3E-CAB2-4FD2-9E21-E4BE98DA5230;
                     PortOperationTime=0; State=1; SwitchOperationTime=0; VfpOperationTime=0; parentId=0FD182B8-436F-476B-AFFB-7636C43E6CCC}
State              : 2
VirtualNetwork     : b95d0c5e-57d4-412b-b571-18a81a16e005
VirtualNetworkName : WSL
MacAddress         : 00-15-5D-52-C0-22
IPAddress          : 192.168.222.111
PrefixLength       : 24
GatewayAddress     : 192.168.222.1
IPSubnetId         : 4d120505-4222-4cb2-8c53-dc0f70049696
DNSServerList      : 192.168.222.1
SharedContainers   : {}
VirtualMachine     : 188276C0-3D4C-45B8-94D5-2DABD11F4654```

Endpoint created by WSL

It has AdditionalParams set to a different SwitchId and SwitchPortId

Get-HNSEndpoint

ID                 : 37baae10-45dd-473c-9b82-d54c795f706a
Name               : Ethernet
Version            : 47244640267
AdditionalParams   : @{SwitchId=573F9E5D-4BC7-49E9-994E-610AB61471BC; SwitchPortId=D913CBE5-3233-455D-9330-3CD99A981CDB}
Resources          : @{AdditionalParams=; AllocationOrder=2; Allocators=System.Object[]; CompartmentOperationTime=0; Flags=0; Health=; ID=A4AD4466-82CB-4920-8340-C6AB7F4378A0;
                     PortOperationTime=0; State=1; SwitchOperationTime=0; VfpOperationTime=0; parentId=2FC115CC-AF91-4DE9-AB83-A07813F1A085}
State              : 2
VirtualNetwork     : b95d0c5e-57d4-412b-b571-18a81a16e005
VirtualNetworkName : WSL
MacAddress         : 00-15-5D-52-C0-2C
IPAddress          : 192.168.222.251
PrefixLength       : 24
GatewayAddress     : 192.168.222.1
IPSubnetId         : 4d120505-4222-4cb2-8c53-dc0f70049696
DNSServerList      : 192.168.222.1
SharedContainers   : {}
VirtualMachine     : 188276C0-3D4C-45B8-94D5-2DABD11F4654

ip addr returns thew following. The MAC is set but the IP address is not showing up.

7: eth0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 00:15:5d:52:c0:22 brd ff:ff:ff:ff:ff:ff
Daniel-Nashed commented 2 years ago

Here is the current template to create the endpoint.

VirtualMachine ID is still hard coded until it works (needs to add JSON parsing ..).

   PCWSTR endpointSettingsJSON = LR"(
    {
        "Name": "Ethernet",
        "IpAddress": "%s",
        "PrefixLength": 24,
        "MacAddress": "00-15-5D-52-C0-22",
        "VirtualNetwork" : "B95D0C5E-57D4-412B-B571-18A81A16E005",
        "VirtualMachine" : "188276C0-3D4C-45B8-94D5-2DABD11F4654"
    })";
Biswa96 commented 2 years ago

How much is this verified to work on your end?

I am using this workaround in Windows 10 for WSLg stuff.

How sure is it that it should work and always work?

I am not sure if this works in Windows 11 because I have not used it yet.

Maybe it is time to get from feedback from the WSL team?

Absolutely.

Daniel-Nashed commented 2 years ago

I can't get it working on Windows 10. It looks all good but the machine only picks up the MAC.
This might be a detail. I did not finish the code to parse the JSON, because I wanted it to work first. I am happy to share the code to look into it and to leverage what I built around I already. This isn't really the final solution. IMHO the WSL project should address it.

Clearly what we are doing here is just a temporary solution to not re-create connections all the time after boot.

Still if you want to look into my code, I can bring it up somewhere. I added a couple of useful routines for testing.
But beside my first approach at the beginning of the code, all the other parts are more for testing your approach to create the EndPoint directly with a fixed MAC and IP.

The code you posted it more PoC code. I am happy to spend the time and contribute a final solution once we make sure it really works. I can't spend days on researching it right now... Even it was a lot of fun last weekend looking into it.

Again I would like the WSL team pick it up and address it in the product! It is really needed for applications requiring more than just the simple "localhost" mapping!.

-- Daniel

Daniel-Nashed commented 2 years ago

@craigloewen-msft why is this issue closed? I don't see any of the related issues you referenced in #7925 are a work-around nor a feature request.

For now until there is a solution I wrote a small helper tool with the help from @Biswa96 . Thanks again. I ended up not trying to set an endpoint, which is something the WSL team should implement in the product.
The work-around creates a new network and sets an alias IP for now.

Thanks

Daniel

craigloewen-msft commented 2 years ago

This issue isn't closed, it's open and our tracking place for this request!

Daniel-Nashed commented 2 years ago

Oh the other one is closed #7925 .. The status for this issue is on top. Sorry! Thanks for your feedback. Do you have any idea when this might be addressed. Or do you have any other work-around than the tool I wrote?

Deploying a tool is challenge for some corporate users I am working with.

Thanks Daniel

wmsphd commented 2 years ago

the link in Daniel's post on 1/21/2022 [i think] is dead.

i'm just a lowly physicist :-) so the code is impenetrable to me. pardon the absolutely ignorant question, but how does one use all of this? i'm currently setting up a virtual research network using wsl2 and docker desktop and a static IP is essential. not asking for anyone to spend any time explaining, if someone could just point me to a reference...? i've googled and bing-ed until my fingers are numb.

many thanks -mike