micahmo / WgServerforWindows

Wg Server for Windows (WS4W) is a desktop application that allows running and managing a WireGuard server endpoint on Windows
MIT License
822 stars 79 forks source link

Multiple WAN IP addresses, bind NAT to single one? #73

Open radekhulan opened 1 year ago

radekhulan commented 1 year ago

I've got 2 public IP addresses on my WAN interface. Wireguard listens on IP1, but when I enable NAT, and connect a client, outgoing IP addresses for clients are basically random, sometimes they get IP1, sometimes IP2.

Is it possible to enforce this, somehow, so that clients get same outgoing IP address as is the listening one?

micahmo commented 1 year ago

Hey @radekhulan, wow that's a great question. I'm not even sure how to test this without having two WAN IPs myself. Out of curiosity, does the Windows machine running WS4W exhibit the same behavior when accessing the internet? If so, then this is more of a Windows or even a router problem.

radekhulan commented 1 year ago

@micahmo you can force IIS to listen (and query, outbound IP) on specific IP address via built-in "netsh http add iplisten ipaddress=xxx.xxx.xxx.xxx" command, you can force hMailServer (SMTP server) to use a specific IP address for SMTP outgoing email in setting, you can force OpenVPN server to use specific IP, etc., so it is definitely app-specific, and doable.

But I have no idea how to force Wireguard :)

radekhulan commented 1 year ago

Strange thing is, IP1 is my primary WAN address, and Wireguard is listening on this primary address. IP2 is secondary address. It should not be used by default, anyway, 90% of time client gets IP2. No idea why this happens.

micahmo commented 1 year ago

Thanks for the additional comments!

What do you mean by Wireguard is listening on this primary address? Is your machine directly on the WAN and not behind any kind of NAT? I ask because, on my system, WireGuard is bound to 0.0.0.0:51820 and has no concept of the WAN IP (aside from what goes in the client config). It would be up to my router to decide whether to forward requests from a given WAN, and I don't know how outgoing requests would be handled.

radekhulan commented 1 year ago

Yes, that machine has directly available 2 public (static) IPs on Ethernet WAN interface.

micahmo commented 1 year ago

Gotcha, thanks! It definitely seems reasonable to assume that you should be able to control which interface WireGuard uses since both of them are local to the machine.

I want to clear up one thing before looking at solutions. You've mentioned a couple times that WireGuard is listening on the right interface, but I'm not sure about that. Can you run this command?

netstat -anob | select-string wireguard.exe -context 1,0

On my system, WireGuard is actually bound to all interfaces.

    UDP    0.0.0.0:51820
>  [wireguard.exe]
    UDP    [::]:51820
>  [wireguard.exe]

If that's true on your system also, then I think it makes a bit more sense why the outgoing interface is unpredictable.

My original thought was that fixing the binding would help. But I'm not finding ways to do that (even on Linux), so I think we're back to your original point that it may be a function of the NAT routing rules.

radekhulan commented 1 year ago
    UDP    0.0.0.0:8443           *:*                                    4344
>  [wireguard.exe]
    UDP    [::]:8443              *:*                                    4344
>  [wireguard.exe]

Yes, it is the same in my case.

micahmo commented 1 year ago

Alrighty, so I'm not sure this can be done at the WireGuard level. It might be baked in to bind to all interfaces.

I did find something else interesting though. There is a concept of a NetNatExternalAddress, multiple of which are created when doing New-NetNat.

Can you run the following command?

Get-NetNatExternalAddress -NatName wg_server_nat

See if there are any entries where the IPAddress field matches the interface you don't want to use. If so, try removing them with this command (replacing ExternalAddressID).

Remove-NetNatExternalAddress -NatName wg_server_nat -ExternalAddressID <id> -Confirm:$false
radekhulan commented 1 year ago

Wow, just wow, this did solve the problem :) I removed wg_server_nat from IP address I do not want to use as outgoing, and now it will always assign the proper one for me.

Big THANK YOU!

Not sure oif this will stick after reboot, might create a task for it.

micahmo commented 1 year ago

Awesome, I'm so glad to hear that!! You're right, it probably won't survive a restart. As it is, WS4W creates a Windows task to recreate the NAT network upon boot. If you make a separate task, you'll have to make sure it runs after that.

I believe it should be possible to integrate this functionality into the application, and I might ask for help testing some things. 😊

In the meantime, here is a handy script that deletes all of the ExternalAddresses which don't match your desired interface IP.

Get-NetNatExternalAddress -NatName wg_server_nat | Where-Object IPAddress -ne <your IP> | Remove-NetNatExternalAddress -Confirm:$false 2> $null

And to make sure they're all gone, this should output 0.

(Get-NetNatExternalAddress -NatName wg_server_nat | Where-Object IPAddress -ne <your IP>).Count
micahmo commented 1 year ago

I've created a new version of the app which adds a "Bind to Specific External IP" option in the NAT routing dropdown. Selecting that should show you all the external IPs, from which you can pick the desired one. (For me, it couldn't always remove all the unwanted ones, but neither could Remove-NetNatExternalAddress, so at least the behavior should be the same.)

If you have some time, would you be willing to help test this? I've attached a .zip of the new version. Unzip and run WgServerforWindows.exe (no need to uninstall your current version).

WgServerforWindows.zip

For testing, I would first disable and then re-enable NAT routing (to put back all the bindings). Then use the new option and pick the IP you want.

If that works for you, then can you also test with a reboot and see if it's able to fix things up?

If it doesn't work after a reboot (I've found that some of the bindings can appear a little while after booting), I added a new setting for a boot task delay, so you can try increasing that to a minute or so.

Let me know what you think.

radekhulan commented 1 year ago

@micahmo sorry for late answer, was not at home.

I did try it. Disable/Enable NAT, bind to specific IP address, I got popup with 3 other IP adresses listed saying there are some IPs left and I should re/enable NAT again. After doing this, same popup.

Anyway, good thing is, it works. :)

Sort of... Bad thing is, and I am not sure why, it did break my other 2 VPNs I am running on thiis server, which are IPSec and OpenVPN. I cannot connect to them anymore. I tried to solve this, but found nothing, ended up restoring the server config from backup :)

micahmo commented 1 year ago

Hey @radekhulan, thank you so much for testing this, and I'm so sorry for having broken your other VPNs! I'm glad you were able to fix things.

My guess is that the Remove-NetNatExternalAddress command I added is more aggressive than the one you ran originally. You probably only removed the external IP that you didn't want to use, whereas I was trying to remove all but the one you want.

If you would find this useful (and can continue to help testing), I can try to rework the feature to be more like the former than the latter. On the other hand, I totally understand if you want to leave your system alone and stable, so if you're happy making this change manually (and are able to reapply it after a restart), I'll leave it alone for now.

radekhulan commented 1 year ago

@micahmo I am open to any testing. My server is running on Hyper-V, so I can make a quick snapshot and restore it later by a single click, it is super easy :) So if you want to continue testing, I am all in :)

micahmo commented 1 year ago

Ok awesome, thank you! Let's take the approach of removing a specific binding, rather than removing all but one (especially since that's how the PowerShell command works anyway).

Here's another version that lets you remove one, and you can choose just the WAN IP that you don't want to use with WireGuard. Let me know how it goes!

WgServerforWindows.zip

radekhulan commented 1 year ago

@micahmo Wireguard works brilliantly with this binds to correct WAN IP address. But it, again, breaks my ability to connect to OpenVPN or IPSec VPNs on the same server :(

OpenVPN client log ends here:

Tue Feb 21 15:55:05 2023 Note: cipher 'CAMELLIA-256-CBC' in --data-ciphers is not supported by ovpn-dco, disabling data channel offload. Tue Feb 21 15:55:05 2023 OpenVPN 2.6.0 [git:v2.6.0/b999466418dddb89] Windows-MSVC [SSL (OpenSSL)] [LZO] [LZ4] [PKCS11] [AEAD] [DCO] built on Feb 15 2023 Tue Feb 21 15:55:05 2023 Windows version 10.0 (Windows 10 or greater), amd64 executable Tue Feb 21 15:55:05 2023 library versions: OpenSSL 3.0.8 7 Feb 2023, LZO 2.10 Tue Feb 21 15:55:05 2023 MANAGEMENT: TCP Socket listening on [AF_INET]127.0.0.1:25341 Tue Feb 21 15:55:05 2023 Need hold release from management interface, waiting... Tue Feb 21 15:55:05 2023 MANAGEMENT: Client connected from [AF_INET]127.0.0.1:56509 Tue Feb 21 15:55:05 2023 MANAGEMENT: CMD 'state on' Tue Feb 21 15:55:05 2023 MANAGEMENT: CMD 'log on all' Tue Feb 21 15:55:05 2023 MANAGEMENT: CMD 'echo on all' Tue Feb 21 15:55:05 2023 MANAGEMENT: CMD 'bytecount 5' Tue Feb 21 15:55:05 2023 MANAGEMENT: CMD 'state' Tue Feb 21 15:55:05 2023 MANAGEMENT: CMD 'hold off' Tue Feb 21 15:55:05 2023 MANAGEMENT: CMD 'hold release' Tue Feb 21 15:55:05 2023 Outgoing Control Channel Authentication: Using 160 bit message hash 'SHA1' for HMAC authentication Tue Feb 21 15:55:05 2023 Incoming Control Channel Authentication: Using 160 bit message hash 'SHA1' for HMAC authentication Tue Feb 21 15:55:05 2023 TCP/UDP: Preserving recently used remote address: [AF_INET]XXX.XXX.XXX.XXX:8443 Tue Feb 21 15:55:05 2023 Socket Buffers: R=[65536->65536] S=[65536->65536] Tue Feb 21 15:55:05 2023 Attempting to establish TCP connection with [AF_INET]XXX.XXX.XXX.XXX:8443 **Tue Feb 21 15:55:05 2023 MANAGEMENT: >STATE:1676969705,TCP_CONNECT,,,,,,**

radekhulan commented 1 year ago

I am using Routing and remote access pretty much the same way as described e.g. here: https://www.snel.com/support/how-to-set-up-an-l2tp-ipsec-vpn-on-windows-server-2019/

Standard stuff. Everything seems to be configured well, but those powershell commands do break something I cannot see.

micahmo commented 1 year ago

Hey thanks again so much for testing this. Just to check, did OpenVPN/IPSec die when you first ran the PowerShell commands and reported success here? I only ask because the application should be doing the exact same thing.

If the commands were breaking things too, then we might have hit the limit of what we can do through Windows' NAT API. Maybe removing the binding for one address affects other things bound to that address (even though it's supposed to be per NAT network). We also know that WireGuard is limited in its ability to bind (it naturally wants to listen on every interface), so we might have to look elsewhere (Windows Firewall rules or something).

radekhulan commented 1 year ago

@micahmo yes, I did not know it back then (did not notice / did not test it), but it dies as well.

My config is following: IP1 - main WAN public IP address IP2 - secondary WAN public IP address

IPSec and OpenVNP TCP and OpenVPN UDP listen on IP2, and via "Routing and remote access" they are given outbound IP1 via (Ethernet enabled) NAT. I believe Windows address pooling should give you primary IP address (IP1) in most cases and it does work reliably for OpenVPN and IPSec, eventhough these services listen on IP2.

Wireguard, however, 99% of time chooses IP2 as outbound, and it does not matter if it listens on IP1 or IP2. Using those PowerShell commands does force it to bing to IP1 (outgoing), but breaks everything else.

micahmo commented 1 year ago

Wireguard, however, 99% of time chooses IP2 as outbound, and it does not matter if it listens on IP1 or IP2

Just a point of clarification here, unless I'm misunderstanding things, WireGuard is listening on all interfaces which we proved here. I don't know whether that affects outbound traffic, but I just don't want to lose the fact that WireGuard doesn't bind itself to a particular adapter.

While I was reviewing that comment, I found something interesting. It shows that WireGuard is bound on port 8443. I also see a reference to that port in the OpenVPN logs. Is that a coincidence or could there be some conflict there?

Beyond that, I don't have any ideas at the present. However, I think I can reproduce this with the ethernet and Wi-Fi adapters on my system (even though they're just LAN), so I'll let you know if I come up with anything.

micahmo commented 1 year ago

I had another idea. WireGuard's routing on Linux is done through manipulation of routing tables. While not quite as powerful, we can do some of the same things on Windows. Let's start by seeing which of the public interfaces has the lower metric (meaning that it will be preferred) by running the following command.

Get-NetIPAddress -AddressFamily IPv4 | Select-Object InterfaceIndex, IPAddress, @{N="Name"; E={(Get-NetAdapter -ifIndex $_.InterfaceIndex).Name}}, @{N="Description"; E={(Get-NetAdapter -ifIndex $_.InterfaceIndex).InterfaceDescription}}, @{N="Metric"; E={(Get-NetIPInterface -ifIndex $_.InterfaceIndex).InterfaceMetric[0]}} | Format-Table

Here is a part of my output.

InterfaceIndex IPAddress       Name         Metric
-------------- ---------       ----         ------
             6 192.168.1.95    Ethernet     25
            13 192.168.1.192   Wi-Fi        35

Sure enough, the interface with the lower metric is the one through which all of my WireGuard traffic flows, even when I connect through the other.

If the same is true for you, try setting the metric of the desired interface very low. Using the InterfaceIndex of the desired interface, run the following command.

Set-NetIPInterface -ifIndex <Index> -InterfaceMetric 1

This immediately made all of my WireGuard traffic go over the desired interface. The major downside to this approach is that this interface will be preferred for all traffic. Maybe that works for you, not sure. If you want to reset it, you can use the following command.

Set-NetIPInterface -ifIndex <Index> -AutomaticMetric Enabled

What I wanted to do was add a new entry to the routing table with New-NetRoute. Unfortunately, it seems like you can only route to a specific interface based on destination address, not source address. If you did have only a set of destination IPs that you use with WireGuard, then that could be a possibility. Let me know what you think.

radekhulan commented 1 year ago

While I was reviewing that comment, I found something interesting. It shows that WireGuard is bound on port 8443. I also see a reference to that port in the OpenVPN logs. Is that a coincidence or could there be some conflict there?

OpenVPN listens on IP2:8443, while Wireguard server endpoint (Pulblic IP:port) is IP1:8443. Anyway, not only OpenVPN connection is broken, also IPSec one, which is port 4500 I believe.

I did try to move Wireguard to :8080, but it had no effect.

radekhulan commented 1 year ago

@micahmo this give me:

InterfaceIndex IPAddress       Name      Description                       Metric
-------------- ---------       ----      -----------                       ------
            39 10.253.0.1      wg_server WireGuard Tunnel                       5
            13 10.8.0.1        TAP2      TAP-Windows Adapter V9 #2             25
             7 10.5.0.1        TAP1      TAP-Windows Adapter V9                25
            18 "IP2"           Ethernet  Microsoft Hyper-V Network Adapter     15
            18 "IP1"           Ethernet  Microsoft Hyper-V Network Adapter     15
             1 127.0.0.1                                                       75
            38 10.10.0.0                                                       75

As you can see I have same interface index and same metrics for both IP1 and IP2

radekhulan commented 1 year ago

I am thinking the easist approach might be to create a second Hyper-V Network Adapter (have to ask server hostig provider to do this), and bind IP2 to this second adapter, and set proper metrics to it. Or do you think it can be done on a single adapter?

micahmo commented 1 year ago

I have same interface index and same metrics for both IP1 and IP2

Whoa, that's interesting! I've only seen that for IPv4 and IPv6 addresses.

I think there's one more thing to try before resorting to asking the hosting provider for a second adapter. Even though both IPs are on the same interface, there may be separate routes. Try running this command.

Get-NetRoute -ifIndex 18

See if there are multiple entries and/or one that more clearly corresponds to the IP you want to use. If so, you can try changing the RouteMetric (either lower on the desired route or higher on the undesired one) with a command like this. You'll have to fill in all the properties so that it matches the output of the previous command.

Set-NetRoute -ifIndex 18 -DestinationPrefix <> -NextHop <> -RouteMetric <>

If that approach doesn't pan out, I think w

radekhulan commented 1 year ago

@micahmo thank you :) Anyway, I was able to change the route metrics from 256 to 255 for IP1:

PS C:\Users\Administrator> Get-NetRoute -ifIndex 18

ifIndex DestinationPrefix                              NextHop                                  RouteMetric PolicyStore
------- -----------------                              -------                                  ----------- -----------
18      255.255.255.255/32                             0.0.0.0                                          256 ActiveStore
18      224.0.0.0/4                                    0.0.0.0                                          256 ActiveStore
18      IP2/32                                         0.0.0.0                                          256 ActiveStore
18      IP1/32                                         0.0.0.0                                          255 ActiveStore
18      IP0/24                                0.0.0.0                                          256 ActiveStore

But.. Wireguard outbound IP address remained as IP2.

radekhulan commented 1 year ago

Got somethign :) This:

Set-NetIPAddress -IPAddress IP2 -SkipAsSource:$true

Solved my problem )

// EDIT: no, it did NOT. Although first 2 or 3 tries it seemed it did. Wireguard still sometimes goes out via IP2

radekhulan commented 1 year ago

@micahmo I am extremely grateful for all your help. But I do not want to waste more of your time, will dig on my own and write here, if I find something :)

micahmo commented 1 year ago

Sounds good, sorry we did not make more progress. This is an interesting scenario for sure!