/*
 *  OpenVPN -- An application to securely tunnel IP networks
 *             over a single TCP/UDP port, with support for SSL/TLS-based
 *             session authentication and key exchange,
 *             packet encryption, packet authentication, and
 *             packet compression.
 *
 *  Copyright (C) 2002-2005 OpenVPN Solutions LLC <info@openvpn.net>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2
 *  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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program (see the file COPYING included with this
 *  distribution); if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * Support routines for configuring and accessing TUN/TAP
 * virtual network adapters.
 *
 * This file is based on the TUN/TAP driver interface routines
 * from VTun by Maxim Krasnyansky <max_mk@yahoo.com>.
 */

#ifdef WIN32
#include "config-win32.h"
#else
#include "config.h"
#endif

#include "syshead.h"

#include "tun.h"
#include "fdmisc.h"
#include "common.h"
#include "misc.h"
#include "socket.h"
#include "manage.h"

#include "memdbg.h"

#ifdef TARGET_SOLARIS
static void solaris_error_close (struct tuntap *tt, const struct env_set *es, const char *actual);
#endif

bool
is_dev_type (const char *dev, const char *dev_type, const char *match_type)
{
  ASSERT (match_type);
  if (!dev)
    return false;
  if (dev_type)
    return !strcmp (dev_type, match_type);
  else
    return !strncmp (dev, match_type, strlen (match_type));
}

int
dev_type_enum (const char *dev, const char *dev_type)
{
  if (is_dev_type (dev, dev_type, "tun"))
    return DEV_TYPE_TUN;
  else if (is_dev_type (dev, dev_type, "tap"))
    return DEV_TYPE_TAP;
  else if (is_dev_type (dev, dev_type, "null"))
    return DEV_TYPE_NULL;
  else
    return DEV_TYPE_UNDEF;
}

const char *
dev_type_string (const char *dev, const char *dev_type)
{
  switch (dev_type_enum (dev, dev_type))
    {
    case DEV_TYPE_TUN:
      return "tun";
    case DEV_TYPE_TAP:
      return "tap";
    case DEV_TYPE_NULL:
      return "null";
    default:
      return "[unknown-dev-type]";
    }
}

const char *
dev_component_in_dev_node (const char *dev_node)
{
  const char *ret;
  const int dirsep = OS_SPECIFIC_DIRSEP;

  if (dev_node)
    {
      ret = strrchr (dev_node, dirsep);
      if (ret && *ret)
	++ret;
      else
	ret = dev_node;
      if (*ret)
	return ret;
    }
  return NULL;
}

/*
 * Try to predict the actual TUN/TAP device instance name,
 * before the device is actually opened.
 */
const char *
guess_tuntap_dev (const char *dev,
		  const char *dev_type,
		  const char *dev_node,
		  struct gc_arena *gc)
{
#ifdef WIN32
  const int dt = dev_type_enum (dev, dev_type);
  if (dt == DEV_TYPE_TUN || dt == DEV_TYPE_TAP)
    {
      return get_netsh_id (dev_node, gc);
    }
#endif

  /* default case */
  return dev;
}

/*
 * Called by the open_tun function of OSes to check if we
 * explicitly support IPv6.
 *
 * In this context, explicit means that the OS expects us to
 * do something special to the tun socket in order to support
 * IPv6, i.e. it is not transparent.
 *
 * ipv6_explicitly_supported should be set to false if we don't
 * have any explicit IPv6 code in the tun device handler.
 *
 * If ipv6_explicitly_supported is true, then we have explicit
 * OS-specific tun dev code for handling IPv6.  If so, tt->ipv6
 * is set according to the --tun-ipv6 command line option.
 */
static void
ipv6_support (bool ipv6, bool ipv6_explicitly_supported, struct tuntap* tt)
{
  tt->ipv6 = false;
  if (ipv6_explicitly_supported)
    tt->ipv6 = ipv6;
  else if (ipv6)
    msg (M_WARN, "NOTE: explicit support for IPv6 tun devices is not provided for this OS");
}

/* --ifconfig-nowarn disables some options sanity checking */
static const char ifconfig_warn_how_to_silence[] = "(silence this warning with --ifconfig-nowarn)";

/*
 * If !tun, make sure ifconfig_remote_netmask looks
 *  like a netmask.
 *
 * If tun, make sure ifconfig_remote_netmask looks
 *  like an IPv4 address.
 */
static void
ifconfig_sanity_check (bool tun, in_addr_t addr)
{
  struct gc_arena gc = gc_new ();
  const bool looks_like_netmask = ((addr & 0xFF000000) == 0xFF000000);
  if (tun)
    {
      if (looks_like_netmask)
	msg (M_WARN, "WARNING: Since you are using --dev tun, the second argument to --ifconfig must be an IP address.  You are using something (%s) that looks more like a netmask. %s",
	     print_in_addr_t (addr, 0, &gc),
	     ifconfig_warn_how_to_silence);
    }
  else /* tap */
    {
      if (!looks_like_netmask)
	msg (M_WARN, "WARNING: Since you are using --dev tap, the second argument to --ifconfig must be a netmask, for example something like 255.255.255.0. %s",
	     ifconfig_warn_how_to_silence);
    }
  gc_free (&gc);
}

/*
 * For TAP-style devices, generate a broadcast address.
 */
static in_addr_t
generate_ifconfig_broadcast_addr (in_addr_t local,
				  in_addr_t netmask)
{
  return local | ~netmask;
}

/*
 * Check that --local and --remote addresses do not
 * clash with ifconfig addresses or subnet.
 */
static void
check_addr_clash (const char *name,
		  int type,
		  in_addr_t public,
		  in_addr_t local,
		  in_addr_t remote_netmask)
{
  struct gc_arena gc = gc_new ();
#if 0
  msg (M_INFO, "CHECK_ADDR_CLASH type=%d public=%s local=%s, remote_netmask=%s",
       type,
       print_in_addr_t (public, 0, &gc),
       print_in_addr_t (local, 0, &gc),
       print_in_addr_t (remote_netmask, 0, &gc));
#endif

  if (public)
    {
      if (type == DEV_TYPE_TUN)
	{
	  const in_addr_t test_netmask = 0xFFFFFF00;
	  const in_addr_t public_net = public & test_netmask;
	  const in_addr_t local_net = local & test_netmask;
	  const in_addr_t remote_net = remote_netmask & test_netmask;

	  if (public == local || public == remote_netmask)
	    msg (M_WARN,
		 "WARNING: --%s address [%s] conflicts with --ifconfig address pair [%s, %s]. %s",
		 name,
		 print_in_addr_t (public, 0, &gc),
		 print_in_addr_t (local, 0, &gc),
		 print_in_addr_t (remote_netmask, 0, &gc),
		 ifconfig_warn_how_to_silence);

	  if (public_net == local_net || public_net == remote_net)
	    msg (M_WARN,
		 "WARNING: potential conflict between --%s address [%s] and --ifconfig address pair [%s, %s] -- this is a warning only that is triggered when local/remote addresses exist within the same /24 subnet as --ifconfig endpoints. %s",
		 name,
		 print_in_addr_t (public, 0, &gc),
		 print_in_addr_t (local, 0, &gc),
		 print_in_addr_t (remote_netmask, 0, &gc),
		 ifconfig_warn_how_to_silence);
	}
      else if (type == DEV_TYPE_TAP)
	{
	  const in_addr_t public_network = public & remote_netmask;
	  const in_addr_t virtual_network = local & remote_netmask;
	  if (public_network == virtual_network)
	    msg (M_WARN,
		 "WARNING: --%s address [%s] conflicts with --ifconfig subnet [%s, %s] -- local and remote addresses cannot be inside of the --ifconfig subnet. %s",
		 name,
		 print_in_addr_t (public, 0, &gc),
		 print_in_addr_t (local, 0, &gc),
		 print_in_addr_t (remote_netmask, 0, &gc),
		 ifconfig_warn_how_to_silence);
	}
    }
  gc_free (&gc);
}

/*
 * Complain if --dev tap and --ifconfig is used on an OS for which
 * we don't have a custom tap ifconfig template below.
 */
static void
no_tap_ifconfig ()
{
  msg (M_FATAL, "Sorry but you cannot use --dev tap and --ifconfig together on this OS because I have not yet been programmed to understand the appropriate ifconfig syntax to use for TAP-style devices on this OS.  Your best alternative is to use an --up script and do the ifconfig command manually.");
}

/*
 * Return a string to be used for options compatibility check
 * between peers.
 */
const char *
ifconfig_options_string (const struct tuntap* tt, bool remote, bool disable, struct gc_arena *gc)
{
  struct buffer out = alloc_buf_gc (256, gc);
  if (tt->did_ifconfig_setup && !disable)
    {
      if (tt->type == DEV_TYPE_TUN)
	{
	  const char *l, *r;
	  if (remote)
	    {
	      r = print_in_addr_t (tt->local, 0, gc);
	      l = print_in_addr_t (tt->remote_netmask, 0, gc);
	    }
	  else
	    {
	      l = print_in_addr_t (tt->local, 0, gc);
	      r = print_in_addr_t (tt->remote_netmask, 0, gc);
	    }
	  buf_printf (&out, "%s %s", r, l);
	}
      else if (tt->type == DEV_TYPE_TAP)
	{
	  buf_printf (&out, "%s %s",
		      print_in_addr_t (tt->local & tt->remote_netmask, 0, gc),
		      print_in_addr_t (tt->remote_netmask, 0, gc));
	}
      else
	buf_printf (&out, "[undef]");
    }
  return BSTR (&out);
}

/*
 * Return a status string describing wait state.
 */
const char *
tun_stat (const struct tuntap *tt, unsigned int rwflags, struct gc_arena *gc)
{
  struct buffer out = alloc_buf_gc (64, gc);
  if (tt)
    {
      if (rwflags & EVENT_READ)
	{
	  buf_printf (&out, "T%s",
		      (tt->rwflags_debug & EVENT_READ) ? "R" : "r");
#ifdef WIN32
	  buf_printf (&out, "%s",
		      overlapped_io_state_ascii (&tt->reads));
#endif
	}
      if (rwflags & EVENT_WRITE)
	{
	  buf_printf (&out, "T%s",
		      (tt->rwflags_debug & EVENT_WRITE) ? "W" : "w");
#ifdef WIN32
	  buf_printf (&out, "%s",
		      overlapped_io_state_ascii (&tt->writes));
#endif
	}
    }
  else
    {
      buf_printf (&out, "T?");
    }
  return BSTR (&out);
}

/*
 * Init tun/tap object.
 *
 * Set up tuntap structure for ifconfig,
 * but don't execute yet.
 */
struct tuntap *
init_tun (const char *dev,       /* --dev option */
	  const char *dev_type,  /* --dev-type option */
	  const char *ifconfig_local_parm,          /* --ifconfig parm 1 */
	  const char *ifconfig_remote_netmask_parm, /* --ifconfig parm 2 */
	  in_addr_t local_public,
	  in_addr_t remote_public,
	  const bool strict_warn,
	  struct env_set *es)
{
  struct gc_arena gc = gc_new ();
  struct tuntap *tt;

  ALLOC_OBJ (tt, struct tuntap);
  clear_tuntap (tt);

  tt->type = dev_type_enum (dev, dev_type);

  if (ifconfig_local_parm && ifconfig_remote_netmask_parm)
    {
      bool tun = false;
      const char *ifconfig_local = NULL;
      const char *ifconfig_remote_netmask = NULL;
      const char *ifconfig_broadcast = NULL;

      /*
       * We only handle TUN/TAP devices here, not --dev null devices.
       */
      if (tt->type == DEV_TYPE_TUN)
	tun = true;
      else if (tt->type == DEV_TYPE_TAP)
	tun = false;
      else
	msg (M_FATAL, "'%s' is not a TUN/TAP device.  The --ifconfig option works only for TUN/TAP devices.", dev);

      /*
       * Convert arguments to binary IPv4 addresses.
       */

      tt->local = getaddr (
			   GETADDR_RESOLVE
			   | GETADDR_HOST_ORDER
			   | GETADDR_FATAL_ON_SIGNAL
			   | GETADDR_FATAL,
			   ifconfig_local_parm,
			   0,
			   NULL,
			   NULL);

      tt->remote_netmask = getaddr (
				    (tun ? GETADDR_RESOLVE : 0)
				    | GETADDR_HOST_ORDER
				    | GETADDR_FATAL_ON_SIGNAL
				    | GETADDR_FATAL,
				    ifconfig_remote_netmask_parm,
				    0,
				    NULL,
				    NULL);

      /*
       * Look for common errors in --ifconfig parms
       */
      if (strict_warn)
	{
	  ifconfig_sanity_check (tun, tt->remote_netmask);

	  /*
	   * If local_public or remote_public addresses are defined,
	   * make sure they do not clash with our virtual subnet.
	   */

	  check_addr_clash ("local",
			    tt->type,
			    local_public,
			    tt->local,
			    tt->remote_netmask);

	  check_addr_clash ("remote",
			    tt->type,
			    remote_public,
			    tt->local,
			    tt->remote_netmask);
	}

      /*
       * Set ifconfig parameters
       */
      ifconfig_local = print_in_addr_t (tt->local, 0, &gc);
      ifconfig_remote_netmask = print_in_addr_t (tt->remote_netmask, 0, &gc);

      /*
       * If TAP-style interface, generate broadcast address.
       */
      if (!tun)
	{
	  tt->broadcast = generate_ifconfig_broadcast_addr (tt->local, tt->remote_netmask);
	  ifconfig_broadcast = print_in_addr_t (tt->broadcast, 0, &gc);
	}

      /*
       * Set environmental variables with ifconfig parameters.
       */
      if (es)
	{
	  setenv_str (es, "ifconfig_local", ifconfig_local);
	  if (tun)
	    {
	      setenv_str (es, "ifconfig_remote", ifconfig_remote_netmask);
	    }
	  else
	    {
	      setenv_str (es, "ifconfig_netmask", ifconfig_remote_netmask);
	      setenv_str (es, "ifconfig_broadcast", ifconfig_broadcast);
	    }
	}

      tt->did_ifconfig_setup = true;
    }
  gc_free (&gc);
  return tt;
}

/*
 * Platform specific tun initializations
 */
void
init_tun_post (struct tuntap *tt,
	       const struct frame *frame,
	       const struct tuntap_options *options)
{
  tt->options = *options;
#ifdef WIN32
  overlapped_io_init (&tt->reads, frame, FALSE, true);
  overlapped_io_init (&tt->writes, frame, TRUE, true);
  tt->rw_handle.read = tt->reads.overlapped.hEvent;
  tt->rw_handle.write = tt->writes.overlapped.hEvent;
  tt->adapter_index = ~0;
#endif
}

/* execute the ifconfig command through the shell */
void
do_ifconfig (struct tuntap *tt,
	     const char *actual,    /* actual device name */
	     int tun_mtu,
	     const struct env_set *es)
{
  struct gc_arena gc = gc_new ();

  if (tt->did_ifconfig_setup)
    {
      bool tun = false;
      const char *ifconfig_local = NULL;
      const char *ifconfig_remote_netmask = NULL;
      const char *ifconfig_broadcast = NULL;
      char command_line[256];

      /*
       * We only handle TUN/TAP devices here, not --dev null devices.
       */
      if (tt->type == DEV_TYPE_TUN)
	tun = true;
      else if (tt->type == DEV_TYPE_TAP)
	tun = false;
      else
	ASSERT (0); /* should have been caught in init_tun */

      /*
       * Set ifconfig parameters
       */
      ifconfig_local = print_in_addr_t (tt->local, 0, &gc);
      ifconfig_remote_netmask = print_in_addr_t (tt->remote_netmask, 0, &gc);

      /*
       * If TAP-style device, generate broadcast address.
       */
      if (!tun)
	ifconfig_broadcast = print_in_addr_t (tt->broadcast, 0, &gc);

#ifdef ENABLE_MANAGEMENT
  if (management)
    {
      management_set_state (management,
			    OPENVPN_STATE_ASSIGN_IP,
			    NULL,
			    tt->local);
    }
#endif


#if defined(TARGET_LINUX)

#ifndef OOMA_OPENVPN
#ifdef CONFIG_FEATURE_IPROUTE 
	/*
	 * Set the MTU for the device
	 */
	openvpn_snprintf (command_line, sizeof (command_line),
			  IPROUTE_PATH " link set dev %s up mtu %d",
			  actual,
			  tun_mtu
			  );
	  msg (M_INFO, "%s", command_line);
	  system_check (command_line, es, S_FATAL, "Linux ip link set failed");

	if (tun) {

		/*
		 * Set the address for the device
		 */
		openvpn_snprintf (command_line, sizeof (command_line),
				  IPROUTE_PATH " addr add dev %s local %s peer %s",
				  actual,
				  ifconfig_local,
				  ifconfig_remote_netmask
				  );
		  msg (M_INFO, "%s", command_line);
		  system_check (command_line, es, S_FATAL, "Linux ip addr add failed");
	} else {
		openvpn_snprintf (command_line, sizeof (command_line),
				  IPROUTE_PATH " addr add dev %s %s/%d broadcast %s",
				  actual,
				  ifconfig_local,
				  count_netmask_bits(ifconfig_remote_netmask),
				  ifconfig_broadcast
				  );
		  msg (M_INFO, "%s", command_line);
		  system_check (command_line, es, S_FATAL, "Linux ip addr add failed");
	}
	tt->did_ifconfig = true;
#else
      if (tun)
	openvpn_snprintf (command_line, sizeof (command_line),
			  IFCONFIG_PATH " %s %s pointopoint %s mtu %d",
			  actual,
			  ifconfig_local,
			  ifconfig_remote_netmask,
			  tun_mtu
			  );
      else
	openvpn_snprintf (command_line, sizeof (command_line),
			  IFCONFIG_PATH " %s %s netmask %s mtu %d broadcast %s",
			  actual,
			  ifconfig_local,
			  ifconfig_remote_netmask,
			  tun_mtu,
			  ifconfig_broadcast
			  );
      msg (M_INFO, "%s", command_line);
      system_check (command_line, es, S_FATAL, "Linux ifconfig failed");
      tt->did_ifconfig = true;

#endif /*CONFIG_FEATURE_IPROUTE*/
#else /* OOMA_OPENVPN */
      tt->did_ifconfig = true;
#endif
      

#elif defined(TARGET_SOLARIS)

      /* Solaris 2.6 (and 7?) cannot set all parameters in one go...
       * example:
       *    ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 up
       *    ifconfig tun2 netmask 255.255.255.255
       */
      if (tun)
	{
	  openvpn_snprintf (command_line, sizeof (command_line),
			    IFCONFIG_PATH " %s %s %s mtu %d up",
			    actual,
			    ifconfig_local,
			    ifconfig_remote_netmask,
			    tun_mtu
			    );

	  msg (M_INFO, "%s", command_line);
	  if (!system_check (command_line, es, 0, "Solaris ifconfig phase-1 failed"))
	    solaris_error_close (tt, es, actual);

	  openvpn_snprintf (command_line, sizeof (command_line),
			    IFCONFIG_PATH " %s netmask 255.255.255.255",
			    actual
			    );
	}
      else
	no_tap_ifconfig ();

      msg (M_INFO, "%s", command_line);
      if (!system_check (command_line, es, 0, "Solaris ifconfig phase-2 failed"))
	solaris_error_close (tt, es, actual);

      tt->did_ifconfig = true;

#elif defined(TARGET_OPENBSD)

      /*
       * OpenBSD tun devices appear to be persistent by default.  It seems in order
       * to make this work correctly, we need to delete the previous instance
       * (if it exists), and re-ifconfig.  Let me know if you know a better way.
       */

      openvpn_snprintf (command_line, sizeof (command_line),
			IFCONFIG_PATH " %s destroy",
			actual);
      msg (M_INFO, "%s", command_line);
      system_check (command_line, es, 0, NULL);
      openvpn_snprintf (command_line, sizeof (command_line),
			IFCONFIG_PATH " %s create",
			actual);
      msg (M_INFO, "%s", command_line);
      system_check (command_line, es, 0, NULL);
      msg (M_INFO, "NOTE: Tried to delete pre-existing tun/tap instance -- No Problem if failure");

      /* example: ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 netmask 255.255.255.255 up */
      if (tun)
	openvpn_snprintf (command_line, sizeof (command_line),
			  IFCONFIG_PATH " %s %s %s mtu %d netmask 255.255.255.255 up",
			  actual,
			  ifconfig_local,
			  ifconfig_remote_netmask,
			  tun_mtu
			  );
      else
	openvpn_snprintf (command_line, sizeof (command_line),
			  IFCONFIG_PATH " %s %s netmask %s mtu %d broadcast %s link0",
			  actual,
			  ifconfig_local,
			  ifconfig_remote_netmask,
			  tun_mtu,
			  ifconfig_broadcast
			  );
      msg (M_INFO, "%s", command_line);
      system_check (command_line, es, S_FATAL, "OpenBSD ifconfig failed");
      tt->did_ifconfig = true;

#elif defined(TARGET_NETBSD)

      if (tun)
	openvpn_snprintf (command_line, sizeof (command_line),
			  IFCONFIG_PATH " %s %s %s mtu %d netmask 255.255.255.255 up",
			  actual,
			  ifconfig_local,
			  ifconfig_remote_netmask,
			  tun_mtu
			  );
      else
	no_tap_ifconfig ();
      msg (M_INFO, "%s", command_line);
      system_check (command_line, es, S_FATAL, "NetBSD ifconfig failed");
      tt->did_ifconfig = true;

#elif defined(TARGET_DARWIN)

      /*
       * Darwin (i.e. Mac OS X) seems to exhibit similar behaviour to OpenBSD...
       */

      openvpn_snprintf (command_line, sizeof (command_line),
			IFCONFIG_PATH " %s delete",
			actual);
      msg (M_INFO, "%s", command_line);
      system_check (command_line, es, 0, NULL);
      msg (M_INFO, "NOTE: Tried to delete pre-existing tun/tap instance -- No Problem if failure");


      /* example: ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 netmask 255.255.255.255 up */
      if (tun)
	openvpn_snprintf (command_line, sizeof (command_line),
			  IFCONFIG_PATH " %s %s %s mtu %d netmask 255.255.255.255 up",
			  actual,
			  ifconfig_local,
			  ifconfig_remote_netmask,
			  tun_mtu
			  );
      else
	openvpn_snprintf (command_line, sizeof (command_line),
			  IFCONFIG_PATH " %s %s netmask %s mtu %d up",
			  actual,
			  ifconfig_local,
			  ifconfig_remote_netmask,
			  tun_mtu
			  );

      msg (M_INFO, "%s", command_line);
      system_check (command_line, es, S_FATAL, "Mac OS X ifconfig failed");
      tt->did_ifconfig = true;

#elif defined(TARGET_FREEBSD)

      /* example: ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 netmask 255.255.255.255 up */
      if (tun)
	openvpn_snprintf (command_line, sizeof (command_line),
			  IFCONFIG_PATH " %s %s %s mtu %d netmask 255.255.255.255 up",
			  actual,
			  ifconfig_local,
			  ifconfig_remote_netmask,
			  tun_mtu
			  );
      else
	openvpn_snprintf (command_line, sizeof (command_line),
			  IFCONFIG_PATH " %s %s netmask %s mtu %d up",
			  actual,
			  ifconfig_local,
			  ifconfig_remote_netmask,
			  tun_mtu
			  );
	
      msg (M_INFO, "%s", command_line);
      system_check (command_line, es, S_FATAL, "FreeBSD ifconfig failed");
      tt->did_ifconfig = true;

#elif defined (WIN32)
      {
	const char *netmask;

	/*
	 * Make sure that both ifconfig addresses are part of the
	 * same .252 subnet.
	 */
	if (tun)
	  {
	    verify_255_255_255_252 (tt->local, tt->remote_netmask);
	    tt->adapter_netmask = ~3;
	    netmask = print_in_addr_t (tt->adapter_netmask, 0, &gc);
	  }
	else
	  {
	    netmask = ifconfig_remote_netmask;
	    tt->adapter_netmask = tt->remote_netmask;
	  }

	/* example: netsh interface ip set address my-tap static 10.3.0.1 255.255.255.0 */
	openvpn_snprintf (command_line, sizeof (command_line),
			  "netsh interface ip set address \"%s\" static %s %s",
			  actual,
			  ifconfig_local,
			  netmask);
	
	switch (tt->options.ip_win32_type)
	  {
	  case IPW32_SET_MANUAL:
	    msg (M_INFO, "******** NOTE:  Please manually set the IP/netmask of '%s' to %s/%s (if it is not already set)",
		 actual,
		 ifconfig_local,
		 netmask);
	    break;
	  case IPW32_SET_NETSH:
	    if (!strcmp (actual, "NULL"))
	      msg (M_FATAL, "Error: When using --ip-win32 netsh, if you have more than one TAP-Win32 adapter, you must also specify --dev-node");
	    netcmd_semaphore_lock ();
	    msg (M_INFO, "%s", command_line);
	    system_check (command_line, es, S_FATAL, "ERROR: netsh command failed");
	    netcmd_semaphore_release ();
	    break;
	  }
	tt->did_ifconfig = true;
      }

#else
      msg (M_FATAL, "Sorry, but I don't know how to do 'ifconfig' commands on this operating system.  You should ifconfig your TUN/TAP device manually or use an --up script.");
#endif
    }
  gc_free (&gc);
}

void
clear_tuntap (struct tuntap *tuntap)
{
  CLEAR (*tuntap);
#ifdef WIN32
  tuntap->hand = NULL;
#else
  tuntap->fd = -1;
#endif
#ifdef TARGET_SOLARIS
  tuntap->ip_fd = -1;
#endif
  tuntap->ipv6 = false;
}

static void
open_null (struct tuntap *tt)
{
  tt->actual_name = string_alloc ("null", NULL);
}

#ifndef WIN32
static void
open_tun_generic (const char *dev, const char *dev_type, const char *dev_node,
		  bool ipv6, bool ipv6_explicitly_supported, bool dynamic,
		  struct tuntap *tt)
{
  char tunname[256];
  char dynamic_name[256];
  bool dynamic_opened = false;

  ipv6_support (ipv6, ipv6_explicitly_supported, tt);

  if (tt->type == DEV_TYPE_NULL)
    {
      open_null (tt);
    }
  else
    {
      /*
       * --dev-node specified, so open an explicit device node
       */
      if (dev_node)
	{
	  openvpn_snprintf (tunname, sizeof (tunname), "%s", dev_node);
	}
      else
	{
	  /*
	   * dynamic open is indicated by --dev specified without
	   * explicit unit number.  Try opening /dev/[dev]n
	   * where n = [0, 255].
	   */
	  if (dynamic && !has_digit(dev))
	    {
	      int i;
	      for (i = 0; i < 256; ++i)
		{
		  openvpn_snprintf (tunname, sizeof (tunname),
				    "/dev/%s%d", dev, i);
		  openvpn_snprintf (dynamic_name, sizeof (dynamic_name),
				    "%s%d", dev, i);
		  if ((tt->fd = open (tunname, O_RDWR)) > 0)
		    {
		      dynamic_opened = true;
		      break;
		    }
		  msg (D_READ_WRITE | M_ERRNO, "Tried opening %s (failed)", tunname);
		}
	      if (!dynamic_opened)
		msg (M_FATAL, "Cannot allocate TUN/TAP dev dynamically");
	    }
	  /*
	   * explicit unit number specified
	   */
	  else
	    {
	      openvpn_snprintf (tunname, sizeof (tunname), "/dev/%s", dev);
	    }
	}

      if (!dynamic_opened)
	{
	  if ((tt->fd = open (tunname, O_RDWR)) < 0)
	    msg (M_ERR, "Cannot open TUN/TAP dev %s", tunname);
	}

      set_nonblock (tt->fd);
      set_cloexec (tt->fd); /* don't pass fd to scripts */
      msg (M_INFO, "TUN/TAP device %s opened", tunname);

      /* tt->actual_name is passed to up and down scripts and used as the ifconfig dev name */
      tt->actual_name = string_alloc (dynamic_opened ? dynamic_name : dev, NULL);
    }
}

static void
close_tun_generic (struct tuntap *tt)
{
  if (tt->fd >= 0)
    close (tt->fd);
  if (tt->actual_name)
    free (tt->actual_name);
  clear_tuntap (tt);
}

#endif

#if defined(TARGET_LINUX)

#ifdef HAVE_LINUX_IF_TUN_H	/* New driver support */

#ifndef HAVE_LINUX_SOCKIOS_H
#error header file linux/sockios.h required
#endif

#if defined(HAVE_TUN_PI) && defined(HAVE_IPHDR) && defined(HAVE_IOVEC) && defined(ETH_P_IPV6) && defined(ETH_P_IP) && defined(HAVE_READV) && defined(HAVE_WRITEV)
#define LINUX_IPV6 1
/* #warning IPv6 ON */
#else
#define LINUX_IPV6 0
/* #warning IPv6 OFF */
#endif

#if !PEDANTIC

void
open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt)
{
  struct ifreq ifr;

  /*
   * Set tt->ipv6 to true if
   * (a) we have the capability of supporting --tun-ipv6, and
   * (b) --tun-ipv6 was specified.
   */
  ipv6_support (ipv6, LINUX_IPV6, tt);

  /*
   * We handle --dev null specially, we do not open /dev/null for this.
   */
  if (tt->type == DEV_TYPE_NULL)
    {
      open_null (tt);
    }
  else
    {
      /*
       * Process --dev-node
       */
      const char *node = dev_node;
      if (!node)
	node = "/dev/net/tun";

      /*
       * Open the interface
       */
      if ((tt->fd = open (node, O_RDWR)) < 0)
	{
	  msg (M_WARN | M_ERRNO, "Note: Cannot open TUN/TAP dev %s", node);
	  goto linux_2_2_fallback;
	}

      /*
       * Process --tun-ipv6
       */
      CLEAR (ifr);
      if (!tt->ipv6)
	ifr.ifr_flags = IFF_NO_PI;

#if defined(IFF_ONE_QUEUE) && defined(SIOCSIFTXQLEN)
      ifr.ifr_flags |= IFF_ONE_QUEUE;
#endif

      /*
       * Figure out if tun or tap device
       */
      if (tt->type == DEV_TYPE_TUN)
	{
	  ifr.ifr_flags |= IFF_TUN;
	}
      else if (tt->type == DEV_TYPE_TAP)
	{
	  ifr.ifr_flags |= IFF_TAP;
	}
      else
	{
	  msg (M_FATAL, "I don't recognize device %s as a tun or tap device",
	       dev);
	}

      /*
       * Set an explicit name, if --dev is not tun or tap
       */
      if (strcmp(dev, "tun") && strcmp(dev, "tap"))
	strncpynt (ifr.ifr_name, dev, IFNAMSIZ);

      /*
       * Use special ioctl that configures tun/tap device with the parms
       * we set in ifr
       */
      if (ioctl (tt->fd, TUNSETIFF, (void *) &ifr) < 0)
	{
	  msg (M_WARN | M_ERRNO, "Note: Cannot ioctl TUNSETIFF %s", dev);
	  goto linux_2_2_fallback;
	}

      msg (M_INFO, "TUN/TAP device %s opened", ifr.ifr_name);

      /*
       * Try making the TX send queue bigger
       */
#if defined(IFF_ONE_QUEUE) && defined(SIOCSIFTXQLEN)
      {
	struct ifreq netifr;
	int ctl_fd;

	if ((ctl_fd = socket (AF_INET, SOCK_DGRAM, 0)) >= 0)
	  {
	    CLEAR (netifr);
	    strncpynt (netifr.ifr_name, ifr.ifr_name, IFNAMSIZ);
	    netifr.ifr_qlen = tt->options.txqueuelen;
	    if (ioctl (ctl_fd, SIOCSIFTXQLEN, (void *) &netifr) >= 0)
	      msg (D_OSBUF, "TUN/TAP TX queue length set to %d", tt->options.txqueuelen);
	    else
	      msg (M_WARN | M_ERRNO, "Note: Cannot set tx queue length on %s", ifr.ifr_name);
	    close (ctl_fd);
	  }
	else
	  {
	    msg (M_WARN | M_ERRNO, "Note: Cannot open control socket on %s", ifr.ifr_name);
	  }
      }
#endif

      set_nonblock (tt->fd);
      set_cloexec (tt->fd);
      tt->actual_name = string_alloc (ifr.ifr_name, NULL);
    }
  return;

 linux_2_2_fallback:
  msg (M_INFO, "Note: Attempting fallback to kernel 2.2 TUN/TAP interface");
  if (tt->fd >= 0)
    {
      close (tt->fd);
      tt->fd = -1;
    }
  open_tun_generic (dev, dev_type, dev_node, ipv6, false, true, tt);
}

#else

void
open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt)
{
  ASSERT (0);
}

#endif

#else

void
open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt)
{
  open_tun_generic (dev, dev_type, dev_node, ipv6, false, true, tt);
}

#endif /* HAVE_LINUX_IF_TUN_H */

#ifdef TUNSETPERSIST

void
tuncfg (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, int persist_mode)
{
  struct tuntap *tt;

  ALLOC_OBJ (tt, struct tuntap);
  clear_tuntap (tt);
  tt->type = dev_type_enum (dev, dev_type);
  open_tun (dev, dev_type, dev_node, ipv6, tt);
  if (ioctl (tt->fd, TUNSETPERSIST, persist_mode) < 0)
    msg (M_ERR, "Cannot ioctl TUNSETPERSIST(%d) %s", persist_mode, dev);
  close_tun (tt);
  msg (M_INFO, "Persist state set to: %s", (persist_mode ? "ON" : "OFF"));
}

#endif /* TUNSETPERSIST */

void
close_tun (struct tuntap *tt)
{
  if (tt)
    {
      close_tun_generic (tt);
      free (tt);
    }
}

int
write_tun (struct tuntap* tt, uint8_t *buf, int len)
{
#if LINUX_IPV6
  if (tt->ipv6)
    {
      struct tun_pi pi;
      struct iphdr *iph;
      struct iovec vect[2];
      int ret;

      iph = (struct iphdr *)buf;

      pi.flags = 0;

      if(iph->version == 6)
	pi.proto = htons(ETH_P_IPV6);
      else
	pi.proto = htons(ETH_P_IP);

      vect[0].iov_len = sizeof(pi);
      vect[0].iov_base = &pi;
      vect[1].iov_len = len;
      vect[1].iov_base = buf;

      ret = writev(tt->fd, vect, 2);
      return(ret - sizeof(pi));
    }
  else
#endif
    return write (tt->fd, buf, len);
}

int
read_tun (struct tuntap* tt, uint8_t *buf, int len)
{
#if LINUX_IPV6
  if (tt->ipv6)
    {
      struct iovec vect[2];
      struct tun_pi pi;
      int ret;

      vect[0].iov_len = sizeof(pi);
      vect[0].iov_base = &pi;
      vect[1].iov_len = len;
      vect[1].iov_base = buf;

      ret = readv(tt->fd, vect, 2);
      return(ret - sizeof(pi));
    }
  else
#endif
    return read (tt->fd, buf, len);
}

#elif defined(TARGET_SOLARIS)

#ifndef TUNNEWPPA
#error I need the symbol TUNNEWPPA from net/if_tun.h
#endif

void
open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt)
{
  int if_fd, muxid, ppa = -1;
  struct ifreq ifr;
  const char *ptr;
  const char *ip_node;
  const char *dev_tuntap_type;
  int link_type;
  bool is_tun;

  ipv6_support (ipv6, false, tt);

  if (tt->type == DEV_TYPE_NULL)
    {
      open_null (tt);
      return;
    }

  if (tt->type == DEV_TYPE_TUN)
    {
      ip_node = "/dev/udp";
      if (!dev_node)
	dev_node = "/dev/tun";
      dev_tuntap_type = "tun";
      link_type = I_PLINK;
      is_tun = true;
    }
  else if (tt->type == DEV_TYPE_TAP)
    {
      ip_node = "/dev/ip";
      if (!dev_node)
	dev_node = "/dev/tap";
      dev_tuntap_type = "tap";
      link_type = I_PLINK; /* was: I_LINK */
      is_tun = false;
    }
  else
    {
      msg (M_FATAL, "I don't recognize device %s as a tun or tap device",
	   dev);
    }
  
  /* get unit number */
  if (*dev)
    {
      ptr = dev;
      while (*ptr && !isdigit ((int) *ptr))
	ptr++;
      ppa = atoi (ptr);
    }

  if ((tt->ip_fd = open (ip_node, O_RDWR, 0)) < 0)
    msg (M_ERR, "Can't open %s", ip_node);

  if ((tt->fd = open (dev_node, O_RDWR, 0)) < 0)
    msg (M_ERR, "Can't open %s", dev_node);

  /* Assign a new PPA and get its unit number. */
  if ((ppa = ioctl (tt->fd, TUNNEWPPA, ppa)) < 0)
    msg (M_ERR, "Can't assign new interface");

  if ((if_fd = open (dev_node, O_RDWR, 0)) < 0)
    msg (M_ERR, "Can't open %s (2)", dev_node);

  if (ioctl (if_fd, I_PUSH, "ip") < 0)
    msg (M_ERR, "Can't push IP module");

  /* Assign ppa according to the unit number returned by tun device */
  if (ioctl (if_fd, IF_UNITSEL, (char *) &ppa) < 0)
    msg (M_ERR, "Can't set PPA %d", ppa);

  if ((muxid = ioctl (tt->ip_fd, link_type, if_fd)) < 0)
    msg (M_ERR, "Can't link %s device to IP", dev_tuntap_type);

  close (if_fd);

  tt->actual_name = (char *) malloc (32);
  check_malloc_return (tt->actual_name);

  openvpn_snprintf (tt->actual_name, 32, "%s%d", dev_tuntap_type, ppa);

  CLEAR (ifr);
  strncpynt (ifr.ifr_name, tt->actual_name, sizeof (ifr.ifr_name));
  ifr.ifr_ip_muxid = muxid;

  if (ioctl (tt->ip_fd, SIOCSIFMUXID, &ifr) < 0)
    {
      ioctl (tt->ip_fd, I_PUNLINK, muxid);
      msg (M_ERR, "Can't set multiplexor id");
    }

  set_nonblock (tt->fd);
  set_cloexec (tt->fd);
  set_cloexec (tt->ip_fd);

  msg (M_INFO, "TUN/TAP device %s opened", tt->actual_name);
}

static void
solaris_close_tun (struct tuntap *tt)
{
  if (tt)
    {
      if (tt->ip_fd >= 0)
	{
	  struct ifreq ifr;
	  CLEAR (ifr);
	  strncpynt (ifr.ifr_name, tt->actual_name, sizeof (ifr.ifr_name));

	  if (ioctl (tt->ip_fd, SIOCGIFFLAGS, &ifr) < 0)
	    msg (M_WARN | M_ERRNO, "Can't get iface flags");

	  if (ioctl (tt->ip_fd, SIOCGIFMUXID, &ifr) < 0)
	    msg (M_WARN | M_ERRNO, "Can't get multiplexor id");

	  if (ioctl (tt->ip_fd, I_PUNLINK, ifr.ifr_ip_muxid) < 0)
	    msg (M_WARN | M_ERRNO, "Can't unlink interface");

	  close (tt->ip_fd);
	  tt->ip_fd = -1;
	}

      if (tt->fd >= 0)
	{
	  close (tt->fd);
	  tt->fd = -1;
	}
    }
}

/*
 * Close TUN device. 
 */
void
close_tun (struct tuntap *tt)
{
  if (tt)
    {
      solaris_close_tun (tt);

      if (tt->actual_name)
	free (tt->actual_name);
      
      clear_tuntap (tt);
      free (tt);
    }
}

static void
solaris_error_close (struct tuntap *tt, const struct env_set *es, const char *actual)
{
  char command_line[256];

  openvpn_snprintf (command_line, sizeof (command_line),
		    IFCONFIG_PATH " %s unplumb",
		    actual);

  msg (M_INFO, "%s", command_line);
  system_check (command_line, es, 0, "Solaris ifconfig unplumb failed");
  close_tun (tt);
  msg (M_FATAL, "Solaris ifconfig failed");
}

int
write_tun (struct tuntap* tt, uint8_t *buf, int len)
{
  struct strbuf sbuf;
  sbuf.len = len;
  sbuf.buf = (char *)buf;
  return putmsg (tt->fd, NULL, &sbuf, 0) >= 0 ? sbuf.len : -1;
}

int
read_tun (struct tuntap* tt, uint8_t *buf, int len)
{
  struct strbuf sbuf;
  int f = 0;

  sbuf.maxlen = len;
  sbuf.buf = (char *)buf;
  return getmsg (tt->fd, NULL, &sbuf, &f) >= 0 ? sbuf.len : -1;
}

#elif defined(TARGET_OPENBSD)

#if !defined(HAVE_READV) || !defined(HAVE_WRITEV)
#error openbsd build requires readv & writev library functions
#endif

/*
 * OpenBSD has a slightly incompatible TUN device from
 * the rest of the world, in that it prepends a
 * uint32 to the beginning of the IP header
 * to designate the protocol (why not just
 * look at the version field in the IP header to
 * determine v4 or v6?).
 *
 * We strip off this field on reads and
 * put it back on writes.
 *
 * I have not tested TAP devices on OpenBSD,
 * but I have conditionalized the special
 * TUN handling code described above to
 * go away for TAP devices.
 */

void
open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt)
{
  open_tun_generic (dev, dev_type, dev_node, ipv6, true, true, tt);

  /* Enable multicast on the interface */
  if (tt->fd >= 0)
    {
      struct tuninfo info;

      if (ioctl (tt->fd, TUNGIFINFO, &info) < 0) {
	msg (M_WARN | M_ERRNO, "Can't get interface info: %s",
	  strerror(errno));
      }

      info.flags |= IFF_MULTICAST;

      if (ioctl (tt->fd, TUNSIFINFO, &info) < 0) {
	msg (M_WARN | M_ERRNO, "Can't set interface info: %s",
	  strerror(errno));
      }
    }
}

void
close_tun (struct tuntap* tt)
{
  if (tt)
    {
      close_tun_generic (tt);
      free (tt);
    }
}

static inline int
openbsd_modify_read_write_return (int len)
{
 if (len > 0)
    return len > sizeof (u_int32_t) ? len - sizeof (u_int32_t) : 0;
  else
    return len;
}

int
write_tun (struct tuntap* tt, uint8_t *buf, int len)
{
  if (tt->type == DEV_TYPE_TUN)
    {
      u_int32_t type;
      struct iovec iv[2];
      struct ip *iph;

      iph = (struct ip *) buf;

      if (tt->ipv6 && iph->ip_v == 6)
	type = htonl (AF_INET6);
      else 
	type = htonl (AF_INET);

      iv[0].iov_base = &type;
      iv[0].iov_len = sizeof (type);
      iv[1].iov_base = buf;
      iv[1].iov_len = len;

      return openbsd_modify_read_write_return (writev (tt->fd, iv, 2));
    }
  else
    return write (tt->fd, buf, len);
}

int
read_tun (struct tuntap* tt, uint8_t *buf, int len)
{
  if (tt->type == DEV_TYPE_TUN)
    {
      u_int32_t type;
      struct iovec iv[2];

      iv[0].iov_base = &type;
      iv[0].iov_len = sizeof (type);
      iv[1].iov_base = buf;
      iv[1].iov_len = len;

      return openbsd_modify_read_write_return (readv (tt->fd, iv, 2));
    }
  else
    return read (tt->fd, buf, len);
}

#elif defined(TARGET_NETBSD)

/*
 * NetBSD does not support IPv6 on tun out of the box,
 * but there exists a patch. When this patch is applied,
 * only two things are left to openvpn:
 * 1. Activate multicasting (this has already been done
 *    before by the kernel, but we make sure that nobody
 *    has deactivated multicasting inbetween.
 * 2. Deactivate "link layer mode" (otherwise NetBSD 
 *    prepends the address family to the packet, and we
 *    would run into the same trouble as with OpenBSD.
 */

void
open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt)
{
    open_tun_generic (dev, dev_type, dev_node, ipv6, true, true, tt);
    if (tt->fd >= 0)
      {
        int i = IFF_POINTOPOINT|IFF_MULTICAST;
        ioctl (tt->fd, TUNSIFMODE, &i);  /* multicast on */
        i = 0;
        ioctl (tt->fd, TUNSLMODE, &i);   /* link layer mode off */
      }
}

void
close_tun (struct tuntap *tt)
{
  if (tt)
    {
      close_tun_generic (tt);
      free (tt);
    }
}

int
write_tun (struct tuntap* tt, uint8_t *buf, int len)
{
    return write (tt->fd, buf, len);
}

int
read_tun (struct tuntap* tt, uint8_t *buf, int len)
{
    return read (tt->fd, buf, len);
}

#elif defined(TARGET_FREEBSD)

static inline int
freebsd_modify_read_write_return (int len)
{
  if (len > 0)
    return len > sizeof (u_int32_t) ? len - sizeof (u_int32_t) : 0;
  else
    return len;
}

void
open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt)
{
  open_tun_generic (dev, dev_type, dev_node, ipv6, true, true, tt);

  if (tt->fd >= 0)
    {
      int i = 0;

      /* Disable extended modes */
      ioctl (tt->fd, TUNSLMODE, &i);
      i = 1;
      ioctl (tt->fd, TUNSIFHEAD, &i);
    }
}

void
close_tun (struct tuntap *tt)
{
  if (tt)
    {
      close_tun_generic (tt);
      free (tt);
    }
}

int
write_tun (struct tuntap* tt, uint8_t *buf, int len)
{
  if (tt->type == DEV_TYPE_TUN)
    {
      u_int32_t type;
      struct iovec iv[2];
      struct ip *iph;

      iph = (struct ip *) buf;

      if (tt->ipv6 && iph->ip_v == 6)
        type = htonl (AF_INET6);
      else 
        type = htonl (AF_INET);

      iv[0].iov_base = (char *)&type;
      iv[0].iov_len = sizeof (type);
      iv[1].iov_base = buf;
      iv[1].iov_len = len;

      return freebsd_modify_read_write_return (writev (tt->fd, iv, 2));
    }
  else
    return write (tt->fd, buf, len);
}

int
read_tun (struct tuntap* tt, uint8_t *buf, int len)
{
  if (tt->type == DEV_TYPE_TUN)
    {
      u_int32_t type;
      struct iovec iv[2];

      iv[0].iov_base = (char *)&type;
      iv[0].iov_len = sizeof (type);
      iv[1].iov_base = buf;
      iv[1].iov_len = len;

      return freebsd_modify_read_write_return (readv (tt->fd, iv, 2));
    }
  else
    return read (tt->fd, buf, len);
}

#elif defined(WIN32)

int
tun_read_queue (struct tuntap *tt, int maxsize)
{
  if (tt->reads.iostate == IOSTATE_INITIAL)
    {
      DWORD len;
      BOOL status;
      int err;

      /* reset buf to its initial state */
      tt->reads.buf = tt->reads.buf_init;

      len = maxsize ? maxsize : BLEN (&tt->reads.buf);
      ASSERT (len <= BLEN (&tt->reads.buf));

      /* the overlapped read will signal this event on I/O completion */
      ASSERT (ResetEvent (tt->reads.overlapped.hEvent));

      status = ReadFile(
		      tt->hand,
		      BPTR (&tt->reads.buf),
		      len,
		      &tt->reads.size,
		      &tt->reads.overlapped
		      );

      if (status) /* operation completed immediately? */
	{
	  /* since we got an immediate return, we must signal the event object ourselves */
	  ASSERT (SetEvent (tt->reads.overlapped.hEvent));

	  tt->reads.iostate = IOSTATE_IMMEDIATE_RETURN;
	  tt->reads.status = 0;

	  dmsg (D_WIN32_IO, "WIN32 I/O: TAP Read immediate return [%d,%d]",
	       (int) len,
	       (int) tt->reads.size);	       
	}
      else
	{
	  err = GetLastError (); 
	  if (err == ERROR_IO_PENDING) /* operation queued? */
	    {
	      tt->reads.iostate = IOSTATE_QUEUED;
	      tt->reads.status = err;
	      dmsg (D_WIN32_IO, "WIN32 I/O: TAP Read queued [%d]",
		   (int) len);
	    }
	  else /* error occurred */
	    {
	      struct gc_arena gc = gc_new ();
	      ASSERT (SetEvent (tt->reads.overlapped.hEvent));
	      tt->reads.iostate = IOSTATE_IMMEDIATE_RETURN;
	      tt->reads.status = err;
	      dmsg (D_WIN32_IO, "WIN32 I/O: TAP Read error [%d] : %s",
		   (int) len,
		   strerror_win32 (status, &gc));
	      gc_free (&gc);
	    }
	}
    }
  return tt->reads.iostate;
}

int
tun_write_queue (struct tuntap *tt, struct buffer *buf)
{
  if (tt->writes.iostate == IOSTATE_INITIAL)
    {
      BOOL status;
      int err;
 
      /* make a private copy of buf */
      tt->writes.buf = tt->writes.buf_init;
      tt->writes.buf.len = 0;
      ASSERT (buf_copy (&tt->writes.buf, buf));

      /* the overlapped write will signal this event on I/O completion */
      ASSERT (ResetEvent (tt->writes.overlapped.hEvent));

      status = WriteFile(
			tt->hand,
			BPTR (&tt->writes.buf),
			BLEN (&tt->writes.buf),
			&tt->writes.size,
			&tt->writes.overlapped
			);

      if (status) /* operation completed immediately? */
	{
	  tt->writes.iostate = IOSTATE_IMMEDIATE_RETURN;

	  /* since we got an immediate return, we must signal the event object ourselves */
	  ASSERT (SetEvent (tt->writes.overlapped.hEvent));

	  tt->writes.status = 0;

	  dmsg (D_WIN32_IO, "WIN32 I/O: TAP Write immediate return [%d,%d]",
	       BLEN (&tt->writes.buf),
	       (int) tt->writes.size);	       
	}
      else
	{
	  err = GetLastError (); 
	  if (err == ERROR_IO_PENDING) /* operation queued? */
	    {
	      tt->writes.iostate = IOSTATE_QUEUED;
	      tt->writes.status = err;
	      dmsg (D_WIN32_IO, "WIN32 I/O: TAP Write queued [%d]",
		   BLEN (&tt->writes.buf));
	    }
	  else /* error occurred */
	    {
	      struct gc_arena gc = gc_new ();
	      ASSERT (SetEvent (tt->writes.overlapped.hEvent));
	      tt->writes.iostate = IOSTATE_IMMEDIATE_RETURN;
	      tt->writes.status = err;
	      dmsg (D_WIN32_IO, "WIN32 I/O: TAP Write error [%d] : %s",
		   BLEN (&tt->writes.buf),
		   strerror_win32 (err, &gc));
	      gc_free (&gc);
	    }
	}
    }
  return tt->writes.iostate;
}

int
tun_finalize (
	      HANDLE h,
	      struct overlapped_io *io,
	      struct buffer *buf)
{
  int ret = -1;
  BOOL status;

  switch (io->iostate)
    {
    case IOSTATE_QUEUED:
      status = GetOverlappedResult(
				   h,
				   &io->overlapped,
				   &io->size,
				   FALSE
				   );
      if (status)
	{
	  /* successful return for a queued operation */
	  if (buf)
	    *buf = io->buf;
	  ret = io->size;
	  io->iostate = IOSTATE_INITIAL;
	  ASSERT (ResetEvent (io->overlapped.hEvent));
	  dmsg (D_WIN32_IO, "WIN32 I/O: TAP Completion success [%d]", ret);
	}
      else
	{
	  /* error during a queued operation */
	  ret = -1;
	  if (GetLastError() != ERROR_IO_INCOMPLETE)
	    {
	      /* if no error (i.e. just not finished yet),
		 then DON'T execute this code */
	      io->iostate = IOSTATE_INITIAL;
	      ASSERT (ResetEvent (io->overlapped.hEvent));
	      msg (D_WIN32_IO | M_ERRNO, "WIN32 I/O: TAP Completion error");
	    }
	}
      break;

    case IOSTATE_IMMEDIATE_RETURN:
      io->iostate = IOSTATE_INITIAL;
      ASSERT (ResetEvent (io->overlapped.hEvent));
      if (io->status)
	{
	  /* error return for a non-queued operation */
	  SetLastError (io->status);
	  ret = -1;
	  msg (D_WIN32_IO | M_ERRNO, "WIN32 I/O: TAP Completion non-queued error");
	}
      else
	{
	  /* successful return for a non-queued operation */
	  if (buf)
	    *buf = io->buf;
	  ret = io->size;
	  dmsg (D_WIN32_IO, "WIN32 I/O: TAP Completion non-queued success [%d]", ret);
	}
      break;

    case IOSTATE_INITIAL: /* were we called without proper queueing? */
      SetLastError (ERROR_INVALID_FUNCTION);
      ret = -1;
      dmsg (D_WIN32_IO, "WIN32 I/O: TAP Completion BAD STATE");
      break;

    default:
      ASSERT (0);
    }

  if (buf)
    buf->len = ret;
  return ret;
}

const struct tap_reg *
get_tap_reg (struct gc_arena *gc)
{
  HKEY adapter_key;
  LONG status;
  DWORD len;
  struct tap_reg *first = NULL;
  struct tap_reg *last = NULL;
  int i = 0;

  status = RegOpenKeyEx(
			HKEY_LOCAL_MACHINE,
			ADAPTER_KEY,
			0,
			KEY_READ,
			&adapter_key);

  if (status != ERROR_SUCCESS)
    msg (M_FATAL, "Error opening registry key: %s", ADAPTER_KEY);

  while (true)
    {
      char enum_name[256];
      char unit_string[256];
      HKEY unit_key;
      char component_id_string[] = "ComponentId";
      char component_id[256];
      char net_cfg_instance_id_string[] = "NetCfgInstanceId";
      char net_cfg_instance_id[256];
      DWORD data_type;

      len = sizeof (enum_name);
      status = RegEnumKeyEx(
			    adapter_key,
			    i,
			    enum_name,
			    &len,
			    NULL,
			    NULL,
			    NULL,
			    NULL);
      if (status == ERROR_NO_MORE_ITEMS)
	break;
      else if (status != ERROR_SUCCESS)
	msg (M_FATAL, "Error enumerating registry subkeys of key: %s",
	     ADAPTER_KEY);

      openvpn_snprintf (unit_string, sizeof(unit_string), "%s\\%s",
			ADAPTER_KEY, enum_name);

      status = RegOpenKeyEx(
			    HKEY_LOCAL_MACHINE,
			    unit_string,
			    0,
			    KEY_READ,
			    &unit_key);

      if (status != ERROR_SUCCESS)
	dmsg (D_REGISTRY, "Error opening registry key: %s", unit_string);
      else
	{
	  len = sizeof (component_id);
	  status = RegQueryValueEx(
				   unit_key,
				   component_id_string,
				   NULL,
				   &data_type,
				   component_id,
				   &len);

	  if (status != ERROR_SUCCESS || data_type != REG_SZ)
	    dmsg (D_REGISTRY, "Error opening registry key: %s\\%s",
		 unit_string, component_id_string);
	  else
	    {	      
	      len = sizeof (net_cfg_instance_id);
	      status = RegQueryValueEx(
				       unit_key,
				       net_cfg_instance_id_string,
				       NULL,
				       &data_type,
				       net_cfg_instance_id,
				       &len);

	      if (status == ERROR_SUCCESS && data_type == REG_SZ)
		{
		  if (!strcmp (component_id, TAP_COMPONENT_ID))
		    {
		      struct tap_reg *reg;
		      ALLOC_OBJ_CLEAR_GC (reg, struct tap_reg, gc);
		      reg->guid = string_alloc (net_cfg_instance_id, gc);
		      
		      /* link into return list */
		      if (!first)
			first = reg;
		      if (last)
			last->next = reg;
		      last = reg;
		    }
		}
	    }
	  RegCloseKey (unit_key);
	}
      ++i;
    }

  RegCloseKey (adapter_key);
  return first;
}

const struct panel_reg *
get_panel_reg (struct gc_arena *gc)
{
  LONG status;
  HKEY network_connections_key;
  DWORD len;
  struct panel_reg *first = NULL;
  struct panel_reg *last = NULL;
  int i = 0;

  status = RegOpenKeyEx(
			HKEY_LOCAL_MACHINE,
			NETWORK_CONNECTIONS_KEY,
			0,
			KEY_READ,
			&network_connections_key);

  if (status != ERROR_SUCCESS)
    msg (M_FATAL, "Error opening registry key: %s", NETWORK_CONNECTIONS_KEY);

  while (true)
    {
      char enum_name[256];
      char connection_string[256];
      HKEY connection_key;
      char name_data[256];
      DWORD name_type;
      const char name_string[] = "Name";

      len = sizeof (enum_name);
      status = RegEnumKeyEx(
			    network_connections_key,
			    i,
			    enum_name,
			    &len,
			    NULL,
			    NULL,
			    NULL,
			    NULL);
      if (status == ERROR_NO_MORE_ITEMS)
	break;
      else if (status != ERROR_SUCCESS)
	msg (M_FATAL, "Error enumerating registry subkeys of key: %s",
	     NETWORK_CONNECTIONS_KEY);

      openvpn_snprintf (connection_string, sizeof(connection_string),
			"%s\\%s\\Connection",
			NETWORK_CONNECTIONS_KEY, enum_name);

      status = RegOpenKeyEx(
			    HKEY_LOCAL_MACHINE,
			    connection_string,
			    0,
			    KEY_READ,
			    &connection_key);

      if (status != ERROR_SUCCESS)
	dmsg (D_REGISTRY, "Error opening registry key: %s", connection_string);
      else
	{
	  len = sizeof (name_data);
	  status = RegQueryValueEx(
				   connection_key,
				   name_string,
				   NULL,
				   &name_type,
				   name_data,
				   &len);

	  if (status != ERROR_SUCCESS || name_type != REG_SZ)
	    dmsg (D_REGISTRY, "Error opening registry key: %s\\%s\\%s",
		 NETWORK_CONNECTIONS_KEY, connection_string, name_string);
	  else
	    {
	      struct panel_reg *reg;

	      ALLOC_OBJ_CLEAR_GC (reg, struct panel_reg, gc);
	      reg->name = string_alloc (name_data, gc);
	      reg->guid = string_alloc (enum_name, gc);
		      
	      /* link into return list */
	      if (!first)
		first = reg;
	      if (last)
		last->next = reg;
	      last = reg;
	    }
	  RegCloseKey (connection_key);
	}
      ++i;
    }

  RegCloseKey (network_connections_key);

  return first;
}

/*
 * Check that two addresses are part of the same 255.255.255.252 subnet.
 */
void
verify_255_255_255_252 (in_addr_t local, in_addr_t remote)
{
  struct gc_arena gc = gc_new ();
  const unsigned int mask = 3;
  const char *err = NULL;

  if (local == remote)
    {
      err = "must be different";
      goto error;
    }
  if ((local & (~mask)) != (remote & (~mask)))
    {
      err = "must exist within the same 255.255.255.252 subnet.  This is a limitation of --dev tun when used with the TAP-WIN32 driver";
      goto error;
    }
  if ((local & mask) == 0
      || (local & mask) == 3
      || (remote & mask) == 0
      || (remote & mask) == 3)
    {
      err = "cannot use the first or last address within a given 255.255.255.252 subnet.  This is a limitation of --dev tun when used with the TAP-WIN32 driver";
      goto error;
    }

  gc_free (&gc);
  return;

 error:
  msg (M_FATAL, "There is a problem in your selection of --ifconfig endpoints [local=%s, remote=%s].  The local and remote VPN endpoints %s.  Try '" PACKAGE " --show-valid-subnets' option for more info.",
       print_in_addr_t (local, 0, &gc),
       print_in_addr_t (remote, 0, &gc),
       err);
  gc_free (&gc);
}

void show_valid_win32_tun_subnets (void)
{
  int i;
  int col = 0;

  printf ("On Windows, point-to-point IP support (i.e. --dev tun)\n");
  printf ("is emulated by the TAP-Win32 driver.  The major limitation\n");
  printf ("imposed by this approach is that the --ifconfig local and\n");
  printf ("remote endpoints must be part of the same 255.255.255.252\n");
  printf ("subnet.  The following list shows examples of endpoint\n");
  printf ("pairs which satisfy this requirement.  Only the final\n");
  printf ("component of the IP address pairs is at issue.\n\n");
  printf ("As an example, the following option would be correct:\n");
  printf ("    --ifconfig 10.7.0.5 10.7.0.6 (on host A)\n");
  printf ("    --ifconfig 10.7.0.6 10.7.0.5 (on host B)\n");
  printf ("because [5,6] is part of the below list.\n\n");

  for (i = 0; i < 256; i += 4)
    {
      printf("[%3d,%3d] ", i+1, i+2);
      if (++col > 4)
	{
	  col = 0;
	  printf ("\n");
	}
    }
  if (col)
    printf ("\n");
}

void
show_tap_win32_adapters (int msglev, int warnlev)
{
  struct gc_arena gc = gc_new ();

  bool warn_panel_null = false;
  bool warn_panel_dup = false;
  bool warn_tap_dup = false;

  int links;

  const struct tap_reg *tr;
  const struct tap_reg *tr1;
  const struct panel_reg *pr;

  const struct tap_reg *tap_reg = get_tap_reg (&gc);
  const struct panel_reg *panel_reg = get_panel_reg (&gc);

  msg (msglev, "Available TAP-WIN32 adapters [name, GUID]:");

  /* loop through each TAP-Win32 adapter registry entry */
  for (tr = tap_reg; tr != NULL; tr = tr->next)
    {
      links = 0;

      /* loop through each network connections entry in the control panel */
      for (pr = panel_reg; pr != NULL; pr = pr->next)
	{
	  if (!strcmp (tr->guid, pr->guid))
	    {
	      msg (msglev, "'%s' %s", pr->name, tr->guid);
	      ++links;
	    }
	}

      if (links > 1)
	{
	  warn_panel_dup = true;
	}
      else if (links == 0)
	{
	  /* a TAP adapter exists without a link from the network
	     connections control panel */
	  warn_panel_null = true;
	  msg (msglev, "[NULL] %s", tr->guid);
	}
    }

  /* check for TAP-Win32 adapter duplicated GUIDs */
  for (tr = tap_reg; tr != NULL; tr = tr->next)
    {
      for (tr1 = tap_reg; tr1 != NULL; tr1 = tr1->next)
	{
	  if (tr != tr1 && !strcmp (tr->guid, tr1->guid))
	    warn_tap_dup = true;
	}
    }

  /* warn on registry inconsistencies */
  if (warn_tap_dup)
    msg (warnlev, "WARNING: Some TAP-Win32 adapters have duplicate GUIDs");

  if (warn_panel_dup)
    msg (warnlev, "WARNING: Some TAP-Win32 adapters have duplicate links from the Network Connections control panel");

  if (warn_panel_null)
    msg (warnlev, "WARNING: Some TAP-Win32 adapters have no link from the Network Connections control panel");

  gc_free (&gc);
}

/*
 * Confirm that GUID is a TAP-Win32 adapter.
 */
static bool
is_tap_win32 (const char *guid, const struct tap_reg *tap_reg)
{
  const struct tap_reg *tr;

  for (tr = tap_reg; tr != NULL; tr = tr->next)
    {
      if (guid && !strcmp (tr->guid, guid))
	return true;
    }

  return false;
}

static const char *
guid_to_name (const char *guid, const struct panel_reg *panel_reg)
{
  const struct panel_reg *pr;

  for (pr = panel_reg; pr != NULL; pr = pr->next)
    {
      if (guid && !strcmp (pr->guid, guid))
	return pr->name;
    }

  return NULL;
}

static const char *
name_to_guid (const char *name, const struct tap_reg *tap_reg, const struct panel_reg *panel_reg)
{
  const struct panel_reg *pr;

  for (pr = panel_reg; pr != NULL; pr = pr->next)
    {
      if (name && !strcmp (pr->name, name) && is_tap_win32 (pr->guid, tap_reg))
	return pr->guid;
    }

  return NULL;
}

static void
at_least_one_tap_win32 (const struct tap_reg *tap_reg)
{
  if (!tap_reg)
    msg (M_FATAL, "There are no TAP-Win32 adapters on this system.  You should be able to create a TAP-Win32 adapter by going to Start -> All Programs -> " PACKAGE_NAME " -> Add a new TAP-Win32 virtual ethernet adapter.");
}

/*
 * Get an adapter GUID and optional actual_name from the 
 * registry for the TAP device # = device_number.
 */
static const char *
get_unspecified_device_guid (const int device_number,
		             char *actual_name,
		             int actual_name_size,
			     const struct tap_reg *tap_reg_src,
			     const struct panel_reg *panel_reg_src,
		             struct gc_arena *gc)
{
  const struct tap_reg *tap_reg = tap_reg_src;
  struct buffer ret = clear_buf ();
  struct buffer actual = clear_buf ();
  int i;

  ASSERT (device_number >= 0);

  /* Make sure we have at least one TAP adapter */
  if (!tap_reg)
    return NULL;

  /* The actual_name output buffer may be NULL */
  if (actual_name)
    {
      ASSERT (actual_name_size > 0);
      buf_set_write (&actual, actual_name, actual_name_size);
    }

  /* Move on to specified device number */
  for (i = 0; i < device_number; i++)
    {
      tap_reg = tap_reg->next;
      if (!tap_reg)
	return NULL;
    }

  /* Save Network Panel name (if exists) in actual_name */
  if (actual_name)
    {
      const char *act = guid_to_name (tap_reg->guid, panel_reg_src);
      if (act)
	buf_printf (&actual, "%s", act);
      else
	buf_printf (&actual, "NULL");
    }

  /* Save GUID for return value */
  ret = alloc_buf_gc (256, gc);
  buf_printf (&ret, "%s", tap_reg->guid);
  return BSTR (&ret);
}

/*
 * Lookup a --dev-node adapter name in the registry
 * returning the GUID and optional actual_name.
 */
static const char *
get_device_guid (const char *name,
		 char *actual_name,
		 int actual_name_size,
		 const struct tap_reg *tap_reg,
		 const struct panel_reg *panel_reg,
		 struct gc_arena *gc)
{
  struct buffer ret = alloc_buf_gc (256, gc);
  struct buffer actual = clear_buf ();

  /* Make sure we have at least one TAP adapter */
  if (!tap_reg)
    return NULL;

  /* The actual_name output buffer may be NULL */
  if (actual_name)
    {
      ASSERT (actual_name_size > 0);
      buf_set_write (&actual, actual_name, actual_name_size);
    }

  /* Check if GUID was explicitly specified as --dev-node parameter */
  if (is_tap_win32 (name, tap_reg))
    {
      const char *act = guid_to_name (name, panel_reg);
      buf_printf (&ret, "%s", name);
      if (act)
	buf_printf (&actual, "%s", act);
      else
	buf_printf (&actual, "NULL");
      return BSTR (&ret);
    }

  /* Lookup TAP adapter in network connections list */
  {
    const char *guid = name_to_guid (name, tap_reg, panel_reg);
    if (guid)
      {
	buf_printf (&actual, "%s", name);
	buf_printf (&ret, "%s", guid);
	return BSTR (&ret);
      }
  }

  return NULL;
}

/*
 * Return a TAP name for netsh commands.
 */
const char *
get_netsh_id (const char *dev_node, struct gc_arena *gc)
{
  const struct tap_reg *tap_reg = get_tap_reg (gc);
  const struct panel_reg *panel_reg = get_panel_reg (gc);
  struct buffer actual = alloc_buf_gc (256, gc);
  const char *guid;

  at_least_one_tap_win32 (tap_reg);

  if (dev_node)
    {
      guid = get_device_guid (dev_node, BPTR (&actual), BCAP (&actual), tap_reg, panel_reg, gc);
    }
  else
    {
      guid = get_unspecified_device_guid (0, BPTR (&actual), BCAP (&actual), tap_reg, panel_reg, gc);

      if (get_unspecified_device_guid (1, NULL, 0, tap_reg, panel_reg, gc)) /* ambiguous if more than one TAP-Win32 adapter */
	guid = NULL;
    }

  if (!guid)
    return "NULL";         /* not found */
  else if (strcmp (BPTR (&actual), "NULL"))
    return BPTR (&actual); /* control panel name */
  else
    return guid;           /* no control panel name, return GUID instead */
}

/*
 * Get adapter info list
 */
const IP_ADAPTER_INFO *
get_adapter_info_list (struct gc_arena *gc)
{
  ULONG size = 0;
  IP_ADAPTER_INFO *pi = NULL;
  DWORD status;

  if ((status = GetAdaptersInfo (NULL, &size)) != ERROR_BUFFER_OVERFLOW)
    {
      msg (M_INFO, "GetAdaptersInfo #1 failed (status=%u) : %s",
	   (unsigned int)status,
	   strerror_win32 (status, gc));
    }
  else
    {
      pi = (PIP_ADAPTER_INFO) gc_malloc (size, false, gc);
      if ((status = GetAdaptersInfo (pi, &size)) == NO_ERROR)
	return pi;
      else
	{
	  msg (M_INFO, "GetAdaptersInfo #2 failed (status=%u) : %s",
	       (unsigned int)status,
	       strerror_win32 (status, gc));
	}
    }
  return pi;
}

static const IP_INTERFACE_INFO *
get_interface_info_list (struct gc_arena *gc)
{
  ULONG size = 0;
  IP_INTERFACE_INFO *ii = NULL;
  DWORD status;

  if ((status = GetInterfaceInfo (NULL, &size)) != ERROR_INSUFFICIENT_BUFFER)
    {
      msg (M_INFO, "GetInterfaceInfo #1 failed (status=%u) : %s",
	   (unsigned int)status,
	   strerror_win32 (status, gc));
    }
  else
    {
      ii = (PIP_INTERFACE_INFO) gc_malloc (size, false, gc);
      if ((status = GetInterfaceInfo (ii, &size)) == NO_ERROR)
	return ii;
      else
	{
	  msg (M_INFO, "GetInterfaceInfo #2 failed (status=%u) : %s",
	       (unsigned int)status,
	       strerror_win32 (status, gc));
	}
    }
  return ii;
}

static const IP_ADAPTER_INDEX_MAP *
get_interface_info (DWORD index, struct gc_arena *gc)
{
  const IP_INTERFACE_INFO *list = get_interface_info_list (gc);
  if (list)
    {
      int i;
      for (i = 0; i < list->NumAdapters; ++i)
	{
	  const IP_ADAPTER_INDEX_MAP *inter = &list->Adapter[i];
	  if (index == inter->Index)
	    return inter;
	}
    }
  return NULL;
}

/*
 * Given an adapter index, return a pointer to the
 * IP_ADAPTER_INFO structure for that adapter.
 */

static const IP_ADAPTER_INFO *
get_adapter (const IP_ADAPTER_INFO *ai, DWORD index)
{
  if (ai && index != (DWORD)~0)
    {
      const IP_ADAPTER_INFO *a;

      /* find index in the linked list */
      for (a = ai; a != NULL; a = a->Next)
	{
	  if (a->Index == index)
	    return a;
	}
    }
  return NULL;
}

static const IP_ADAPTER_INFO *
get_adapter_info (DWORD index, struct gc_arena *gc)
{
  return get_adapter (get_adapter_info_list (gc), index);
}

static int
get_adapter_n_ip_netmask (const IP_ADAPTER_INFO *ai)
{
  if (ai)
    {
      int n = 0;
      const IP_ADDR_STRING *ip = &ai->IpAddressList;

      while (ip)
	{
	  ++n;
	  ip = ip->Next;
	}
      return n;
    }
  else
    return 0;
}

static bool
get_adapter_ip_netmask (const IP_ADAPTER_INFO *ai, const int n, in_addr_t *ip, in_addr_t *netmask)
{
  bool ret = false;
  *ip = 0;
  *netmask = 0;

  if (ai)
    {
      const IP_ADDR_STRING *iplist = &ai->IpAddressList;
      int i = 0;

      while (iplist)
	{
	  if (i == n)
	    break;
	  ++i;
	  iplist = iplist->Next;
	}

      if (iplist)
	{
	  const unsigned int getaddr_flags = GETADDR_HOST_ORDER;
	  const char *ip_str = iplist->IpAddress.String;
	  const char *netmask_str = iplist->IpMask.String;
	  bool succeed1 = false;
	  bool succeed2 = false;

	  if (ip_str && netmask_str && strlen (ip_str) && strlen (netmask_str))
	    {
	      *ip = getaddr (getaddr_flags, ip_str, 0, &succeed1, NULL);
	      *netmask = getaddr (getaddr_flags, netmask_str, 0, &succeed2, NULL);
	      ret = (succeed1 == true && succeed2 == true);
	    }
	}
    }

  return ret;
}

const IP_ADAPTER_INFO *
get_tun_adapter (const struct tuntap *tt, const IP_ADAPTER_INFO *list)
{
  if (list && tt)
    return get_adapter (list, tt->adapter_index);
  else
    return NULL;
}

bool
is_adapter_up (const struct tuntap *tt, const IP_ADAPTER_INFO *list)
{
  int i;
  bool ret = false;

  const IP_ADAPTER_INFO *ai = get_tun_adapter (tt, list);

  if (ai)
    {
      const int n = get_adapter_n_ip_netmask (ai);

      /* loop once for every IP/netmask assigned to adapter */
      for (i = 0; i < n; ++i)
	{
	  in_addr_t ip, netmask;
	  if (get_adapter_ip_netmask (ai, i, &ip, &netmask))
	    {
	      if (tt->local && tt->adapter_netmask)
		{
		  /* wait for our --ifconfig parms to match the actual adapter parms */
		  if (tt->local == ip && tt->adapter_netmask == netmask)
		    ret = true;
		}
	      else
		{
		  /* --ifconfig was not defined, maybe using a real DHCP server */
		  if (ip && netmask)
		    ret = true;
		}
	    }
	}
    }
  else
    ret = true; /* this can occur when TAP adapter is bridged */

  return ret;
}

bool
is_ip_in_adapter_subnet (const IP_ADAPTER_INFO *ai, const in_addr_t ip, in_addr_t *highest_netmask)
{
  int i;
  bool ret = false;

  if (highest_netmask)
    *highest_netmask = 0;

  if (ai)
    {
      const int n = get_adapter_n_ip_netmask (ai);
      for (i = 0; i < n; ++i)
	{
	  in_addr_t adapter_ip, adapter_netmask;
	  if (get_adapter_ip_netmask (ai, i, &adapter_ip, &adapter_netmask))
	    {
	      if (adapter_ip && adapter_netmask && (ip & adapter_netmask) == (adapter_ip & adapter_netmask))
		{
		  if (highest_netmask && adapter_netmask > *highest_netmask)
		    *highest_netmask = adapter_netmask;
		  ret = true;
		}
	    }
	}
    }
  return ret;
}

DWORD
adapter_index_of_ip (const IP_ADAPTER_INFO *list, const in_addr_t ip, int *count)
{
  struct gc_arena gc = gc_new ();
  DWORD ret = ~0;
  in_addr_t highest_netmask = 0;
  bool first = true;

  if (count)
    *count = 0;

  while (list)
    {
      in_addr_t hn;

      if (is_ip_in_adapter_subnet (list, ip, &hn))
	{
	  if (first || hn > highest_netmask)
	    {
	      highest_netmask = hn;
	      if (count)
		*count = 1;
	      ret = list->Index;
	      first = false;
	    }
	  else if (hn == highest_netmask)
	    {
	      if (count)
		++*count;
	    }
	}
      list = list->Next;
    }

  dmsg (D_ROUTE_DEBUG, "DEBUG: IP Locate: ip=%s nm=%s index=%d count=%d",
       print_in_addr_t (ip, 0, &gc),
       print_in_addr_t (highest_netmask, 0, &gc),
       (int)ret,
       count ? *count : -1);

  if (ret == ~0 && count)
    *count = 0;

  gc_free (&gc);
  return ret;
}

/*
 * Given an adapter index, return true if the adapter
 * is DHCP disabled.
 */
static bool
dhcp_disabled (DWORD index)
{
  struct gc_arena gc = gc_new ();
  const IP_ADAPTER_INFO *ai = get_adapter_info (index, &gc);
  bool ret = false;

  if (ai && !ai->DhcpEnabled)
    ret = true;

  gc_free (&gc);
  return ret;
}

/*
 * Delete all temporary address/netmask pairs which were added
 * to adapter (given by index) by previous calls to AddIPAddress.
 */
static void
delete_temp_addresses (DWORD index)
{
  struct gc_arena gc = gc_new ();
  const IP_ADAPTER_INFO *a = get_adapter_info (index, &gc);

  if (a)
    {
      const IP_ADDR_STRING *ip = &a->IpAddressList;
      while (ip)
	{
	  DWORD status;
	  const DWORD context = ip->Context;

	  if ((status = DeleteIPAddress ((ULONG) context)) == NO_ERROR)
	    {
	      msg (M_INFO, "Successfully deleted previously set dynamic IP/netmask: %s/%s",
		   ip->IpAddress.String,
		   ip->IpMask.String);
	    }
	  else
	    {
	      const char *empty = "0.0.0.0";
	      if (strcmp (ip->IpAddress.String, empty)
		  || strcmp (ip->IpMask.String, empty))
		msg (M_INFO, "NOTE: could not delete previously set dynamic IP/netmask: %s/%s (status=%u)",
		     ip->IpAddress.String,
		     ip->IpMask.String,
		     (unsigned int)status);
	    }
	  ip = ip->Next;
	}
    }
  gc_free (&gc);
}

/*
 * Get interface index for use with IP Helper API functions.
 */
static DWORD
get_interface_index (const char *guid)
{
  struct gc_arena gc = gc_new ();
  ULONG index;
  DWORD status;
  wchar_t wbuf[256];
  snwprintf (wbuf, SIZE (wbuf), L"\\DEVICE\\TCPIP_%S", guid);
  wbuf [SIZE(wbuf) - 1] = 0;
  if ((status = GetAdapterIndex (wbuf, &index)) != NO_ERROR)
    {
      msg (M_INFO, "NOTE: could not get adapter index for %S, status=%u : %s",
	   wbuf,
	   (unsigned int)status,
	   strerror_win32 (status, &gc));
      gc_free (&gc);
      return (DWORD)~0;
    }
  else
    {
      gc_free (&gc);
      return index;
    }
}

/*
 * Return a string representing a PIP_ADDR_STRING
 */
static const char *
format_ip_addr_string (const IP_ADDR_STRING *ip, struct gc_arena *gc)
{
  struct buffer out = alloc_buf_gc (256, gc);
  while (ip)
    {
      buf_printf (&out, "%s", ip->IpAddress.String);
      if (strlen (ip->IpMask.String))
	{
	  buf_printf (&out, "/");
	  buf_printf (&out, "%s", ip->IpMask.String);
	}
      buf_printf (&out, " ");
      ip = ip->Next;
    }
  return BSTR (&out);
}

/*
 * Show info for a single adapter
 */
static void
show_adapter (int msglev, const IP_ADAPTER_INFO *a, struct gc_arena *gc)
{
  msg (msglev, "%s", a->Description);
  msg (msglev, "  Index = %d", (int)a->Index);
  msg (msglev, "  GUID = %s", a->AdapterName);
  msg (msglev, "  IP = %s", format_ip_addr_string (&a->IpAddressList, gc));
  msg (msglev, "  MAC = %s", format_hex_ex (a->Address, a->AddressLength, 0, 1, ":", gc));
  msg (msglev, "  GATEWAY = %s", format_ip_addr_string (&a->GatewayList, gc));
  if (a->DhcpEnabled)
    {
      msg (msglev, "  DHCP SERV = %s", format_ip_addr_string (&a->DhcpServer, gc));
      msg (msglev, "  DHCP LEASE OBTAINED = %s", time_string (a->LeaseObtained, 0, false, gc));
      msg (msglev, "  DHCP LEASE EXPIRES  = %s", time_string (a->LeaseExpires, 0, false, gc));
    }
  if (a->HaveWins)
    {
      msg (msglev, "  PRI WINS = %s", format_ip_addr_string (&a->PrimaryWinsServer, gc));
      msg (msglev, "  SEC WINS = %s", format_ip_addr_string (&a->SecondaryWinsServer, gc));
    }
}

/*
 * Show current adapter list
 */
void
show_adapters (int msglev)
{
  struct gc_arena gc = gc_new ();
  const IP_ADAPTER_INFO *ai = get_adapter_info_list (&gc);

  msg (msglev, "SYSTEM ADAPTER LIST");
  if (ai)
    {
      const IP_ADAPTER_INFO *a;

      /* find index in the linked list */
      for (a = ai; a != NULL; a = a->Next)
	{
	  show_adapter (msglev, a, &gc);
	}
    }
  gc_free (&gc);
}

/*
 * DHCP release/renewal
 */

bool
dhcp_release (const struct tuntap *tt)
{
  struct gc_arena gc = gc_new ();
  bool ret = false;
  if (tt && tt->options.ip_win32_type == IPW32_SET_DHCP_MASQ && tt->adapter_index != ~0)
    {
      const IP_ADAPTER_INDEX_MAP *inter = get_interface_info (tt->adapter_index, &gc);
      if (inter)
	{
	  DWORD status = IpReleaseAddress ((IP_ADAPTER_INDEX_MAP *)inter);
	  if (status == NO_ERROR)
	    {
	      msg (D_TUNTAP_INFO, "TAP: DHCP address released");
	      ret = true;
	    }
	  else
	    msg (M_WARN, "NOTE: Release of DHCP-assigned IP address lease on TAP-Win32 adapter failed: %s (code=%u)",
		 strerror_win32 (status, &gc),
		 (unsigned int)status);
	}
    }
  gc_free (&gc);
  return ret;
}

bool
dhcp_renew (const struct tuntap *tt)
{
  struct gc_arena gc = gc_new ();
  bool ret = false;
  if (tt && tt->options.ip_win32_type == IPW32_SET_DHCP_MASQ && tt->adapter_index != ~0)
    {
      const IP_ADAPTER_INDEX_MAP *inter = get_interface_info (tt->adapter_index, &gc);
      if (inter)
	{
	  DWORD status = IpRenewAddress ((IP_ADAPTER_INDEX_MAP *)inter);
	  if (status == NO_ERROR)
	    {
	      msg (D_TUNTAP_INFO, "TAP: DHCP address renewal succeeded");
	      ret = true;
	    }
	  else
	    msg (M_WARN, "WARNING: Failed to renew DHCP IP address lease on TAP-Win32 adapter: %s (code=%u)",
		 strerror_win32 (status, &gc),
		 (unsigned int)status);
	}
    }
  gc_free (&gc);
  return ret;
}

/*
 * Convert DHCP options from the command line / config file
 * into a raw DHCP-format options string.
 */

static void
write_dhcp_u8 (struct buffer *buf, const int type, const int data)
{
  if (!buf_safe (buf, 3))
    msg (M_FATAL, "write_dhcp_u8: buffer overflow building DHCP options");
  buf_write_u8 (buf, type);
  buf_write_u8 (buf, 1);
  buf_write_u8 (buf, data);
}

static void
write_dhcp_u32_array (struct buffer *buf, const int type, const uint32_t *data, const unsigned int len)
{
  if (len > 0)
    {
      int i;
      const int size = len * sizeof (uint32_t);

      if (!buf_safe (buf, 2 + size))
	msg (M_FATAL, "write_dhcp_u32_array: buffer overflow building DHCP options");
      if (size < 1 || size > 255)
	msg (M_FATAL, "write_dhcp_u32_array: size (%d) must be > 0 and <= 255", size);
      buf_write_u8 (buf, type);
      buf_write_u8 (buf, size);
      for (i = 0; i < len; ++i)
	buf_write_u32 (buf, data[i]);
    }
}

static void
write_dhcp_str (struct buffer *buf, const int type, const char *str)
{
  const int len = strlen (str);
  if (!buf_safe (buf, 2 + len))
    msg (M_FATAL, "write_dhcp_str: buffer overflow building DHCP options");
  if (len < 1 || len > 255)
    msg (M_FATAL, "write_dhcp_str: string '%s' must be > 0 bytes and <= 255 bytes", str);
  buf_write_u8 (buf, type);
  buf_write_u8 (buf, len);
  buf_write (buf, str, len);
}

static void
build_dhcp_options_string (struct buffer *buf, const struct tuntap_options *o)
{
  if (o->domain)
    write_dhcp_str (buf, 15, o->domain);

  if (o->netbios_scope)
    write_dhcp_str (buf, 47, o->netbios_scope);

  if (o->netbios_node_type)
    write_dhcp_u8 (buf, 46, o->netbios_node_type);

  write_dhcp_u32_array (buf, 6, (uint32_t*)o->dns, o->dns_len);
  write_dhcp_u32_array (buf, 44, (uint32_t*)o->wins, o->wins_len);
  write_dhcp_u32_array (buf, 42, (uint32_t*)o->ntp, o->ntp_len);
  write_dhcp_u32_array (buf, 45, (uint32_t*)o->nbdd, o->nbdd_len);

  /* 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 (o->disable_nbt)
  {
    buf_write_u8 (buf, 43);
    buf_write_u8 (buf,  6);  /* total length field */
    buf_write_u8 (buf,  0x001);
    buf_write_u8 (buf,  4);  /* length of the vendor specified field */
    buf_write_u32 (buf, 0x002);
  }
}

void
open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt)
{
  struct gc_arena gc = gc_new ();
  char device_path[256];
  const char *device_guid = NULL;
  DWORD len;

  /*netcmd_semaphore_lock ();*/

  ipv6_support (ipv6, false, tt);

  if (tt->type == DEV_TYPE_NULL)
    {
      open_null (tt);
      gc_free (&gc);
      return;
    }
  else if (tt->type == DEV_TYPE_TAP || tt->type == DEV_TYPE_TUN)
    {
      ;
    }
  else
    {
      msg (M_FATAL|M_NOPREFIX, "Unknown virtual device type: '%s'", dev);
    }

  /*
   * Lookup the device name in the registry, using the --dev-node high level name.
   */
  {
    const struct tap_reg *tap_reg = get_tap_reg (&gc);
    const struct panel_reg *panel_reg = get_panel_reg (&gc);
    char guid_buffer[256];

    at_least_one_tap_win32 (tap_reg);

    if (dev_node)
      {
        /* Get the device GUID for the device specified with --dev-node. */
        device_guid = get_device_guid (dev_node, guid_buffer, sizeof (guid_buffer), tap_reg, panel_reg, &gc);

	if (!device_guid)
	    msg (M_FATAL, "TAP-Win32 adapter '%s' not found", dev_node);

        /* Open Windows TAP-Win32 adapter */
        openvpn_snprintf (device_path, sizeof(device_path), "%s%s%s",
   		          USERMODEDEVICEDIR,
		          device_guid,
		          TAPSUFFIX);

        tt->hand = CreateFile (
			       device_path,
			       GENERIC_READ | GENERIC_WRITE,
			       0, /* was: FILE_SHARE_READ */
			       0,
			       OPEN_EXISTING,
			       FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED,
			       0
			       );

        if (tt->hand == INVALID_HANDLE_VALUE)
          msg (M_ERR, "CreateFile failed on TAP device: %s", device_path);
      }
    else 
      {
        int device_number = 0;

        /* Try opening all TAP devices until we find one available */
        while (true)
          {
            device_guid = get_unspecified_device_guid (device_number, 
						       guid_buffer, 
						       sizeof (guid_buffer),
						       tap_reg,
						       panel_reg,
						       &gc);

	    if (!device_guid)
	      msg (M_FATAL, "All TAP-Win32 adapters on this system are currently in use.");

            /* Open Windows TAP-Win32 adapter */
            openvpn_snprintf (device_path, sizeof(device_path), "%s%s%s",
       		  	      USERMODEDEVICEDIR,
			      device_guid,
			      TAPSUFFIX);

            tt->hand = CreateFile (
			 	   device_path,
				   GENERIC_READ | GENERIC_WRITE,
				   0, /* was: FILE_SHARE_READ */
				   0,
				   OPEN_EXISTING,
				   FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED,
				   0
				   );

            if (tt->hand == INVALID_HANDLE_VALUE)
              msg (D_TUNTAP_INFO, "CreateFile failed on TAP device: %s", device_path);
            else
              break;
        
            device_number++;
          }
      }

    /* translate high-level device name into a device instance
       GUID using the registry */
    tt->actual_name = string_alloc (guid_buffer, NULL);
  }

  msg (M_INFO, "TAP-WIN32 device [%s] opened: %s", tt->actual_name, device_path);

  /* get driver version info */
  {
    ULONG info[3];
    CLEAR (info);
    if (DeviceIoControl (tt->hand, TAP_IOCTL_GET_VERSION,
			 &info, sizeof (info),
			 &info, sizeof (info), &len, NULL))
      {
	msg (D_TUNTAP_INFO, "TAP-Win32 Driver Version %d.%d %s",
	     (int) info[0],
	     (int) info[1],
	     (info[2] ? "(DEBUG)" : ""));

      }
    if ( !(info[0] > TAP_WIN32_MIN_MAJOR
	   || (info[0] == TAP_WIN32_MIN_MAJOR && info[1] >= TAP_WIN32_MIN_MINOR)) )
      msg (M_FATAL, "ERROR:  This version of " PACKAGE_NAME " requires a TAP-Win32 driver that is at least version %d.%d -- If you recently upgraded your " PACKAGE_NAME " distribution, a reboot is probably required at this point to get Windows to see the new driver.",
	   TAP_WIN32_MIN_MAJOR,
	   TAP_WIN32_MIN_MINOR);
  }

  /* get driver MTU */
  {
    ULONG mtu;
    if (DeviceIoControl (tt->hand, TAP_IOCTL_GET_MTU,
			 &mtu, sizeof (mtu),
			 &mtu, sizeof (mtu), &len, NULL))
      {
	tt->post_open_mtu = (int) mtu;
	msg (D_MTU_INFO, "TAP-Win32 MTU=%d", (int) mtu);
      }
  }

  /* set point-to-point mode if TUN device */

  if (tt->type == DEV_TYPE_TUN)
    {
      in_addr_t ep[2];
      ep[0] = htonl (tt->local);
      ep[1] = htonl (tt->remote_netmask);
      if (!tt->did_ifconfig_setup)
	{
	  msg (M_FATAL, "ERROR: --dev tun also requires --ifconfig");
	}
      if (!DeviceIoControl (tt->hand, TAP_IOCTL_CONFIG_POINT_TO_POINT,
			    ep, sizeof (ep),
			    ep, sizeof (ep), &len, NULL))
	msg (M_FATAL, "ERROR: The TAP-Win32 driver rejected a DeviceIoControl call to set Point-to-Point mode, which is required for --dev tun");
    }

  /* should we tell the TAP-Win32 driver to masquerade as a DHCP server as a means
     of setting the adapter address? */
  if (tt->did_ifconfig_setup && tt->options.ip_win32_type == IPW32_SET_DHCP_MASQ)
    {
      uint32_t ep[4];

      /* We will answer DHCP requests with a reply to set IP/subnet to these values */
      ep[0] = htonl (tt->local);
      ep[1] = htonl (tt->adapter_netmask);

      /* At what IP address should the DHCP server masquerade at? */
      if (tt->type == DEV_TYPE_TUN)
	{
	  ep[2] = htonl (tt->remote_netmask);
	  if (tt->options.dhcp_masq_custom_offset)
	    msg (M_WARN, "WARNING: because you are using '--dev tun' mode, the '--ip-win32 dynamic [offset]' option is ignoring the offset parameter");
	}
      else
	{
	  in_addr_t dsa; /* DHCP server addr */

	  ASSERT (tt->type == DEV_TYPE_TAP);

	  if (tt->options.dhcp_masq_offset < 0)
	    dsa = (tt->local | (~tt->adapter_netmask)) + tt->options.dhcp_masq_offset;
	  else
	    dsa = (tt->local & tt->adapter_netmask) + tt->options.dhcp_masq_offset;

	  if (dsa == tt->local)
	    msg (M_FATAL, "ERROR: There is a clash between the --ifconfig local address and the internal DHCP server address -- both are set to %s -- please use the --ip-win32 dynamic option to choose a different free address from the --ifconfig subnet for the internal DHCP server", print_in_addr_t (dsa, 0, &gc));

	  if ((tt->local & tt->adapter_netmask) != (dsa & tt->adapter_netmask))
	    msg (M_FATAL, "ERROR: --tap-win32 dynamic [offset] : offset is outside of --ifconfig subnet");

	  ep[2] = htonl (dsa);
	}

      /* lease time in seconds */
      ep[3] = (uint32_t) tt->options.dhcp_lease_time;

      ASSERT (ep[3] > 0);

      if (!DeviceIoControl (tt->hand, TAP_IOCTL_CONFIG_DHCP_MASQ,
			    ep, sizeof (ep),
			    ep, sizeof (ep), &len, NULL))
	msg (M_FATAL, "ERROR: The TAP-Win32 driver rejected a DeviceIoControl call to set TAP_IOCTL_CONFIG_DHCP_MASQ mode");

      msg (M_INFO, "Notified TAP-Win32 driver to set a DHCP IP/netmask of %s/%s on interface %s [DHCP-serv: %s, lease-time: %d]",
	   print_in_addr_t (tt->local, 0, &gc),
	   print_in_addr_t (tt->adapter_netmask, 0, &gc),
	   device_guid,
	   print_in_addr_t (ep[2], IA_NET_ORDER, &gc),
	   ep[3]
	   );

      /* user-supplied DHCP options capability */
      if (tt->options.dhcp_options)
	{
	  struct buffer buf = alloc_buf (256);
	  build_dhcp_options_string (&buf, &tt->options);
	  msg (D_DHCP_OPT, "DHCP option string: %s", format_hex (BPTR (&buf), BLEN (&buf), 0, &gc));
	  if (!DeviceIoControl (tt->hand, TAP_IOCTL_CONFIG_DHCP_SET_OPT,
				BPTR (&buf), BLEN (&buf),
				BPTR (&buf), BLEN (&buf), &len, NULL))
	    msg (M_FATAL, "ERROR: The TAP-Win32 driver rejected a TAP_IOCTL_CONFIG_DHCP_SET_OPT DeviceIoControl call");
	  free_buf (&buf);
	}
    }

  /* set driver media status to 'connected' */
  {
    ULONG status = TRUE;
    if (!DeviceIoControl (tt->hand, TAP_IOCTL_SET_MEDIA_STATUS,
			  &status, sizeof (status),
			  &status, sizeof (status), &len, NULL))
      msg (M_WARN, "WARNING: The TAP-Win32 driver rejected a TAP_IOCTL_SET_MEDIA_STATUS DeviceIoControl call.");
  }

  /* possible wait for adapter to come up */
  {
    int s = tt->options.tap_sleep;
    if (s > 0)
      {
	msg (M_INFO, "Sleeping for %d seconds...", s);
	openvpn_sleep (s);
      }
  }

  /* possibly use IP Helper API to set IP address on adapter */
  {
    DWORD index = get_interface_index (device_guid);
    tt->adapter_index = index;
    
    /* flush arp cache */
    if (index != (DWORD)~0)
      {
	DWORD status;

	if ((status = FlushIpNetTable (index)) == NO_ERROR)
	  msg (M_INFO, "Successful ARP Flush on interface [%u] %s",
	       (unsigned int)index,
	       device_guid);
	else
	  msg (M_WARN, "NOTE: FlushIpNetTable failed on interface [%u] %s (status=%u) : %s",
	       (unsigned int)index,
	       device_guid,
	       (unsigned int)status,
	       strerror_win32 (status, &gc));
      }

    /*
     * If the TAP-Win32 driver is masquerading as a DHCP server
     * make sure the TCP/IP properties for the adapter are
     * set correctly.
     */
    if (tt->did_ifconfig_setup && tt->options.ip_win32_type == IPW32_SET_DHCP_MASQ)
      {
	/* check dhcp enable status */
	if (dhcp_disabled (index))
	  msg (M_WARN, "WARNING: You have selected '--ip-win32 dynamic', which will not work unless the TAP-Win32 TCP/IP properties are set to 'Obtain an IP address automatically'");

	/* force an explicit DHCP lease renewal on TAP adapter? */
	if (tt->options.dhcp_pre_release)
	  dhcp_release (tt);
	if (tt->options.dhcp_renew)
	  dhcp_renew (tt);
      }

    if (tt->did_ifconfig_setup && tt->options.ip_win32_type == IPW32_SET_IPAPI)
      {
	DWORD status;
	const char *error_suffix = "I am having trouble using the Windows 'IP helper API' to automatically set the IP address -- consider using other --ip-win32 methods (not 'ipapi')";

	/* couldn't get adapter index */
	if (index == (DWORD)~0)
	  {
	    msg (M_FATAL, "ERROR: unable to get adapter index for interface %s -- %s",
		 device_guid,
		 error_suffix);
	  }

	/* check dhcp enable status */
	if (dhcp_disabled (index))
	  msg (M_WARN, "NOTE: You have selected (explicitly or by default) '--ip-win32 ipapi', which has a better chance of working correctly if the TAP-Win32 TCP/IP properties are set to 'Obtain an IP address automatically'");

	/* delete previously added IP addresses which were not
	   correctly deleted */
	delete_temp_addresses (index);

	/* add a new IP address */
	if ((status = AddIPAddress (htonl(tt->local),
				    htonl(tt->adapter_netmask),
				    index,
				    &tt->ipapi_context,
				    &tt->ipapi_instance)) == NO_ERROR)
	  msg (M_INFO, "Succeeded in adding a temporary IP/netmask of %s/%s to interface %s using the Win32 IP Helper API",
	       print_in_addr_t (tt->local, 0, &gc),
	       print_in_addr_t (tt->adapter_netmask, 0, &gc),
	       device_guid
	       );
	else
	  msg (M_FATAL, "ERROR: AddIPAddress %s/%s failed on interface %s, index=%d, status=%u (windows error: '%s') -- %s",
	       print_in_addr_t (tt->local, 0, &gc),
	       print_in_addr_t (tt->adapter_netmask, 0, &gc),
	       device_guid,
	       (int)index,
	       (unsigned int)status,
	       strerror_win32 (status, &gc),
	       error_suffix);
	tt->ipapi_context_defined = true;
      }
  }
  /*netcmd_semaphore_release ();*/
  gc_free (&gc);
}

const char *
tap_win32_getinfo (const struct tuntap *tt, struct gc_arena *gc)
{
  if (tt && tt->hand != NULL)
    {
      struct buffer out = alloc_buf_gc (256, gc);
      DWORD len;
      if (DeviceIoControl (tt->hand, TAP_IOCTL_GET_INFO,
			   BSTR (&out), BCAP (&out),
			   BSTR (&out), BCAP (&out),
			   &len, NULL))
	{
	  return BSTR (&out);
	}
    }
  return NULL;
}

void
tun_show_debug (struct tuntap *tt)
{
  if (tt && tt->hand != NULL)
    {
      struct buffer out = alloc_buf (1024);
      DWORD len;
      while (DeviceIoControl (tt->hand, TAP_IOCTL_GET_LOG_LINE,
			      BSTR (&out), BCAP (&out),
			      BSTR (&out), BCAP (&out),
			      &len, NULL))
	{
	  msg (D_TAP_WIN32_DEBUG, "TAP-Win32: %s", BSTR (&out));
	}
      free_buf (&out);
    }
}

void
close_tun (struct tuntap *tt)
{
  struct gc_arena gc = gc_new ();

  if (tt)
    {
#if 1
      if (tt->ipapi_context_defined)
	{
	  DWORD status;
	  if ((status = DeleteIPAddress (tt->ipapi_context)) != NO_ERROR)
	    {
	      msg (M_WARN, "Warning: DeleteIPAddress[%u] failed on TAP-Win32 adapter, status=%u : %s",
		   (unsigned int)tt->ipapi_context,
		   (unsigned int)status,
		   strerror_win32 (status, &gc));
	    }
	}
#endif

      if (tt->options.dhcp_release)
	dhcp_release (tt);

      if (tt->hand != NULL)
	{
	  dmsg (D_WIN32_IO_LOW, "Attempting CancelIO on TAP-Win32 adapter");
	  if (!CancelIo (tt->hand))
	    msg (M_WARN | M_ERRNO, "Warning: CancelIO failed on TAP-Win32 adapter");
	}

      dmsg (D_WIN32_IO_LOW, "Attempting close of overlapped read event on TAP-Win32 adapter");
      overlapped_io_close (&tt->reads);

      dmsg (D_WIN32_IO_LOW, "Attempting close of overlapped write event on TAP-Win32 adapter");
      overlapped_io_close (&tt->writes);

      if (tt->hand != NULL)
	{
	  dmsg (D_WIN32_IO_LOW, "Attempting CloseHandle on TAP-Win32 adapter");
	  if (!CloseHandle (tt->hand))
	    msg (M_WARN | M_ERRNO, "Warning: CloseHandle failed on TAP-Win32 adapter");
	}

      if (tt->actual_name)
	free (tt->actual_name);

      clear_tuntap (tt);
      free (tt);
    }
  gc_free (&gc);
}

/*
 * Convert --ip-win32 constants between index and ascii form.
 */

struct ipset_names {
  const char *short_form;
};

/* Indexed by IPW32_SET_x */
static const struct ipset_names ipset_names[] = {
  {"manual"},
  {"netsh"},
  {"ipapi"},
  {"dynamic"}
};

int
ascii2ipset (const char* name)
{
  int i;
  ASSERT (IPW32_SET_N == SIZE (ipset_names));
  for (i = 0; i < IPW32_SET_N; ++i)
    if (!strcmp (name, ipset_names[i].short_form))
      return i;
  return -1;
}

const char *
ipset2ascii (int index)
{
  ASSERT (IPW32_SET_N == SIZE (ipset_names));
  if (index < 0 || index >= IPW32_SET_N)
    return "[unknown --ip-win32 type]";
  else
    return ipset_names[index].short_form;
}

const char *
ipset2ascii_all (struct gc_arena *gc)
{
  struct buffer out = alloc_buf_gc (256, gc);
  int i;

  ASSERT (IPW32_SET_N == SIZE (ipset_names));
  for (i = 0; i < IPW32_SET_N; ++i)
    {
      if (i)
	buf_printf(&out, " ");
      buf_printf(&out, "[%s]", ipset2ascii(i));
    }
  return BSTR (&out);
}

#else /* generic */

void
open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt)
{
  open_tun_generic (dev, dev_type, dev_node, ipv6, false, true, tt);
}

void
close_tun (struct tuntap* tt)
{
  if (tt)
    {
      close_tun_generic (tt);
      free (tt);
    }
}

int
write_tun (struct tuntap* tt, uint8_t *buf, int len)
{
  return write (tt->fd, buf, len);
}

int
read_tun (struct tuntap* tt, uint8_t *buf, int len)
{
  return read (tt->fd, buf, len);
}

#endif
