Newer
Older
XinYang_IOS / Pods / OpenVPNAdapter / Sources / OpenVPN3 / openvpn / tun / win / tunutil.hpp
@zhangfeng zhangfeng on 7 Dec 2023 38 KB 1.8.0
//    OpenVPN -- An application to securely tunnel IP networks
//               over a single port, with support for SSL/TLS-based
//               session authentication and key exchange,
//               packet encryption, packet authentication, and
//               packet compression.
//
//    Copyright (C) 2012-2020 OpenVPN Inc.
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Affero General Public License Version 3
//    as published by the Free Software Foundation.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU Affero General Public License for more details.
//
//    You should have received a copy of the GNU Affero General Public License
//    along with this program in the COPYING file.
//    If not, see <http://www.gnu.org/licenses/>.

// tun interface utilities for Windows

#ifndef OPENVPN_TUN_WIN_TUNUTIL_H
#define OPENVPN_TUN_WIN_TUNUTIL_H

#include <openvpn/common/socktypes.hpp> // prevent winsock multiple def errors

#include <windows.h>
#include <winsock2.h> // for IPv6
#include <winioctl.h>
#include <iphlpapi.h>
#include <ntddndis.h>
#include <wininet.h>
#include <ws2tcpip.h> // for IPv6
#include <tlhelp32.h> // for impersonating as LocalSystem


#include <setupapi.h>
#include <devguid.h>
#include <cfgmgr32.h>

#ifdef __MINGW32__
#include <ddk/ndisguid.h>
#else
#include <ndisguid.h>
#endif

#include <string>
#include <vector>
#include <sstream>
#include <cstdint> // for std::uint32_t
#include <memory>

#include <tap-windows.h>

#include <openvpn/common/to_string.hpp>
#include <openvpn/common/size.hpp>
#include <openvpn/common/exception.hpp>
#include <openvpn/common/socktypes.hpp>
#include <openvpn/common/string.hpp>
#include <openvpn/common/stringize.hpp>
#include <openvpn/common/action.hpp>
#include <openvpn/common/uniqueptr.hpp>
#include <openvpn/common/wstring.hpp>
#include <openvpn/buffer/buffer.hpp>
#include <openvpn/addr/ip.hpp>
#include <openvpn/tun/builder/capture.hpp>
#include <openvpn/win/reg.hpp>
#include <openvpn/win/scoped_handle.hpp>
#include <openvpn/win/unicode.hpp>
#include <openvpn/win/cmd.hpp>
#include <openvpn/win/winerr.hpp>

namespace openvpn {
  namespace TunWin {
    namespace Util {
      OPENVPN_EXCEPTION(tun_win_util);

      namespace {
	// from tap-windows.h
	const char ADAPTER[] = ADAPTER_KEY; // CONST GLOBAL
	const char NETWORK_CONNECTIONS[] = NETWORK_CONNECTIONS_KEY; // CONST GLOBAL

	// generally defined on cl command line
	const char COMPONENT_ID[] = OPENVPN_STRINGIZE(TAP_WIN_COMPONENT_ID); // CONST GLOBAL
	const char WINTUN_COMPONENT_ID[] = "wintun"; // CONST GLOBAL

	const char ROOT_COMPONENT_ID[] = "root\\" OPENVPN_STRINGIZE(TAP_WIN_COMPONENT_ID);
	const char ROOT_WINTUN_COMPONENT_ID[] = "root\\wintun"; 
      }

      using TapGuidLuid = std::pair<std::string, DWORD>;

      // Return a list of TAP device GUIDs installed on the system,
      // filtered by TAP_WIN_COMPONENT_ID.
      inline std::vector<TapGuidLuid> tap_guids(bool wintun)
      {
	LONG status;
	DWORD len;
	DWORD data_type;

	std::vector<TapGuidLuid> ret;

	Win::RegKey adapter_key;
	status = ::RegOpenKeyExA(HKEY_LOCAL_MACHINE,
				 ADAPTER,
				 0,
				 KEY_READ,
				 adapter_key.ref());
	if (status != ERROR_SUCCESS)
	  {
	    const Win::Error err(status);
	    OPENVPN_THROW(tun_win_util, "tap_guids: error opening adapter registry key: " << ADAPTER << " : " << err.message());
	  }

	for (int i = 0;; ++i)
	  {
	    char strbuf[256];
	    Win::RegKey unit_key;

	    len = sizeof(strbuf);
	    status = ::RegEnumKeyExA(adapter_key(),
				     i,
				     strbuf,
				     &len,
				     nullptr,
				     nullptr,
				     nullptr,
				     nullptr);
	    if (status == ERROR_NO_MORE_ITEMS)
	      break;
	    else if (status != ERROR_SUCCESS)
	      OPENVPN_THROW(tun_win_util, "tap_guids: error enumerating registry subkeys of key: " << ADAPTER);
	    strbuf[len] = '\0';

	    const std::string unit_string = std::string(ADAPTER)
	                                  + std::string("\\")
	                                  + std::string(strbuf);

	    status = ::RegOpenKeyExA(HKEY_LOCAL_MACHINE,
				     unit_string.c_str(),
				     0,
				     KEY_READ,
				     unit_key.ref());

	    if (status != ERROR_SUCCESS)
	      continue;

	    len = sizeof(strbuf);
	    status = ::RegQueryValueExA(unit_key(),
					"ComponentId",
					nullptr,
					&data_type,
					(LPBYTE)strbuf,
					&len);

	    if (status != ERROR_SUCCESS || data_type != REG_SZ)
	      continue;
	    strbuf[len] = '\0';
	    if (string::strcasecmp(strbuf, wintun ? WINTUN_COMPONENT_ID : COMPONENT_ID) &&
	      string::strcasecmp(strbuf, wintun ? ROOT_WINTUN_COMPONENT_ID : ROOT_COMPONENT_ID))
	      continue;

	    TapGuidLuid tgl;

	    len = sizeof(strbuf);
	    status = ::RegQueryValueExA(unit_key(),
					"NetCfgInstanceId",
					nullptr,
					&data_type,
					(LPBYTE)strbuf,
					&len);

	    if (status == ERROR_SUCCESS && data_type == REG_SZ)
	      {
		strbuf[len] = '\0';
		tgl.first = std::string(strbuf);
	      }

	    DWORD luid;
	    len = sizeof(luid);
	    status = ::RegQueryValueExA(unit_key(),
					"NetLuidIndex",
					nullptr,
					&data_type,
					(LPBYTE)&luid,
					&len);

	    if (status == ERROR_SUCCESS && data_type == REG_DWORD)
	      {
		tgl.second = luid;
	      }

	    ret.push_back(tgl);
	  }
	return ret;
      }

      struct TapNameGuidPair
      {
	TapNameGuidPair() : index(DWORD(-1)) {}

	bool index_defined() const { return index != DWORD(-1); }

	std::string index_or_name() const
	{
	  if (index_defined())
	    return to_string(index);
	  else if (!name.empty())
	    return '"' + name + '"';
	  else
	    OPENVPN_THROW(tun_win_util, "TapNameGuidPair: TAP interface " << guid << " has no name or interface index");
	}

	std::string name;
	std::string guid;
	DWORD net_luid_index;
	DWORD index;
      };

      struct TapNameGuidPairList : public std::vector<TapNameGuidPair>
      {
	TapNameGuidPairList(bool wintun)
	{
	  // first get the TAP guids
	  {
	    std::vector<TapGuidLuid> guids = tap_guids(wintun);
	    for (auto i = guids.begin(); i != guids.end(); i++)
	      {
		TapNameGuidPair pair;
		pair.guid = i->first;
		pair.net_luid_index = i->second;

		// lookup adapter index
		{
		  ULONG aindex;
		  const size_t len = 128;
		  wchar_t wbuf[len];
		  _snwprintf(wbuf, len, L"\\DEVICE\\TCPIP_%S", pair.guid.c_str());
		  wbuf[len-1] = 0;
		  if (::GetAdapterIndex(wbuf, &aindex) == NO_ERROR)
		    pair.index = aindex;
		}

		push_back(pair);
	      }
	  }

	  // next, match up control panel interface names with GUIDs
	  {
	    LONG status;
	    DWORD len;
	    DWORD data_type;

	    Win::RegKey network_connections_key;
	    status = ::RegOpenKeyExA(HKEY_LOCAL_MACHINE,
				     NETWORK_CONNECTIONS,
				     0,
				     KEY_READ,
				     network_connections_key.ref());
	    if (status != ERROR_SUCCESS)
	      {
		const Win::Error err(status);
		OPENVPN_THROW(tun_win_util, "TapNameGuidPairList: error opening network connections registry key: " << NETWORK_CONNECTIONS << " : " << err.message());
	      }

	    for (int i = 0;; ++i)
	      {
		char strbuf[256];
		Win::RegKey connection_key;

		len = sizeof(strbuf);
		status = ::RegEnumKeyExA(network_connections_key(),
					 i,
					 strbuf,
					 &len,
					 nullptr,
					 nullptr,
					 nullptr,
					 nullptr);
		if (status == ERROR_NO_MORE_ITEMS)
		  break;
		else if (status != ERROR_SUCCESS)
		  OPENVPN_THROW(tun_win_util, "TapNameGuidPairList: error enumerating registry subkeys of key: " << NETWORK_CONNECTIONS);
		strbuf[len] = '\0';

		const std::string guid = std::string(strbuf);
		const std::string connection_string = std::string(NETWORK_CONNECTIONS) + std::string("\\") + guid + std::string("\\Connection");

		status = ::RegOpenKeyExA(HKEY_LOCAL_MACHINE,
					 connection_string.c_str(),
					 0,
					 KEY_READ,
					 connection_key.ref());
		if (status != ERROR_SUCCESS)
		  continue;

		wchar_t wbuf[256] = L"";
		DWORD cbwbuf = sizeof(wbuf);
		status = ::RegQueryValueExW(connection_key(),
					    L"Name",
					    nullptr,
					    &data_type,
					    (LPBYTE)wbuf,
					    &cbwbuf);
		if (status != ERROR_SUCCESS || data_type != REG_SZ)
		  continue;
		wbuf[(cbwbuf / sizeof(wchar_t)) - 1] = L'\0';

		// iterate through self and try to patch the name
		{
		  for (iterator j = begin(); j != end(); j++)
		    {
		      TapNameGuidPair& pair = *j;
		      if (pair.guid == guid)
			pair.name = wstring::to_utf8(wbuf);
		    }
		}
	      }
	  }
	}

	std::string to_string() const
	{
	  std::ostringstream os;
	  for (const_iterator i = begin(); i != end(); i++)
	    {
	      const TapNameGuidPair& pair = *i;
	      os << "guid='" << pair.guid << '\'';
	      if (pair.index_defined())
		os << " index=" << pair.index;
	      if (!pair.name.empty())
		os << " name='" << pair.name << '\'';
	      os << std::endl;
	    }
	  return os.str();
	}

	std::string name_from_guid(const std::string& guid) const
	{
	  for (const_iterator i = begin(); i != end(); i++)
	    {
	      const TapNameGuidPair& pair = *i;
	      if (pair.guid == guid)
		return pair.name;
	    }
	}

	std::string guid_from_name(const std::string& name) const
	{
	  for (const_iterator i = begin(); i != end(); i++)
	    {
	      const TapNameGuidPair& pair = *i;
	      if (pair.name == name)
		return pair.guid;
	    }
	}
      };

      struct DeviceInstanceIdInterfacePair
      {
	std::string net_cfg_instance_id;
	std::string device_interface_list;
      };

      class DevInfoSetHelper
      {
      public:
	DevInfoSetHelper()
	{
	  handle = SetupDiGetClassDevsEx(&GUID_DEVCLASS_NET, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL);
	}

	bool is_valid()
	{
	  return handle != INVALID_HANDLE_VALUE;
	}

	operator HDEVINFO()
	{
	  return handle;
	}

	~DevInfoSetHelper()
	{
	  if (is_valid())
	    {
	      SetupDiDestroyDeviceInfoList(handle);
	    }
	}

      private:
	HDEVINFO handle;
      };

      struct DeviceInstanceIdInterfaceList : public std::vector<DeviceInstanceIdInterfacePair>
      {
	DeviceInstanceIdInterfaceList()
	{
	  DevInfoSetHelper device_info_set;
	  if (!device_info_set.is_valid())
	    return;

	  for (DWORD i = 0;; ++i)
	    {
	      SP_DEVINFO_DATA dev_info_data;
	      ZeroMemory(&dev_info_data, sizeof(SP_DEVINFO_DATA));
	      dev_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
	      BOOL res = SetupDiEnumDeviceInfo(device_info_set, i, &dev_info_data);
	      if (!res)
		{
		  if (GetLastError() == ERROR_NO_MORE_ITEMS)
		    break;
		  else
		    continue;
		}

	      Win::RegKey regkey;
	      *regkey.ref() = SetupDiOpenDevRegKey(device_info_set, &dev_info_data, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_QUERY_VALUE);
	      if (!regkey.defined())
		continue;

	      std::string str_net_cfg_instance_id;

	      DWORD size;
	      LONG status = RegQueryValueExA(regkey(), "NetCfgInstanceId", NULL, NULL, NULL, &size);
	      if (status != ERROR_SUCCESS)
		continue;
	      BufferAllocatedType<char, thread_unsafe_refcount> buf_net_cfg_inst_id(size, BufferAllocated::CONSTRUCT_ZERO);

	      status = RegQueryValueExA(regkey(), "NetCfgInstanceId", NULL, NULL, (LPBYTE)buf_net_cfg_inst_id.data(), &size);
	      if (status == ERROR_SUCCESS)
		{
		  buf_net_cfg_inst_id.data()[size - 1] = '\0';
		  str_net_cfg_instance_id = std::string(buf_net_cfg_inst_id.data());
		}
	      else
		continue;

	      res = SetupDiGetDeviceInstanceId(device_info_set, &dev_info_data, NULL, 0, &size);
	      if (res != FALSE && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
		continue;

	      BufferAllocatedType<char, thread_unsafe_refcount> buf_dev_inst_id(size, BufferAllocated::CONSTRUCT_ZERO);
	      if (!SetupDiGetDeviceInstanceId(device_info_set, &dev_info_data, buf_dev_inst_id.data(), size, &size))
		continue;
	      buf_dev_inst_id.set_size(size);

	      ULONG dev_interface_list_size = 0;
	      CONFIGRET cr = CM_Get_Device_Interface_List_Size(&dev_interface_list_size,
							       (LPGUID)& GUID_DEVINTERFACE_NET,
							       buf_dev_inst_id.data(),
							       CM_GET_DEVICE_INTERFACE_LIST_PRESENT);

	      if (cr != CR_SUCCESS)
		continue;

	      BufferAllocatedType<char, thread_unsafe_refcount> buf_dev_iface_list(dev_interface_list_size, BufferAllocated::CONSTRUCT_ZERO);
	      cr = CM_Get_Device_Interface_List((LPGUID)& GUID_DEVINTERFACE_NET, buf_dev_inst_id.data(),
						buf_dev_iface_list.data(),
      						dev_interface_list_size,
						CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
	      if (cr != CR_SUCCESS)
		continue;

	      DeviceInstanceIdInterfacePair pair;
	      pair.net_cfg_instance_id = str_net_cfg_instance_id;
	      pair.device_interface_list = std::string(buf_dev_iface_list.data());
	      push_back(pair);
	    }
	}
      };

      // given a TAP GUID, form the pathname of the TAP device node
      inline std::string tap_path(const TapNameGuidPair& tap)
      {
	  return std::string(USERMODEDEVICEDIR) + tap.guid + std::string(TAP_WIN_SUFFIX);
      }

      // open an available TAP adapter
      inline HANDLE tap_open(const TapNameGuidPairList& guids,
			     std::string& path_opened,
			     TapNameGuidPair& used,
			     bool wintun)
      {
	Win::ScopedHANDLE hand;

	std::unique_ptr<DeviceInstanceIdInterfaceList> inst_id_interface_list;
	if (wintun)
	  inst_id_interface_list.reset(new DeviceInstanceIdInterfaceList());

	// iterate over list of TAP adapters on system
	for (TapNameGuidPairList::const_iterator i = guids.begin(); i != guids.end(); i++)
	  {
	    const TapNameGuidPair& tap = *i;

	    std::string path;

	    if (wintun)
	      {
		for (const auto& inst_id_interface : *inst_id_interface_list)
		  {
		    if (inst_id_interface.net_cfg_instance_id != tap.guid)
		      continue;

		    path = inst_id_interface.device_interface_list;
		    break;
		  }
	      }
	    else
	      {
		path = tap_path(tap);
	      }

	    if (path.length() > 0)
	      {
		hand.reset(::CreateFileA(path.c_str(),
			   GENERIC_READ | GENERIC_WRITE,
			   0, /* was: FILE_SHARE_READ */
			   0,
			   OPEN_EXISTING,
			   FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED,
			   0));

		if (hand.defined())
		  {
		    used = tap;
		    path_opened = path;
		    break;
		  }
	      }
	  }
	return hand.release();
      }

      // set TAP adapter to topology subnet
      inline void tap_configure_topology_subnet(HANDLE th, const IP::Addr& local, const unsigned int prefix_len)
      {
	const IPv4::Addr netmask = IPv4::Addr::netmask_from_prefix_len(prefix_len);
	const IPv4::Addr network = local.to_ipv4() & netmask;

	std::uint32_t ep[3];
	ep[0] = htonl(local.to_ipv4().to_uint32());
	ep[1] = htonl(network.to_uint32());
	ep[2] = htonl(netmask.to_uint32());

	DWORD len;
	if (!::DeviceIoControl(th, TAP_WIN_IOCTL_CONFIG_TUN,
			       ep, sizeof (ep),
			       ep, sizeof (ep), &len, nullptr))
	  throw tun_win_util("DeviceIoControl TAP_WIN_IOCTL_CONFIG_TUN failed");
      }

      // set TAP adapter to topology net30
      inline void tap_configure_topology_net30(HANDLE th, const IP::Addr& local_addr, const IP::Addr& remote_addr)
      {
	const IPv4::Addr local = local_addr.to_ipv4();
	const IPv4::Addr remote = remote_addr.to_ipv4();

	std::uint32_t ep[2];
	ep[0] = htonl(local.to_uint32());
	ep[1] = htonl(remote.to_uint32());

	DWORD len;
	if (!::DeviceIoControl(th, TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT,
			       ep, sizeof (ep),
			       ep, sizeof (ep), &len, nullptr))
	  throw tun_win_util("DeviceIoControl TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT failed");
      }

      // set driver media status to 'connected'
      inline void tap_set_media_status(HANDLE th, bool media_status)
      {
	DWORD len;
	ULONG status = media_status ? TRUE : FALSE;
	if (!::DeviceIoControl(th, TAP_WIN_IOCTL_SET_MEDIA_STATUS,
			       &status, sizeof (status),
			       &status, sizeof (status), &len, nullptr))
	  throw tun_win_util("DeviceIoControl TAP_WIN_IOCTL_SET_MEDIA_STATUS failed");
      }

      // get debug logging from TAP driver (requires that
      // TAP driver was built with logging enabled)
      inline void tap_process_logging(HANDLE th)
      {
	const size_t size = 1024;
	std::unique_ptr<char[]> line(new char[size]);
	DWORD len;

	while (::DeviceIoControl(th, TAP_WIN_IOCTL_GET_LOG_LINE,
				 line.get(), size,
				 line.get(), size,
				 &len, nullptr))
	  {
	    OPENVPN_LOG("TAP-Windows: " << line.get());
	  }
      }

      struct InterfaceInfoList
      {
      public:
	InterfaceInfoList()
	{
	  DWORD size = 0;
	  if (::GetInterfaceInfo(nullptr, &size) != ERROR_INSUFFICIENT_BUFFER)
	    OPENVPN_THROW(tun_win_util, "InterfaceInfoList: GetInterfaceInfo #1");
	  list.reset((IP_INTERFACE_INFO*)new unsigned char[size]);
	  if (::GetInterfaceInfo(list.get(), &size) != NO_ERROR)
	    OPENVPN_THROW(tun_win_util, "InterfaceInfoList: GetInterfaceInfo #2");
	}

	IP_ADAPTER_INDEX_MAP* iface(const DWORD index) const
	{
	  if (list)
	    {
	      for (LONG i = 0; i < list->NumAdapters; ++i)
		{
		  IP_ADAPTER_INDEX_MAP* inter = &list->Adapter[i];
		  if (index == inter->Index)
		    return inter;
		}
	    }
	  return nullptr;
	}

	std::unique_ptr<IP_INTERFACE_INFO> list;
      };

      inline void dhcp_release(const InterfaceInfoList& ii,
			       const DWORD adapter_index,
			       std::ostream& os)
      {
	IP_ADAPTER_INDEX_MAP* iface = ii.iface(adapter_index);
	if (iface)
	  {
	    const DWORD status = ::IpReleaseAddress(iface);
	    if (status == NO_ERROR)
	      os << "TAP: DHCP release succeeded" << std::endl;
	    else
	      os << "TAP: DHCP release failed" << std::endl;
	  }
      }

      inline void dhcp_renew(const InterfaceInfoList& ii,
			     const DWORD adapter_index,
			     std::ostream& os)
      {
	IP_ADAPTER_INDEX_MAP* iface = ii.iface(adapter_index);
	if (iface)
	  {
	    const DWORD status = ::IpRenewAddress(iface);
	    if (status == NO_ERROR)
	      os << "TAP: DHCP renew succeeded" << std::endl;
	    else
	      os << "TAP: DHCP renew failed" << std::endl;
	  }
      }

      inline void flush_arp(const DWORD adapter_index,
			    std::ostream& os)
      {
	const DWORD status = ::FlushIpNetTable2(AF_INET, adapter_index);
	if (status == NO_ERROR)
	  os << "TAP: ARP flush succeeded" << std::endl;
	else
	  os << "TAP: ARP flush failed" << std::endl;
      }

      struct IPNetmask4
      {
	IPNetmask4() {}

	IPNetmask4(const TunBuilderCapture& pull, const char *title)
	{
	  const TunBuilderCapture::RouteAddress* local4 = pull.vpn_ipv4();
	  if (local4)
	    {
	      ip = IPv4::Addr::from_string(local4->address, title);
	      netmask = IPv4::Addr::netmask_from_prefix_len(local4->prefix_length);
	    }
	}

	IPNetmask4(const IP_ADDR_STRING *ias)
	{
	  if (ias)
	    {
	      try {
		if (ias->IpAddress.String)
		  ip = IPv4::Addr::from_string(ias->IpAddress.String);
	      }
	      catch (const std::exception&)
		{
		}
	      try {
		if (ias->IpMask.String)
		  netmask = IPv4::Addr::from_string(ias->IpMask.String);
	      }
	      catch (const std::exception&)
		{
		}
	    }
	}

	bool operator==(const IPNetmask4& rhs) const
	{
	  return ip == rhs.ip && netmask == rhs.netmask;
	}

	bool operator!=(const IPNetmask4& rhs) const
	{
	  return !operator==(rhs);
	}

	IPv4::Addr ip = IPv4::Addr::from_zero();
	IPv4::Addr netmask = IPv4::Addr::from_zero();
      };

      struct IPAdaptersInfo
      {
	IPAdaptersInfo()
	{
	  ULONG size = 0;
	  if (::GetAdaptersInfo(nullptr, &size) != ERROR_BUFFER_OVERFLOW)
	    OPENVPN_THROW(tun_win_util, "IPAdaptersInfo: GetAdaptersInfo #1");
	  list.reset((IP_ADAPTER_INFO*)new unsigned char[size]);
	  if (::GetAdaptersInfo(list.get(), &size) != NO_ERROR)
	    OPENVPN_THROW(tun_win_util, "IPAdaptersInfo: GetAdaptersInfo #2");
	}

	const IP_ADAPTER_INFO* adapter(const DWORD index) const
	{
	  if (list)
	    {
	      for (const IP_ADAPTER_INFO* a = list.get(); a != nullptr; a = a->Next)
		{
		  if (index == a->Index)
		    return a;
		}
	    }
	  return nullptr;
	}

	bool is_up(const DWORD index, const IPNetmask4& vpn_addr) const
	{
	  const IP_ADAPTER_INFO* ai = adapter(index);
	  if (ai)
	    {
	      for (const IP_ADDR_STRING *iplist = &ai->IpAddressList; iplist != nullptr; iplist = iplist->Next)
		{
		  if (vpn_addr == IPNetmask4(iplist))
		    return true;
		}
	    }
	  return false;
	}

	bool is_dhcp_enabled(const DWORD index) const
	{
	  const IP_ADAPTER_INFO* ai = adapter(index);
	  return ai && ai->DhcpEnabled;
	}

	std::unique_ptr<IP_ADAPTER_INFO> list;
      };

      struct IPPerAdapterInfo
      {
	IPPerAdapterInfo(const DWORD index)
	{
	  ULONG size = 0;
	  if (::GetPerAdapterInfo(index, nullptr, &size) != ERROR_BUFFER_OVERFLOW)
	    return;
	  adapt.reset((IP_PER_ADAPTER_INFO*)new unsigned char[size]);
	  if (::GetPerAdapterInfo(index, adapt.get(), &size) != ERROR_SUCCESS)
	    adapt.reset();
	}

	std::unique_ptr<IP_PER_ADAPTER_INFO> adapt;
      };

      // Use the TAP DHCP masquerade capability to set TAP adapter properties.
      // Generally only used on pre-Vista.
      class TAPDHCPMasquerade
      {
      public:
	OPENVPN_EXCEPTION(dhcp_masq);

	// VPN IP/netmask
	IPNetmask4 vpn;

	// IP address of fake DHCP server in TAP adapter
	IPv4::Addr dhcp_serv_addr = IPv4::Addr::from_zero();

	// DHCP lease for one year
	unsigned int lease_time = 31536000;

	// DHCP options
	std::string domain;            // DOMAIN (15)
	std::string netbios_scope;     // NBS (47)
	int netbios_node_type = 0;     // NBT 1,2,4,8 (46)
	bool disable_nbt = false;      // DISABLE_NBT (43, Vendor option 001)
	std::vector<IPv4::Addr> dns;   // DNS (6)
	std::vector<IPv4::Addr> wins;  // WINS (44)
	std::vector<IPv4::Addr> ntp;   // NTP (42)
	std::vector<IPv4::Addr> nbdd;  // NBDD (45)

	void init_from_capture(const TunBuilderCapture& pull)
	{
	  // VPN IP/netmask
	  vpn = IPNetmask4(pull, "VPN IP");

	  // DHCP server address
	  {
	    const IPv4::Addr network_addr = vpn.ip & vpn.netmask;
	    const std::uint32_t extent = vpn.netmask.extent_from_netmask_uint32();
	    if (extent >= 16)
	      dhcp_serv_addr = network_addr + (extent - 2);
	    else
	      dhcp_serv_addr = network_addr;
	  }

	  // DNS
	  for (auto &ds : pull.dns_servers)
	    {
	      if (!ds.ipv6)
		dns.push_back(IPv4::Addr::from_string(ds.address, "DNS Server"));
	    }

	  // WINS
	  for (auto &ws : pull.wins_servers)
	    wins.push_back(IPv4::Addr::from_string(ws.address, "WINS Server"));

	  // DOMAIN
	  if (!pull.search_domains.empty())
	    domain = pull.search_domains[0].domain;
	}

	void ioctl(HANDLE th) const
	{
	  // TAP_WIN_IOCTL_CONFIG_DHCP_MASQ
	  {
	    std::uint32_t ep[4];
	    ep[0] = vpn.ip.to_uint32_net();
	    ep[1] = vpn.netmask.to_uint32_net();
	    ep[2] = dhcp_serv_addr.to_uint32_net();
	    ep[3] = lease_time;

	    DWORD len;
	    if (!::DeviceIoControl(th, TAP_WIN_IOCTL_CONFIG_DHCP_MASQ,
				   ep, sizeof (ep),
				   ep, sizeof (ep), &len, nullptr))
	      throw dhcp_masq("DeviceIoControl TAP_WIN_IOCTL_CONFIG_DHCP_MASQ failed");
	  }

	  // TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT
	  {
	    BufferAllocated buf(256, BufferAllocated::GROW);
	    write_options(buf);

	    DWORD len;
	    if (!::DeviceIoControl(th, TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT,
				   buf.data(), buf.size(),
				   buf.data(), buf.size(), &len, nullptr))
	      throw dhcp_masq("DeviceIoControl TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT failed");
	  }
	}

      private:
	void write_options(Buffer& buf) const
	{
	  // DOMAIN
	  write_dhcp_str(buf, 15, domain);

	  // NBS
	  write_dhcp_str(buf, 47, netbios_scope);

	  // NBT
	  if (netbios_node_type)
	    write_dhcp_u8(buf, 46, netbios_node_type);

	  // DNS
	  write_dhcp_addr_list(buf, 6, dns);

	  // WINS
	  write_dhcp_addr_list(buf, 44, wins);

	  // NTP
	  write_dhcp_addr_list(buf, 42, ntp);

	  // NBDD
	  write_dhcp_addr_list(buf, 45, nbdd);

	  // DISABLE_NBT
	  //
	  // The MS DHCP server option 'Disable Netbios-over-TCP/IP
	  // is implemented as vendor option 001, value 002.
	  // A value of 001 means 'leave NBT alone' which is the default.
	  if (disable_nbt)
	    {
	      buf.push_back(43);
	      buf.push_back(6);     // total length field
	      buf.push_back(0x001);
	      buf.push_back(4);     // length of the vendor-specified field
	      {
		const std::uint32_t raw = 0x002;
		buf.write((const unsigned char *)&raw, sizeof(raw));
	      }
	    }
	}

	static void write_dhcp_u8(Buffer& buf,
				  const unsigned char type,
				  const unsigned char data)
	{
	  buf.push_back(type);
	  buf.push_back(1);
	  buf.push_back(data);
	}

	static void write_dhcp_str(Buffer& buf,
				   const unsigned char type,
				   const std::string& str)
	{
	  const size_t len = str.length();
	  if (len)
	    {
	      if (len > 255)
		OPENVPN_THROW(dhcp_masq, "string '" << str << "' must be > 0 bytes and <= 255 bytes");
	      buf.push_back(type);
	      buf.push_back((unsigned char)len);
	      buf.write((const unsigned char *)str.c_str(), len);
	    }
	}

	static void write_dhcp_addr_list(Buffer& buf,
					 const unsigned char type,
					 const std::vector<IPv4::Addr>& addr_list)
	{
	  if (!addr_list.empty())
	    {
	      const size_t size = addr_list.size() * sizeof(std::uint32_t);
	      if (size < 1 || size > 255)
		OPENVPN_THROW(dhcp_masq, "array size=" << size << " must be > 0 bytes and <= 255 bytes");
	      buf.push_back(type);
	      buf.push_back((unsigned char)size);
	      for (auto &a : addr_list)
		{
		  const std::uint32_t rawaddr = a.to_uint32_net();
		  buf.write((const unsigned char *)&rawaddr, sizeof(std::uint32_t));
		}
	    }
	}
      };

      class TAPDriverVersion
      {
      public:
	TAPDriverVersion(HANDLE th)
	  : defined(false)
	{
	  DWORD len;
	  info[0] = info[1] = info[2] = 0;
	  if (::DeviceIoControl(th, TAP_WIN_IOCTL_GET_VERSION,
				&info, sizeof (info),
				&info, sizeof (info), &len, nullptr))
	    defined = true;
	}

	std::string to_string()
	{
	  std::ostringstream os;
	  os << "TAP-Windows Driver Version ";
	  if (defined)
	    {
	      os << info[0] << '.' << info[1];
	      if (info[2])
		os << " (DEBUG)";
	    }
	  else
	    os << "UNDEFINED";
	  return os.str();
	}

      private:
	bool defined;
	ULONG info[3];
      };

      // An action to set the DNS "Connection-specific DNS Suffix"
      class ActionSetAdapterDomainSuffix : public Action
      {
      public:
	ActionSetAdapterDomainSuffix(const std::string& search_domain_arg,
			      const std::string& tap_guid_arg)
	  : search_domain(search_domain_arg),
	    tap_guid(tap_guid_arg)
	{
	}

	virtual void execute(std::ostream& os) override
	{
	  os << to_string() << std::endl;

	  LONG status;
	  Win::RegKey key;
	  const std::string reg_key_name = "SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters\\Interfaces\\" + tap_guid;
	  status = ::RegOpenKeyExA(HKEY_LOCAL_MACHINE,
				   reg_key_name.c_str(),
				   0,
				   KEY_READ|KEY_WRITE,
				   key.ref());
	  if (status != ERROR_SUCCESS)
	    {
	      const Win::Error err(status);
	      OPENVPN_THROW(tun_win_util, "ActionSetAdapterDomainSuffix: error opening registry key: " << reg_key_name << " : " << err.message());
	    }

	  Win::UTF16 dom(Win::utf16(search_domain));
	  status = ::RegSetValueExW(key(),
				    L"Domain",
				    0,
				    REG_SZ,
				    (const BYTE *)dom.get(),
				    (Win::utf16_strlen(dom.get())+1)*2);
	  if (status != ERROR_SUCCESS)
	    OPENVPN_THROW(tun_win_util, "ActionSetAdapterDomainSuffix: error writing Domain registry key: " << reg_key_name);
	}

	virtual std::string to_string() const override
	{
	  return "Set adapter domain suffix: '" + search_domain + "' " + tap_guid;
	}

      private:
	const std::string search_domain;
	const std::string tap_guid;
      };

      // get the Windows IPv4 routing table
      inline const MIB_IPFORWARDTABLE* windows_routing_table()
      {
	ULONG size = 0;
	DWORD status;
	std::unique_ptr<MIB_IPFORWARDTABLE> rt;

	status = ::GetIpForwardTable(nullptr, &size, TRUE);
	if (status == ERROR_INSUFFICIENT_BUFFER)
	  {
	    rt.reset((MIB_IPFORWARDTABLE*)new unsigned char[size]);
	    status = ::GetIpForwardTable(rt.get(), &size, TRUE);
	    if (status != NO_ERROR)
	      {
		OPENVPN_LOG("windows_routing_table: GetIpForwardTable failed");
		return nullptr;
	      }
	  }
	return rt.release();
      }

#if _WIN32_WINNT >= 0x0600 // Vista and higher
      // Get the Windows IPv4/IPv6 routing table.
      // Note that returned pointer must be freed with FreeMibTable.
      inline const MIB_IPFORWARD_TABLE2* windows_routing_table2(ADDRESS_FAMILY af)
      {
	MIB_IPFORWARD_TABLE2* routes = nullptr;
	int res = ::GetIpForwardTable2(af, &routes);
	if (res == NO_ERROR)
	  return routes;
	else
	  return nullptr;
      }
#endif

      class BestGateway
      {
      public:
	/**
	 * Construct object which represents default gateway
	 */
	BestGateway()
	{
	  std::unique_ptr<const MIB_IPFORWARDTABLE> rt(windows_routing_table());
	  if (rt)
	    {
	      const MIB_IPFORWARDROW* gw = nullptr;
	      for (size_t i = 0; i < rt->dwNumEntries; ++i)
		{
		  const MIB_IPFORWARDROW* row = &rt->table[i];
		  if (!row->dwForwardDest && !row->dwForwardMask
		      && (!gw || row->dwForwardMetric1 < gw->dwForwardMetric1))
		    gw = row;
		}
	      if (gw)
		{
		  index = gw->dwForwardIfIndex;
		  addr = IPv4::Addr::from_uint32(ntohl(gw->dwForwardNextHop)).to_string();
		}
	    }
	}

	/**
	 * Construct object which represents best gateway to given
	 * destination, excluding gateway on VPN interface. Gateway is chosen
	 * first by the longest prefix match and then by metric. If destination
	 * is in local network, no gateway is selected and "local_route" flag is set.
	 *
	 * @param dest destination IPv4 address
	 * @param vpn_interface_index index of VPN interface which is excluded from gateway selection
	 */
	BestGateway(const std::string& dest, DWORD vpn_interface_index)
	{
	  DWORD dest_addr;
	  auto res = inet_pton(AF_INET, dest.c_str(), &dest_addr);
	  switch (res)
	    {
	    case -1:
	      OPENVPN_THROW(tun_win_util, "GetBestGateway: error converting IPv4 address " << dest << " to int: " << ::WSAGetLastError());

	    case 0:
	      OPENVPN_THROW(tun_win_util, "GetBestGateway: " << dest << " is not a valid IPv4 address");
	    }

	  {
	    MIB_IPFORWARDROW row;
	    DWORD res2 = GetBestRoute(dest_addr, 0, &row);
	    if (res2 != NO_ERROR)
	      {
		OPENVPN_THROW(tun_win_util, "GetBestGateway: error retrieving the best route for " << dest << ": " << res2);
	      }

	    if (row.dwForwardType == MIB_IPROUTE_TYPE_DIRECT)
	      {
		local_route_ = true;
		return;
	      }
	  }

	  std::unique_ptr<const MIB_IPFORWARDTABLE> rt(windows_routing_table());
	  if (rt)
	    {
	      const MIB_IPFORWARDROW* gw = nullptr;
	      for (size_t i = 0; i < rt->dwNumEntries; ++i)
		{
		  const MIB_IPFORWARDROW* row = &rt->table[i];
		  // does route match?
		  if ((dest_addr & row->dwForwardMask) == (row->dwForwardDest & row->dwForwardMask))
		    {
		      // skip gateway on VPN interface
		      if ((vpn_interface_index != DWORD(-1)) && (row->dwForwardIfIndex == vpn_interface_index))
			{
			  OPENVPN_LOG("GetBestGateway: skip gateway " <<
				      IPv4::Addr::from_uint32(ntohl(row->dwForwardNextHop)).to_string() <<
				      " on VPN interface " << vpn_interface_index);
			  continue;
			}

		      if (!gw)
			{
			  gw = row;
			  continue;
			}

		      auto cur_prefix = IPv4::Addr::prefix_len_32(ntohl(gw->dwForwardMask));
		      auto new_prefix = IPv4::Addr::prefix_len_32(ntohl(row->dwForwardMask));
		      auto new_metric_is_higher = row->dwForwardMetric1 > gw->dwForwardMetric1;

		      if ((new_prefix > cur_prefix) || ((new_prefix == cur_prefix) && (new_metric_is_higher)))
			gw = row;
		    }
		}
	      if (gw)
		{
		  index = gw->dwForwardIfIndex;
		  addr = IPv4::Addr::from_uint32(ntohl(gw->dwForwardNextHop)).to_string();
		  OPENVPN_LOG("GetBestGateway: selected gateway " << addr << " on adapter " << index << " for destination " << dest);
		}
	    }
	}

	bool defined() const
	{
	  return index != DWORD(-1) && !addr.empty();
	}

	DWORD interface_index() const
	{
	  return index;
	}

	const std::string& gateway_address() const
	{
	  return addr;
	}

	/**
	 * Return true if destination, provided to constructor,
	 * doesn't require gateway, false otherwise.
	 */
	bool local_route() const
	{
	  return local_route_;
	}

      private:
	DWORD index = -1;
	std::string addr;
	bool local_route_ = false;
      };

      // An action to delete all routes on an interface
      class ActionDeleteAllRoutesOnInterface : public Action
      {
      public:
	ActionDeleteAllRoutesOnInterface(const DWORD iface_index_arg)
	  : iface_index(iface_index_arg)
	{
	}

	virtual void execute(std::ostream& os) override
	{
	  os << to_string() << std::endl;

	  ActionList::Ptr actions = new ActionList();
	  remove_all_ipv4_routes_on_iface(iface_index, *actions);
#if _WIN32_WINNT >= 0x0600 // Vista and higher
	  remove_all_ipv6_routes_on_iface(iface_index, *actions);
#endif
	  actions->execute(os);
	}

	virtual std::string to_string() const override
	{
	  return "ActionDeleteAllRoutesOnInterface iface_index=" + std::to_string(iface_index);
	}

      private:
	static void remove_all_ipv4_routes_on_iface(DWORD index, ActionList& actions)
	{
	  std::unique_ptr<const MIB_IPFORWARDTABLE> rt(windows_routing_table());
	  if (rt)
	    {
	      for (size_t i = 0; i < rt->dwNumEntries; ++i)
		{
		  const MIB_IPFORWARDROW* row = &rt->table[i];
		  if (row->dwForwardIfIndex == index)
		    {
		      const IPv4::Addr net = IPv4::Addr::from_uint32(ntohl(row->dwForwardDest));
		      const IPv4::Addr mask = IPv4::Addr::from_uint32(ntohl(row->dwForwardMask));
		      const std::string net_str = net.to_string();
		      const unsigned int pl = mask.prefix_len();

		      // don't remove multicast route or other Windows-assigned routes
		      if (net_str == "224.0.0.0" && pl == 4)
			continue;
		      if (net_str == "255.255.255.255" && pl == 32)
			continue;

		      actions.add(new WinCmd("netsh interface ip delete route " + net_str + '/' + openvpn::to_string(pl) + ' ' + openvpn::to_string(index) + " store=active"));
		    }
		}
	    }
	}

#if _WIN32_WINNT >= 0x0600 // Vista and higher
	static void remove_all_ipv6_routes_on_iface(DWORD index, ActionList& actions)
	{
	  unique_ptr_del<const MIB_IPFORWARD_TABLE2> rt2(windows_routing_table2(AF_INET6),
							 [](const MIB_IPFORWARD_TABLE2* p) { FreeMibTable((PVOID)p); });
	  if (rt2)
	    {
	      const IPv6::Addr ll_net = IPv6::Addr::from_string("fe80::");
	      const IPv6::Addr ll_mask = IPv6::Addr::netmask_from_prefix_len(64);
	      for (size_t i = 0; i < rt2->NumEntries; ++i)
		{
		  const MIB_IPFORWARD_ROW2* row = &rt2->Table[i];
		  if (row->InterfaceIndex == index)
		    {
		      const unsigned int pl = row->DestinationPrefix.PrefixLength;
		      if (row->DestinationPrefix.Prefix.si_family == AF_INET6)
			{
			  const IPv6::Addr net = IPv6::Addr::from_byte_string(row->DestinationPrefix.Prefix.Ipv6.sin6_addr.u.Byte);
			  const std::string net_str = net.to_string();

			  // don't remove multicast route or other Windows-assigned routes
			  if (net_str == "ff00::" && pl == 8)
			    continue;
			  if ((net & ll_mask) == ll_net && pl >= 64)
			    continue;
			  actions.add(new WinCmd("netsh interface ipv6 delete route " + net_str + '/' + openvpn::to_string(pl) + ' ' + openvpn::to_string(index) + " store=active"));
			}
		    }
		}
	    }
	}
#endif

	const DWORD iface_index;
      };

      class ActionEnableDHCP : public WinCmd
      {
      public:
	ActionEnableDHCP(const TapNameGuidPair& tap)
	  : WinCmd(cmd(tap))
	{
	}

      private:
	static std::string cmd(const TapNameGuidPair& tap)
	{
	  return "netsh interface ip set address " + tap.index_or_name() + " dhcp";
	}
      };

      namespace TunNETSH
      {
	class AddRoute4Cmd : public Action
	{
	public:
	  typedef RCPtr<AddRoute4Cmd> Ptr;

	  AddRoute4Cmd(const std::string& route_address,
		       int prefix_length,
		       const TunWin::Util::TapNameGuidPair& tap,
		       const std::string& gw_address,
		       int metric,
		       bool add)
	  {
	    std::ostringstream os;
	    os << "netsh interface ip ";
	    if (add)
	      os << "add ";
	    else
	      os << "delete ";
	    os << "route " << route_address << "/" << std::to_string(prefix_length) << " " << tap.index_or_name() << " " << gw_address << " ";
	    if (add && metric >= 0)
	      os << "metric=" << std::to_string(metric) << " ";
	    os << "store=active";
	    cmd.reset(new WinCmd(os.str()));
	  };

	  void execute(std::ostream& os) override
	  {
	    cmd->execute(os);
	  }

	  std::string to_string() const override
	  {
	    return cmd->to_string();
	  }

	private:
	  WinCmd::Ptr cmd;
	};
      }

      namespace TunIPHELPER
      {
	static SOCKADDR_INET sockaddr_inet(short family, const std::string& addr)
	{
	  SOCKADDR_INET sa;
	  ZeroMemory(&sa, sizeof(sa));
	  sa.si_family = family;
	  inet_pton(family, addr.c_str(), family == AF_INET ? &(sa.Ipv4.sin_addr) : (PVOID) & (sa.Ipv6.sin6_addr));
	  return sa;
	}

	static DWORD InterfaceLuid(const std::string& iface_name, PNET_LUID luid)
	{
	  auto wide_name = wstring::from_utf8(iface_name);
	  return ConvertInterfaceAliasToLuid(wide_name.c_str(), luid);
	}

	class AddRoute4Cmd : public Action
	{
	public:
	  typedef RCPtr<AddRoute4Cmd> Ptr;

	  AddRoute4Cmd(const std::string& route_address,
		       int prefix_length,
		       const TunWin::Util::TapNameGuidPair& tap,
		       const std::string& gw_address,
		       int metric,
		       bool add) : add(add)
	  {
	    os_ << "IPHelper: ";
	    if (add)
	      os_ << "add ";
	    else
	      os_ << "delete ";
	    os_ << "route " << route_address << "/" << std::to_string(prefix_length) << " " << tap.index_or_name() << " " << gw_address << " ";
	    os_ << "metric=" << std::to_string(metric);

	    ZeroMemory(&fwd_row, sizeof(fwd_row));
	    fwd_row.ValidLifetime = 0xffffffff;
	    fwd_row.PreferredLifetime = 0xffffffff;
	    fwd_row.Protocol = (NL_ROUTE_PROTOCOL)MIB_IPPROTO_NETMGMT;
	    fwd_row.Metric = metric;
	    fwd_row.DestinationPrefix.Prefix = sockaddr_inet(AF_INET, route_address);
	    fwd_row.DestinationPrefix.PrefixLength = prefix_length;
	    fwd_row.NextHop = sockaddr_inet(AF_INET, gw_address);

	    if (tap.index_defined())
	      fwd_row.InterfaceIndex = tap.index;
	    else if (!tap.name.empty())
	      {
		NET_LUID luid;
		auto err = InterfaceLuid(tap.name, &luid);
		if (err)
		  OPENVPN_THROW(tun_win_util, "Cannot convert interface name " << tap.name << " to LUID");
		fwd_row.InterfaceLuid = luid;
	      }
	  };

	  void execute(std::ostream& os) override
	  {
	    os << os_.str() << std::endl;
	    DWORD res;
	    if (add)
	      res = CreateIpForwardEntry2(&fwd_row);
	    else
	      res = DeleteIpForwardEntry2(&fwd_row);
	    if (res)
	      os << "cannot modify route: error " << res << std::endl;
	  }

	  std::string to_string() const override
	  {
	    return os_.str();
	  }

	private:
	  MIB_IPFORWARD_ROW2 fwd_row;
	  bool add;
	  std::ostringstream os_;
	};
      }
    }
  }
}

#endif