From 4a224cdfc38e03440797ae388b9fee6b5a0bd56f Mon Sep 17 00:00:00 2001 From: Daniel Lenski Date: Fri, 2 Apr 2021 00:16:59 -0700 Subject: [PATCH] Try to delete-and-reclaim IP addresses from down interfaces MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit As mentioned previously, Windows will not allow us to set an IP address on the tunnel interface (using 'netsh' in vpnc-script-win.js) if that address is assigned to another interface, *even if* that interface is not currently up. In order to figure out if our interface's desired IP address(es) are assigned to any other interface(s), we have to iterate over the results of `GetAdaptersAddresses`. This retrieves a list of all IP addresses (both Legacy and IPv6) by interface, along with up/down status thereof. If we find another interface that is assigned our desired IP address, but is not up, we should attempt to unassign/delete/reclaim the address from this interface. In order to do that, we need to use a completely separate API (`GetUnicastIpAddressTable`) to get a list of all assigned IP addresses in a form that can be used to delete them (with `DeleteUnicastIpAddressEntry`). The simplest way to structure the code to do this is to use two nested loops: - The outer loop iterates over the adapters and addresses returned by `GetAdaptersAddresses` - The inner loop — which runs only in the case of a conflicting address — iterates over the addresses as returned by `GetUnicastIpAddressTables`. - Perhaps this could be optimized, but because this runs only once per VPN connection, and a typical client system will have < --- tun-win32.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/tun-win32.c b/tun-win32.c index 7525e5ab..851b2735 100644 --- a/tun-win32.c +++ b/tun-win32.c @@ -24,7 +24,9 @@ #include /* must precede iphlpapi.h, per https://docs.microsoft.com/en-us/windows/win32/api/iptypes/ns-iptypes-ip_adapter_addresses_lh */ #include +#include #include +#include #include #include @@ -244,7 +246,7 @@ static int check_address_conflicts(struct openconnect_info *vpninfo) if (!addresses) return -ENOMEM; - /* AF_UNSPEC means both IPv4 and IPv6 */ + /* AF_UNSPEC means both Legacy IP and IPv6 */ LastError = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, NULL, addresses, &bufSize); @@ -263,7 +265,8 @@ static int check_address_conflicts(struct openconnect_info *vpninfo) if (addresses->IfIndex == vpninfo->tun_idx) continue; - for (PIP_ADAPTER_UNICAST_ADDRESS ua = addresses->FirstUnicastAddress; ua; ua=ua->Next) { + for (PIP_ADAPTER_UNICAST_ADDRESS ua = addresses->FirstUnicastAddress; + ua; ua=ua->Next) { struct sockaddr *a = ua->Address.lpSockaddr; union { struct sockaddr_in s4; @@ -293,12 +296,49 @@ static int check_address_conflicts(struct openconnect_info *vpninfo) _("Adapter \"%S\" / %ld is DOWN and using our IPv%d address. We will reclaim the address from it.\n"), addresses->FriendlyName, addresses->IfIndex, a->sa_family == AF_INET ? 4 : 6); - /* FIXME: we SHOULD be able to delete this address, but - * DeleteUnicastIpAddressEntry requires a table we don't have right now (maybe make a "fake" row?) - * and DeleteIPAddress is IPv4 only, and requires a magic number we don't have + /* XX: In order to use DeleteUnicastIpAddressEntry(), we bizarrely have to iterate through a + * whole different table of IP addresses to find the right row. I previously tried + * faking/synthesizing the requisite MIB_UNICASTIPADDRESS_TABLE rows, and it did not + * work. */ - ret = -ENOENT; - goto out; + int found = 0; + PMIB_UNICASTIPADDRESS_TABLE pipTable = NULL; + LastError = GetUnicastIpAddressTable(AF_UNSPEC, &pipTable); + if (LastError != ERROR_SUCCESS) { + char *errstr = openconnect__win32_strerror(LastError); + vpn_progress(vpninfo, PRG_ERR, _("GetUnicastIpAddressTable() failed: %s\n"), errstr); + ret = -EIO; + goto out; + } + for (int i = 0; i < pipTable->NumEntries; i++) { + if (pipTable->Table[i].Address.si_family == AF_INET && a->sa_family == AF_INET) { + if (sa->s4.sin_addr.s_addr == pipTable->Table[i].Address.Ipv4.sin_addr.s_addr) + found = 1; + } else if (pipTable->Table[i].Address.si_family == AF_INET6 && a->sa_family == AF_INET6) { + if (!memcmp(&sa->s6.sin6_addr, &pipTable->Table[i].Address.Ipv6, sizeof(sa->s6.sin6_addr))) + found = 1; + } + if (found) { + LastError = DeleteUnicastIpAddressEntry(&pipTable->Table[i]); + break; + } + } + FreeMibTable(pipTable); + + if (LastError != NO_ERROR) { + char *errstr = openconnect__win32_strerror(LastError); + vpn_progress(vpninfo, PRG_ERR, _("DeleteUnicastIpAddressEntry() failed: %s\n"), errstr); + ret = -EIO; + goto out; + } + + if (!found) { + vpn_progress(vpninfo, PRG_ERR, + _("GetUnicastIpAddressTable() did not find matching address to reclaim\n")); + /* ret = -EIO; + goto out; */ + } + } } } -- 2.50.1