[Openvpn-devel] Wintun - experimental support

Message ID 1559746770-21285-1-git-send-email-lstipakov@gmail.com
State New
Headers show
Series
  • [Openvpn-devel] Wintun - experimental support
Related show

Commit Message

Lev Stipakov June 5, 2019, 2:59 p.m.
From: Lev Stipakov <lev@openvpn.net>

This adds experimental support for Wintun,
an alternative to tap-windows6 on Windows.

To use wintun, specify

  --windows-driver wintun

as config option. Default value is "tap-windows6".

Unlike tap-windows6, Wintun returns up to 256 packets,
which have to be decapsulated and processed one by one.
We do it by reading data into a new buffer, processing
single packet and returning control to event loop
to give it a chance to write processed packet to link.

If there are no pending writes and there is data in wintun
buffer, event loop triggers tun read. There we read single
packet from wintun buffer or, if it is empty, read from
tun driver.

Signed-off-by: Lev Stipakov <lev@openvpn.net>
---
 src/openvpn/forward.c |  76 +++++++++++++++++-
 src/openvpn/forward.h |  15 ++++
 src/openvpn/init.c    |   7 ++
 src/openvpn/openvpn.h |   4 +
 src/openvpn/options.c |  36 ++++++++-
 src/openvpn/options.h |   1 +
 src/openvpn/socket.c  |   4 +-
 src/openvpn/syshead.h |   1 +
 src/openvpn/tun.c     | 212 +++++++++++++++++++++++++++++++++-----------------
 src/openvpn/tun.h     |  34 +++++++-
 src/openvpn/win32.c   | 119 ++++++++++++++++++++++++++--
 src/openvpn/win32.h   |  19 ++++-
 12 files changed, 441 insertions(+), 87 deletions(-)

Comments

Lev Stipakov June 7, 2019, 2:29 p.m. | #1
If someone would like to test it - you can get a Windows binary from here:
https://ci.appveyor.com/api/buildjobs/w20knnbut89uoeuw/artifacts/msvc%2Fimage%2Fbin.zip

Note that interactive service doesn't (yet) work with Wintun, you need to
run openvpn as a privileged process. For example, from Administrator
command prompt:

> c:\Temp\ovpn2_wintun>openvpn.exe --config lev.ovpn --windows-driver wintun

ke 5. kesäk. 2019 klo 17.59 Lev Stipakov (lstipakov@gmail.com) kirjoitti:

> From: Lev Stipakov <lev@openvpn.net>
>
> This adds experimental support for Wintun,
> an alternative to tap-windows6 on Windows.
>
> To use wintun, specify
>
>   --windows-driver wintun
>
> as config option. Default value is "tap-windows6".
>
> Unlike tap-windows6, Wintun returns up to 256 packets,
> which have to be decapsulated and processed one by one.
> We do it by reading data into a new buffer, processing
> single packet and returning control to event loop
> to give it a chance to write processed packet to link.
>
> If there are no pending writes and there is data in wintun
> buffer, event loop triggers tun read. There we read single
> packet from wintun buffer or, if it is empty, read from
> tun driver.
>
> Signed-off-by: Lev Stipakov <lev@openvpn.net>
> ---
>  src/openvpn/forward.c |  76 +++++++++++++++++-
>  src/openvpn/forward.h |  15 ++++
>  src/openvpn/init.c    |   7 ++
>  src/openvpn/openvpn.h |   4 +
>  src/openvpn/options.c |  36 ++++++++-
>  src/openvpn/options.h |   1 +
>  src/openvpn/socket.c  |   4 +-
>  src/openvpn/syshead.h |   1 +
>  src/openvpn/tun.c     | 212
> +++++++++++++++++++++++++++++++++-----------------
>  src/openvpn/tun.h     |  34 +++++++-
>  src/openvpn/win32.c   | 119 ++++++++++++++++++++++++++--
>  src/openvpn/win32.h   |  19 ++++-
>  12 files changed, 441 insertions(+), 87 deletions(-)
>
> diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c
> index ad4c1f5..8077835 100644
> --- a/src/openvpn/forward.c
> +++ b/src/openvpn/forward.c
> @@ -2182,6 +2182,52 @@ io_wait_dowork(struct context *c, const unsigned
> int flags)
>      dmsg(D_EVENT_WAIT, "I/O WAIT status=0x%04x", c->c2.event_set_status);
>  }
>
> +#ifdef _WIN32
> +/**
> + * This processes single packet from wintun buffer,
> + * which can contain up to 256 packets.
> + */
> +static void
> +process_incoming_wintun(struct context *c)
> +{
> +    struct buffer *buf = &c->c2.wintun_buf;
> +    uint32_t *size = NULL;
> +    int end_padding_len = 0;
> +
> +    /* get packet size */
> +    size = (uint32_t *)buf_read_alloc(buf, 4);
> +    if (size == NULL)
> +    {
> +        return;
> +    }
> +
> +    /* skip start padding */
> +    if (!buf_advance(buf, 12))
> +    {
> +        return;
> +    }
> +
> +    /* copy packet */
> +    c->c2.buf = c->c2.buffers->read_tun_buf;
> +    buf_init(&c->c2.buf, FRAME_HEADROOM(&c->c2.frame));
> +    buf_safe(&c->c2.buf, MAX_RW_SIZE_TUN(&c->c2.frame));
> +    if (!buf_copy_n(&c->c2.buf, buf, (int)*size))
> +    {
> +        return;
> +    }
> +
> +    /* this encrypts packet and puts it to c->c2.to_link buffer */
> +    process_incoming_tun(c);
> +
> +    /* skip end padding */
> +    end_padding_len = (16 - (*size & 15)) % 16;
> +    if (!buf_advance(buf, end_padding_len))
> +    {
> +        return;
> +    }
> +}
> +#endif
> +
>  void
>  process_io(struct context *c)
>  {
> @@ -2217,10 +2263,34 @@ process_io(struct context *c)
>      /* Incoming data on TUN device */
>      else if (status & TUN_READ)
>      {
> -        read_incoming_tun(c);
> -        if (!IS_SIG(c))
> +#ifdef _WIN32
> +        if (c->options.wintun)
> +        {
> +            /* only read from driver if there are no packets in buffer */
> +            if (BLEN(&c->c2.wintun_buf) == 0)
> +            {
> +                read_incoming_tun(c);
> +                c->c2.wintun_buf = c->c2.buf;
> +            }
> +            if (!IS_SIG(c))
> +            {
> +                process_incoming_wintun(c);
> +            }
> +
> +            /*
> +             * We need to write processed wintun packet to link,
> +             * hence we give control back to event loop, which will
> +             * call us again if wintun buffer is not empty
> +             */
> +        }
> +        else
> +#endif
>          {
> -            process_incoming_tun(c);
> +            read_incoming_tun(c);
> +            if (!IS_SIG(c))
> +            {
> +                process_incoming_tun(c);
> +            }
>          }
>      }
>  }
> diff --git a/src/openvpn/forward.h b/src/openvpn/forward.h
> index 48202c0..ac3e932 100644
> --- a/src/openvpn/forward.h
> +++ b/src/openvpn/forward.h
> @@ -61,6 +61,7 @@
>  #define IOW_MBUF            (1<<7)
>  #define IOW_READ_TUN_FORCE  (1<<8)
>  #define IOW_WAIT_SIGNAL     (1<<9)
> +#define IOW_READ_WINTUN_BUF (1<<10)
>
>  #define IOW_READ            (IOW_READ_TUN|IOW_READ_LINK)
>
> @@ -375,6 +376,12 @@ p2p_iow_flags(const struct context *c)
>      {
>          flags |= IOW_TO_TUN;
>      }
> +#ifdef _WIN32
> +    if (c->c2.wintun_buf.len > 0)
> +    {
> +        flags |= IOW_READ_WINTUN_BUF;
> +    }
> +#endif
>      return flags;
>  }
>
> @@ -401,6 +408,14 @@ io_wait(struct context *c, const unsigned int flags)
>          }
>          c->c2.event_set_status = ret;
>      }
> +#ifdef _WIN32
> +    /* make sure we write to link before reading from wintun buffer,
> +     * otherwise we'll overwrite c2->to_link buffer and lose a packet */
> +    else if (!(flags & IOW_TO_LINK) && (flags & IOW_READ_WINTUN_BUF))
> +    {
> +        c->c2.event_set_status = TUN_READ;
> +    }
> +#endif
>      else
>      {
>          /* slow path */
> diff --git a/src/openvpn/init.c b/src/openvpn/init.c
> index ef26503..1625272 100644
> --- a/src/openvpn/init.c
> +++ b/src/openvpn/init.c
> @@ -1696,6 +1696,10 @@ do_init_tun(struct context *c)
>                              !c->options.ifconfig_nowarn,
>                              c->c2.es);
>
> +#ifdef _WIN32
> +    c->c1.tuntap->wintun = c->options.wintun;
> +#endif
> +
>      init_tun_post(c->c1.tuntap,
>                    &c->c2.frame,
>                    &c->options.tuntap_options);
> @@ -1738,6 +1742,9 @@ do_open_tun(struct context *c)
>      /* store (hide) interactive service handle in tuntap_options */
>      c->c1.tuntap->options.msg_channel = c->options.msg_channel;
>      msg(D_ROUTE, "interactive service msg_channel=%u", (unsigned int)
> c->options.msg_channel);
> +
> +    c->c1.tuntap->wintun = c->options.wintun;
> +
>  #endif
>
>      /* allocate route list structure */
> diff --git a/src/openvpn/openvpn.h b/src/openvpn/openvpn.h
> index d11f61d..d694666 100644
> --- a/src/openvpn/openvpn.h
> +++ b/src/openvpn/openvpn.h
> @@ -382,6 +382,10 @@ struct context_2
>      struct buffer buf;
>      struct buffer to_tun;
>      struct buffer to_link;
> +#ifdef _WIN32
> +    /* contains up to 256 packets */
> +    struct buffer wintun_buf;
> +#endif
>
>      /* should we print R|W|r|w to console on packet transfers? */
>      bool log_rw;
> diff --git a/src/openvpn/options.c b/src/openvpn/options.c
> index e34b65b..534a6be 100644
> --- a/src/openvpn/options.c
> +++ b/src/openvpn/options.c
> @@ -845,6 +845,7 @@ init_options(struct options *o, const bool init_gc)
>      o->tuntap_options.dhcp_masq_offset = 0;     /* use network address as
> internal DHCP server address */
>      o->route_method = ROUTE_METHOD_ADAPTIVE;
>      o->block_outside_dns = false;
> +    o->wintun = false;
>  #endif
>  #if P2MP_SERVER
>      o->real_hash_size = 256;
> @@ -2937,6 +2938,12 @@ options_postprocess_mutate_invariant(struct options
> *options)
>          options->ifconfig_noexec = false;
>      }
>
> +    /* for wintun kernel doesn't send DHCP requests, so use ipapi to set
> IP address and netmask */
> +    if (options->wintun)
> +    {
> +        options->tuntap_options.ip_win32_type = IPW32_SET_IPAPI;
> +    }
> +
>      remap_redirect_gateway_flags(options);
>  #endif
>
> @@ -3980,6 +3987,26 @@ foreign_option(struct options *o, char *argv[], int
> len, struct env_set *es)
>      }
>  }
>
> +#ifdef _WIN32
> +bool
> +parse_windows_driver(const char *str, const int msglevel)
> +{
> +    if (streq(str, "tap"))
> +    {
> +        return false;
> +    }
> +    else if (streq(str, "wintun"))
> +    {
> +        return true;
> +    }
> +    else
> +    {
> +        msg(msglevel, "--windows-driver must be tap-windows6 or wintun");
> +        return false;
> +    }
> +}
> +#endif
> +
>  /*
>   * parse/print topology coding
>   */
> @@ -5220,6 +5247,13 @@ add_option(struct options *options,
>          VERIFY_PERMISSION(OPT_P_GENERAL);
>          options->dev_type = p[1];
>      }
> +#ifdef _WIN32
> +    else if (streq(p[0], "windows-driver") && p[1] && !p[2])
> +    {
> +        VERIFY_PERMISSION(OPT_P_GENERAL);
> +        options->wintun = parse_windows_driver(p[1], M_FATAL);
> +    }
> +#endif
>      else if (streq(p[0], "dev-node") && p[1] && !p[2])
>      {
>          VERIFY_PERMISSION(OPT_P_GENERAL);
> @@ -7217,7 +7251,7 @@ add_option(struct options *options,
>      else if (streq(p[0], "show-adapters") && !p[1])
>      {
>          VERIFY_PERMISSION(OPT_P_GENERAL);
> -        show_tap_win_adapters(M_INFO|M_NOPREFIX, M_WARN|M_NOPREFIX);
> +        show_tap_win_adapters(M_INFO|M_NOPREFIX, M_WARN|M_NOPREFIX,
> options->wintun);
>          openvpn_exit(OPENVPN_EXIT_STATUS_GOOD); /* exit point */
>      }
>      else if (streq(p[0], "show-net") && !p[1])
> diff --git a/src/openvpn/options.h b/src/openvpn/options.h
> index e2b3893..670b70a 100644
> --- a/src/openvpn/options.h
> +++ b/src/openvpn/options.h
> @@ -614,6 +614,7 @@ struct options
>      bool show_net_up;
>      int route_method;
>      bool block_outside_dns;
> +    bool wintun;
>  #endif
>
>      bool use_peer_id;
> diff --git a/src/openvpn/socket.c b/src/openvpn/socket.c
> index c472cf1..4352869 100644
> --- a/src/openvpn/socket.c
> +++ b/src/openvpn/socket.c
> @@ -1635,8 +1635,8 @@ static void
>  socket_frame_init(const struct frame *frame, struct link_socket *sock)
>  {
>  #ifdef _WIN32
> -    overlapped_io_init(&sock->reads, frame, FALSE, false);
> -    overlapped_io_init(&sock->writes, frame, TRUE, false);
> +    overlapped_io_init(&sock->reads, frame, true, false, false);
> +    overlapped_io_init(&sock->writes, frame, false, false, false);
>      sock->rw_handle.read = sock->reads.overlapped.hEvent;
>      sock->rw_handle.write = sock->writes.overlapped.hEvent;
>  #endif
> diff --git a/src/openvpn/syshead.h b/src/openvpn/syshead.h
> index 2b4c49f..e7cbf25 100644
> --- a/src/openvpn/syshead.h
> +++ b/src/openvpn/syshead.h
> @@ -39,6 +39,7 @@
>  #ifdef _WIN32
>  #include <windows.h>
>  #include <winsock2.h>
> +#include <tlhelp32.h>
>  #define sleep(x) Sleep((x)*1000)
>  #define random rand
>  #define srandom srand
> diff --git a/src/openvpn/tun.c b/src/openvpn/tun.c
> index e929b50..5fd8339 100644
> --- a/src/openvpn/tun.c
> +++ b/src/openvpn/tun.c
> @@ -788,8 +788,8 @@ init_tun_post(struct tuntap *tt,
>  {
>      tt->options = *options;
>  #ifdef _WIN32
> -    overlapped_io_init(&tt->reads, frame, FALSE, true);
> -    overlapped_io_init(&tt->writes, frame, TRUE, true);
> +    overlapped_io_init(&tt->reads, frame, true, true, tt->wintun);
> +    overlapped_io_init(&tt->writes, frame, false, true, tt->wintun);
>      tt->rw_handle.read = tt->reads.overlapped.hEvent;
>      tt->rw_handle.write = tt->writes.overlapped.hEvent;
>      tt->adapter_index = TUN_ADAPTER_INDEX_INVALID;
> @@ -3430,7 +3430,7 @@ tun_finalize(
>  }
>
>  const struct tap_reg *
> -get_tap_reg(struct gc_arena *gc)
> +get_tap_reg(struct gc_arena *gc, bool wintun)
>  {
>      HKEY adapter_key;
>      LONG status;
> @@ -3461,6 +3461,20 @@ get_tap_reg(struct gc_arena *gc)
>          char net_cfg_instance_id_string[] = "NetCfgInstanceId";
>          char net_cfg_instance_id[256];
>          DWORD data_type;
> +        char expected_component_id[256];
> +        char expected_root_component_id[256];
> +        char net_luid_index_string[] = "NetLuidIndex";
> +        DWORD luid_index;
> +
> +        if (wintun)
> +        {
> +            strcpy(expected_component_id, WINTUN_COMPONENT_ID);
> +        }
> +        else
> +        {
> +            strcpy(expected_component_id, TAP_WIN_COMPONENT_ID);
> +        }
> +        openvpn_snprintf(expected_root_component_id,
> sizeof(expected_root_component_id), "root\\%s", expected_component_id);
>
>          len = sizeof(enum_name);
>          status = RegEnumKeyEx(
> @@ -3525,13 +3539,27 @@ get_tap_reg(struct gc_arena *gc)
>
>                  if (status == ERROR_SUCCESS && data_type == REG_SZ)
>                  {
> -                    if (!strcmp(component_id, TAP_WIN_COMPONENT_ID) ||
> -                        !strcmp(component_id, "root\\"
> TAP_WIN_COMPONENT_ID))
> +                    if (!strcmp(component_id, expected_component_id) ||
> +                        !strcmp(component_id, expected_root_component_id))
>                      {
>                          struct tap_reg *reg;
>                          ALLOC_OBJ_CLEAR_GC(reg, struct tap_reg, gc);
>                          reg->guid = string_alloc(net_cfg_instance_id, gc);
>
> +                        len = sizeof(luid_index);
> +                        status = RegQueryValueEx(
> +                            unit_key,
> +                            net_luid_index_string,
> +                            NULL,
> +                            &data_type,
> +                            (LPBYTE)&luid_index,
> +                            &len);
> +
> +                        if (status == ERROR_SUCCESS && data_type ==
> REG_DWORD)
> +                        {
> +                            reg->luid_index = luid_index;
> +                        }
> +
>                          /* link into return list */
>                          if (!first)
>                          {
> @@ -3744,7 +3772,7 @@ show_valid_win32_tun_subnets(void)
>  }
>
>  void
> -show_tap_win_adapters(int msglev, int warnlev)
> +show_tap_win_adapters(int msglev, int warnlev, bool wintun)
>  {
>      struct gc_arena gc = gc_new();
>
> @@ -3758,10 +3786,10 @@ show_tap_win_adapters(int msglev, int warnlev)
>      const struct tap_reg *tr1;
>      const struct panel_reg *pr;
>
> -    const struct tap_reg *tap_reg = get_tap_reg(&gc);
> +    const struct tap_reg *tap_reg = get_tap_reg(&gc, wintun);
>      const struct panel_reg *panel_reg = get_panel_reg(&gc);
>
> -    msg(msglev, "Available TAP-WIN32 adapters [name, GUID]:");
> +    msg(msglev, "Available %s adapters [name, GUID]:", wintun ? "WinTun"
> : "TAP-WIN32");
>
>      /* loop through each TAP-Windows adapter registry entry */
>      for (tr = tap_reg; tr != NULL; tr = tr->next)
> @@ -3883,13 +3911,14 @@ at_least_one_tap_win(const struct tap_reg *tap_reg)
>  }
>
>  /*
> - * Get an adapter GUID and optional actual_name from the
> + * Get an adapter GUID, LUID index 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,
> +                            int *luid_index,
>                              const struct tap_reg *tap_reg_src,
>                              const struct panel_reg *panel_reg_src,
>                              struct gc_arena *gc)
> @@ -3938,6 +3967,11 @@ get_unspecified_device_guid(const int device_number,
>          }
>      }
>
> +    if (luid_index)
> +    {
> +        *luid_index = tap_reg->luid_index;
> +    }
> +
>      /* Save GUID for return value */
>      ret = alloc_buf_gc(256, gc);
>      buf_printf(&ret, "%s", tap_reg->guid);
> @@ -4536,7 +4570,7 @@ get_adapter_index_flexible(const char *name)  /*
> actual name or GUID */
>      }
>      if (index == TUN_ADAPTER_INDEX_INVALID)
>      {
> -        const struct tap_reg *tap_reg = get_tap_reg(&gc);
> +        const struct tap_reg *tap_reg = get_tap_reg(&gc, false);
>          const struct panel_reg *panel_reg = get_panel_reg(&gc);
>          const char *guid = name_to_guid(name, tap_reg, panel_reg);
>          index = get_adapter_index_method_1(guid);
> @@ -4663,7 +4697,7 @@ void
>  tap_allow_nonadmin_access(const char *dev_node)
>  {
>      struct gc_arena gc = gc_new();
> -    const struct tap_reg *tap_reg = get_tap_reg(&gc);
> +    const struct tap_reg *tap_reg = get_tap_reg(&gc, false);
>      const struct panel_reg *panel_reg = get_panel_reg(&gc);
>      const char *device_guid = NULL;
>      HANDLE hand;
> @@ -4717,6 +4751,7 @@ tap_allow_nonadmin_access(const char *dev_node)
>                                                        actual_buffer,
>
>  sizeof(actual_buffer),
>                                                        tap_reg,
> +                                                      NULL,
>                                                        panel_reg,
>                                                        &gc);
>
> @@ -5239,7 +5274,7 @@ out:
>  static const char *
>  netsh_get_id(const char *dev_node, struct gc_arena *gc)
>  {
> -    const struct tap_reg *tap_reg = get_tap_reg(gc);
> +    const struct tap_reg *tap_reg = get_tap_reg(gc, false);
>      const struct panel_reg *panel_reg = get_panel_reg(gc);
>      struct buffer actual = alloc_buf_gc(256, gc);
>      const char *guid;
> @@ -5252,9 +5287,9 @@ netsh_get_id(const char *dev_node, struct gc_arena
> *gc)
>      }
>      else
>      {
> -        guid = get_unspecified_device_guid(0, BPTR(&actual),
> BCAP(&actual), tap_reg, panel_reg, gc);
> +        guid = get_unspecified_device_guid(0, BPTR(&actual),
> BCAP(&actual), NULL, tap_reg, panel_reg, gc);
>
> -        if (get_unspecified_device_guid(1, NULL, 0, tap_reg, panel_reg,
> gc)) /* ambiguous if more than one TAP-Windows adapter */
> +        if (get_unspecified_device_guid(1, NULL, 0, NULL, tap_reg,
> panel_reg, gc)) /* ambiguous if more than one TAP-Windows adapter */
>          {
>              guid = NULL;
>          }
> @@ -5528,6 +5563,7 @@ open_tun(const char *dev, const char *dev_type,
> const char *dev_node, struct tun
>      struct gc_arena gc = gc_new();
>      char device_path[256];
>      const char *device_guid = NULL;
> +    DWORD device_luid_index = 0;
>      DWORD len;
>      bool dhcp_masq = false;
>      bool dhcp_masq_post = false;
> @@ -5554,7 +5590,7 @@ open_tun(const char *dev, const char *dev_type,
> const char *dev_node, struct tun
>       * 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 tap_reg *tap_reg = get_tap_reg(&gc, tt->wintun);
>          const struct panel_reg *panel_reg = get_panel_reg(&gc);
>          char actual_buffer[256];
>
> @@ -5601,6 +5637,7 @@ open_tun(const char *dev, const char *dev_type,
> const char *dev_node, struct tun
>                  device_guid = get_unspecified_device_guid(device_number,
>                                                            actual_buffer,
>
>  sizeof(actual_buffer),
> +
> &device_luid_index,
>                                                            tap_reg,
>                                                            panel_reg,
>                                                            &gc);
> @@ -5610,11 +5647,28 @@ open_tun(const char *dev, const char *dev_type,
> const char *dev_node, struct tun
>                      msg(M_FATAL, "All TAP-Windows adapters on this system
> are currently in use.");
>                  }
>
> -                /* Open Windows TAP-Windows adapter */
> -                openvpn_snprintf(device_path, sizeof(device_path),
> "%s%s%s",
> -                                 USERMODEDEVICEDIR,
> -                                 device_guid,
> -                                 TAP_WIN_SUFFIX);
> +                /* Open wintun / tap-windows6 adapter */
> +                if (tt->wintun)
> +                {
> +                    openvpn_snprintf(device_path, sizeof(device_path),
> "%sWINTUN%lu",
> +                                     USERMODEDEVICEDIR,
> +                                     device_luid_index);
> +                }
> +                else
> +                {
> +                    openvpn_snprintf(device_path, sizeof(device_path),
> "%s%s%s",
> +                                     USERMODEDEVICEDIR,
> +                                     device_guid,
> +                                     TAP_WIN_SUFFIX);
> +                }
> +
> +                if (tt->wintun)
> +                {
> +                    if (!impersonate_as_system())
> +                    {
> +                        msg(M_FATAL, "ERROR:  Failed to impersonate as
> SYSTEM, make sure process is running under privileged account");
> +                    }
> +                }
>
>                  tt->hand = CreateFile(
>                      device_path,
> @@ -5626,6 +5680,14 @@ open_tun(const char *dev, const char *dev_type,
> const char *dev_node, struct tun
>                      0
>                      );
>
> +                if (tt->wintun)
> +                {
> +                    if (!RevertToSelf())
> +                    {
> +                        msg(M_FATAL, "ERROR:  RevertToSelf error: %lu",
> GetLastError());
> +                    }
> +                }
> +
>                  if (tt->hand == INVALID_HANDLE_VALUE)
>                  {
>                      msg(D_TUNTAP_INFO, "CreateFile failed on TAP device:
> %s", device_path);
> @@ -5644,12 +5706,14 @@ open_tun(const char *dev, const char *dev_type,
> const char *dev_node, struct tun
>          tt->actual_name = string_alloc(actual_buffer, NULL);
>      }
>
> -    msg(M_INFO, "TAP-WIN32 device [%s] opened: %s", tt->actual_name,
> device_path);
> +    msg(M_INFO, "%s device device [%s] opened: %s", tt->wintun ? "Wintun"
> : "TAP - WIN32", tt->actual_name, device_path);
>      tt->adapter_index = get_adapter_index(device_guid);
>
> -    /* get driver version info */
> +    if (!tt->wintun)
>      {
> +        /* get driver version info */
>          ULONG info[3];
> +        ULONG mtu;
>          CLEAR(info);
>          if (DeviceIoControl(tt->hand, TAP_WIN_IOCTL_GET_VERSION,
>                              &info, sizeof(info),
> @@ -5684,11 +5748,8 @@ open_tun(const char *dev, const char *dev_type,
> const char *dev_node, struct tun
>          {
>              msg( M_FATAL, "ERROR:  Tap-Win32 driver version %d.%d is
> buggy regarding small IPv4 packets in TUN mode. Upgrade your Tap-Win32
> driver.", (int) info[0], (int) info[1] );
>          }
> -    }
>
> -    /* get driver MTU */
> -    {
> -        ULONG mtu;
> +        /* get driver MTU */
>          if (DeviceIoControl(tt->hand, TAP_WIN_IOCTL_GET_MTU,
>                              &mtu, sizeof(mtu),
>                              &mtu, sizeof(mtu), &len, NULL))
> @@ -5746,7 +5807,7 @@ open_tun(const char *dev, const char *dev_type,
> const char *dev_node, struct tun
>
>      /* set point-to-point mode if TUN device */
>
> -    if (tt->type == DEV_TYPE_TUN)
> +    if (tt->type == DEV_TYPE_TUN && !tt->wintun)
>      {
>          if (!tt->did_ifconfig_setup && !tt->did_ifconfig_ipv6_setup)
>          {
> @@ -5834,71 +5895,80 @@ open_tun(const char *dev, const char *dev_type,
> const char *dev_node, struct tun
>              ep[2] = dhcp_masq_addr(tt->local, tt->adapter_netmask,
> tt->options.dhcp_masq_custom_offset ? tt->options.dhcp_masq_offset : 0);
>          }
>
> -        /* lease time in seconds */
> -        ep[3] = (uint32_t) tt->options.dhcp_lease_time;
> +        if (!tt->wintun)
> +        {
> +            /* lease time in seconds */
> +            ep[3] = (uint32_t)tt->options.dhcp_lease_time;
>
> -        ASSERT(ep[3] > 0);
> +            ASSERT(ep[3] > 0);
>
>  #ifndef SIMULATE_DHCP_FAILED /* this code is disabled to simulate bad
> DHCP negotiation */
> -        if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_DHCP_MASQ,
> -                             ep, sizeof(ep),
> -                             ep, sizeof(ep), &len, NULL))
> -        {
> -            msg(M_FATAL, "ERROR: The TAP-Windows driver rejected a
> DeviceIoControl call to set TAP_WIN_IOCTL_CONFIG_DHCP_MASQ mode");
> -        }
> +            if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_DHCP_MASQ,
> +                ep, sizeof(ep),
> +                ep, sizeof(ep), &len, NULL))
> +            {
> +                msg(M_FATAL, "ERROR: The TAP-Windows driver rejected a
> DeviceIoControl call to set TAP_WIN_IOCTL_CONFIG_DHCP_MASQ mode");
> +            }
>
> -        msg(M_INFO, "Notified TAP-Windows 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]
> +            msg(M_INFO, "Notified TAP-Windows 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);
> -            if (build_dhcp_options_string(&buf, &tt->options))
> +            /* user-supplied DHCP options capability */
> +            if (tt->options.dhcp_options)
>              {
> -                msg(D_DHCP_OPT, "DHCP option string: %s",
> format_hex(BPTR(&buf), BLEN(&buf), 0, &gc));
> -                if (!DeviceIoControl(tt->hand,
> TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT,
> -                                     BPTR(&buf), BLEN(&buf),
> -                                     BPTR(&buf), BLEN(&buf), &len, NULL))
> +                struct buffer buf = alloc_buf(256);
> +                if (build_dhcp_options_string(&buf, &tt->options))
>                  {
> -                    msg(M_FATAL, "ERROR: The TAP-Windows driver rejected
> a TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT DeviceIoControl call");
> +                    msg(D_DHCP_OPT, "DHCP option string: %s",
> format_hex(BPTR(&buf), BLEN(&buf), 0, &gc));
> +                    if (!DeviceIoControl(tt->hand,
> TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT,
> +                        BPTR(&buf), BLEN(&buf),
> +                        BPTR(&buf), BLEN(&buf), &len, NULL))
> +                    {
> +                        msg(M_FATAL, "ERROR: The TAP-Windows driver
> rejected a TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT DeviceIoControl call");
> +                    }
>                  }
> +                else
> +                {
> +                    msg(M_WARN, "DHCP option string not set due to
> error");
> +                }
> +                free_buf(&buf);
>              }
> -            else
> -            {
> -                msg(M_WARN, "DHCP option string not set due to error");
> -            }
> -            free_buf(&buf);
> -        }
>  #endif /* ifndef SIMULATE_DHCP_FAILED */
> +        }
> +
> +
>      }
>
> -    /* set driver media status to 'connected' */
> +    if (!tt->wintun)
>      {
> -        ULONG status = TRUE;
> -        if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_SET_MEDIA_STATUS,
> -                             &status, sizeof(status),
> -                             &status, sizeof(status), &len, NULL))
> +        /* set driver media status to 'connected' */
>          {
> -            msg(M_WARN, "WARNING: The TAP-Windows driver rejected a
> TAP_WIN_IOCTL_SET_MEDIA_STATUS DeviceIoControl call.");
> +            ULONG status = TRUE;
> +            if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_SET_MEDIA_STATUS,
> +                &status, sizeof(status),
> +                &status, sizeof(status), &len, NULL))
> +            {
> +              msg(M_WARN, "WARNING: The TAP-Windows driver rejected a
> TAP_WIN_IOCTL_SET_MEDIA_STATUS DeviceIoControl call.");
> +            }
>          }
> -    }
>
> -    /* possible wait for adapter to come up */
> -    {
> -        int s = tt->options.tap_sleep;
> -        if (s > 0)
> +        /* possible wait for adapter to come up */
>          {
> -            msg(M_INFO, "Sleeping for %d seconds...", s);
> -            management_sleep(s);
> +            int s = tt->options.tap_sleep;
> +            if (s > 0)
> +            {
> +                msg(M_INFO, "Sleeping for %d seconds...", s);
> +                management_sleep(s);
> +            }
>          }
>      }
>
> +
>      /* possibly use IP Helper API to set IP address on adapter */
>      {
>          const DWORD index = tt->adapter_index;
> diff --git a/src/openvpn/tun.h b/src/openvpn/tun.h
> index 9ed8ef0..6591d59 100644
> --- a/src/openvpn/tun.h
> +++ b/src/openvpn/tun.h
> @@ -37,6 +37,10 @@
>  #include "proto.h"
>  #include "misc.h"
>
> +#ifdef _WIN32
> +#define WINTUN_COMPONENT_ID "wintun"
> +#endif
> +
>  #if defined(_WIN32) || defined(TARGET_ANDROID)
>
>  #define TUN_ADAPTER_INDEX_INVALID ((DWORD)-1)
> @@ -174,6 +178,9 @@ struct tuntap
>       * ~0 if undefined */
>      DWORD adapter_index;
>
> +    bool wintun; /* true if wintun is used instead of tap-windows6 */
> +    char wintun_padding[16];
> +
>      int standby_iter;
>  #else  /* ifdef _WIN32 */
>      int fd; /* file descriptor for TUN/TAP dev */
> @@ -337,6 +344,7 @@ route_order(void)
>  struct tap_reg
>  {
>      const char *guid;
> +    DWORD luid_index;
>      struct tap_reg *next;
>  };
>
> @@ -374,7 +382,7 @@ DWORD adapter_index_of_ip(const IP_ADAPTER_INFO *list,
>                            int *count,
>                            in_addr_t *netmask);
>
> -void show_tap_win_adapters(int msglev, int warnlev);
> +void show_tap_win_adapters(int msglev, int warnlev, bool wintun);
>
>  void show_adapters(int msglev);
>
> @@ -465,6 +473,30 @@ read_tun_buffered(struct tuntap *tt, struct buffer
> *buf)
>  static inline int
>  write_tun_buffered(struct tuntap *tt, struct buffer *buf)
>  {
> +    if (tt->wintun)
> +    {
> +        int len = BLEN(buf);
> +
> +        /* variable len end padding */
> +        int end_padding_len = 16 - (len & 15);
> +        if (!buf_write(buf, tt->wintun_padding, end_padding_len))
> +        {
> +            return -1;
> +        }
> +
> +        /* 12 bytes start padding */
> +        if (!buf_write_prepend(buf, tt->wintun_padding, 12))
> +        {
> +            return -1;
> +        }
> +
> +        /* 4 bytes size */
> +        if (!buf_write_prepend(buf, &len, 4))
> +        {
> +            return -1;
> +        }
> +    }
> +
>      return tun_write_win32(tt, buf);
>  }
>
> diff --git a/src/openvpn/win32.c b/src/openvpn/win32.c
> index eb4c030..4b4a2b1 100644
> --- a/src/openvpn/win32.c
> +++ b/src/openvpn/win32.c
> @@ -164,20 +164,34 @@ init_security_attributes_allow_all(struct
> security_attributes *obj)
>  void
>  overlapped_io_init(struct overlapped_io *o,
>                     const struct frame *frame,
> -                   BOOL event_state,
> -                   bool tuntap_buffer)  /* if true: tuntap buffer, if
> false: socket buffer */
> +                   bool reads, /* if true: reads buffer, if false: writes
> buffer */
> +                   bool tuntap_buffer,  /* if true: tuntap buffer, if
> false: socket buffer */
> +                   bool wintun)
>  {
>      CLEAR(*o);
>
>      /* manual reset event, initially set according to event_state */
> -    o->overlapped.hEvent = CreateEvent(NULL, TRUE, event_state, NULL);
> +    o->overlapped.hEvent = CreateEvent(NULL, TRUE, reads ? FALSE : TRUE,
> NULL);
>      if (o->overlapped.hEvent == NULL)
>      {
>          msg(M_ERR, "Error: overlapped_io_init: CreateEvent failed");
>      }
>
> -    /* allocate buffer for overlapped I/O */
> -    alloc_buf_sock_tun(&o->buf_init, frame, tuntap_buffer, 0);
> +    if (wintun && tuntap_buffer && reads)
> +    {
> +        /*
> +         * wintun could return up to 256 packets,
> +         * each packet is accompanied with size, start and end padding
> +        */
> +        int buf_size = (MAX_RW_SIZE_TUN(frame) + 4 + 12 + 15) * 256;
> +        o->buf_init = alloc_buf(buf_size);
> +        o->buf_init.len = buf_size;
> +    }
> +    else
> +    {
> +        /* allocate buffer for overlapped I/O */
> +        alloc_buf_sock_tun(&o->buf_init, frame, tuntap_buffer, 0);
> +    }
>  }
>
>  void
> @@ -1493,4 +1507,99 @@ send_msg_iservice(HANDLE pipe, const void *data,
> size_t size,
>      return ret;
>  }
>
> +bool
> +impersonate_as_system()
> +{
> +    HANDLE thread_token, process_snapshot, winlogon_process,
> winlogon_token, duplicated_token, file_handle;
> +    PROCESSENTRY32 entry;
> +    BOOL ret;
> +    DWORD pid = 0;
> +    TOKEN_PRIVILEGES privileges;
> +
> +    CLEAR(entry);
> +    CLEAR(privileges);
> +
> +    entry.dwSize = sizeof(PROCESSENTRY32);
> +
> +    privileges.PrivilegeCount = 1;
> +    privileges.Privileges->Attributes = SE_PRIVILEGE_ENABLED;
> +
> +    if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME,
> &privileges.Privileges[0].Luid))
> +    {
> +        return false;
> +    }
> +
> +    if (!ImpersonateSelf(SecurityImpersonation))
> +    {
> +        return false;
> +    }
> +
> +    if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES,
> FALSE, &thread_token))
> +    {
> +        RevertToSelf();
> +        return false;
> +    }
> +    if (!AdjustTokenPrivileges(thread_token, FALSE, &privileges,
> sizeof(privileges), NULL, NULL))
> +    {
> +        CloseHandle(thread_token);
> +        RevertToSelf();
> +        return false;
> +    }
> +    CloseHandle(thread_token);
> +
> +    process_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
> +    if (process_snapshot == INVALID_HANDLE_VALUE)
> +    {
> +        RevertToSelf();
> +        return false;
> +    }
> +    for (ret = Process32First(process_snapshot, &entry); ret; ret =
> Process32Next(process_snapshot, &entry))
> +    {
> +        if (!_stricmp(entry.szExeFile, "winlogon.exe"))
> +        {
> +            pid = entry.th32ProcessID;
> +            break;
> +        }
> +    }
> +    CloseHandle(process_snapshot);
> +    if (!pid)
> +    {
> +        RevertToSelf();
> +        return false;
> +    }
> +
> +    winlogon_process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
> +    if (!winlogon_process)
> +    {
> +        RevertToSelf();
> +        return false;
> +    }
> +
> +    if (!OpenProcessToken(winlogon_process, TOKEN_IMPERSONATE |
> TOKEN_DUPLICATE, &winlogon_token))
> +    {
> +        CloseHandle(winlogon_process);
> +        RevertToSelf();
> +        return false;
> +    }
> +    CloseHandle(winlogon_process);
> +
> +    if (!DuplicateToken(winlogon_token, SecurityImpersonation,
> &duplicated_token))
> +    {
> +        CloseHandle(winlogon_token);
> +        RevertToSelf();
> +        return false;
> +    }
> +    CloseHandle(winlogon_token);
> +
> +    if (!SetThreadToken(NULL, duplicated_token))
> +    {
> +        CloseHandle(duplicated_token);
> +        RevertToSelf();
> +        return false;
> +    }
> +    CloseHandle(duplicated_token);
> +
> +    return true;
> +}
> +
>  #endif /* ifdef _WIN32 */
> diff --git a/src/openvpn/win32.h b/src/openvpn/win32.h
> index 4814bbc..e511b56 100644
> --- a/src/openvpn/win32.h
> +++ b/src/openvpn/win32.h
> @@ -212,10 +212,12 @@ struct overlapped_io {
>      struct buffer buf;
>  };
>
> -void overlapped_io_init(struct overlapped_io *o,
> -                        const struct frame *frame,
> -                        BOOL event_state,
> -                        bool tuntap_buffer);
> +void
> +overlapped_io_init(struct overlapped_io *o,
> +                   const struct frame *frame,
> +                   bool reads, /* if true: reads buffer, if false: writes
> buffer */
> +                   bool tuntap_buffer,  /* if true: tuntap buffer, if
> false: socket buffer */
> +                   bool wintun);
>
>  void overlapped_io_close(struct overlapped_io *o);
>
> @@ -323,5 +325,14 @@ bool send_msg_iservice(HANDLE pipe, const void *data,
> size_t size,
>  int
>  openvpn_execve(const struct argv *a, const struct env_set *es, const
> unsigned int flags);
>
> +/**
> + * Impersonates current thread as SYSTEM, required
> + * to open Wintun device.
> + *
> + * @returns     True if it succeeds, false if it fails.
> + */
> +bool
> +impersonate_as_system();
> +
>  #endif /* ifndef OPENVPN_WIN32_H */
>  #endif /* ifdef _WIN32 */
> --
> 2.7.4
>
>

Patch

diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c
index ad4c1f5..8077835 100644
--- a/src/openvpn/forward.c
+++ b/src/openvpn/forward.c
@@ -2182,6 +2182,52 @@  io_wait_dowork(struct context *c, const unsigned int flags)
     dmsg(D_EVENT_WAIT, "I/O WAIT status=0x%04x", c->c2.event_set_status);
 }
 
+#ifdef _WIN32
+/**
+ * This processes single packet from wintun buffer,
+ * which can contain up to 256 packets.
+ */
+static void
+process_incoming_wintun(struct context *c)
+{
+    struct buffer *buf = &c->c2.wintun_buf;
+    uint32_t *size = NULL;
+    int end_padding_len = 0;
+
+    /* get packet size */
+    size = (uint32_t *)buf_read_alloc(buf, 4);
+    if (size == NULL)
+    {
+        return;
+    }
+
+    /* skip start padding */
+    if (!buf_advance(buf, 12))
+    {
+        return;
+    }
+
+    /* copy packet */
+    c->c2.buf = c->c2.buffers->read_tun_buf;
+    buf_init(&c->c2.buf, FRAME_HEADROOM(&c->c2.frame));
+    buf_safe(&c->c2.buf, MAX_RW_SIZE_TUN(&c->c2.frame));
+    if (!buf_copy_n(&c->c2.buf, buf, (int)*size))
+    {
+        return;
+    }
+
+    /* this encrypts packet and puts it to c->c2.to_link buffer */
+    process_incoming_tun(c);
+
+    /* skip end padding */
+    end_padding_len = (16 - (*size & 15)) % 16;
+    if (!buf_advance(buf, end_padding_len))
+    {
+        return;
+    }
+}
+#endif
+
 void
 process_io(struct context *c)
 {
@@ -2217,10 +2263,34 @@  process_io(struct context *c)
     /* Incoming data on TUN device */
     else if (status & TUN_READ)
     {
-        read_incoming_tun(c);
-        if (!IS_SIG(c))
+#ifdef _WIN32
+        if (c->options.wintun)
+        {
+            /* only read from driver if there are no packets in buffer */
+            if (BLEN(&c->c2.wintun_buf) == 0)
+            {
+                read_incoming_tun(c);
+                c->c2.wintun_buf = c->c2.buf;
+            }
+            if (!IS_SIG(c))
+            {
+                process_incoming_wintun(c);
+            }
+
+            /*
+             * We need to write processed wintun packet to link,
+             * hence we give control back to event loop, which will
+             * call us again if wintun buffer is not empty
+             */
+        }
+        else
+#endif
         {
-            process_incoming_tun(c);
+            read_incoming_tun(c);
+            if (!IS_SIG(c))
+            {
+                process_incoming_tun(c);
+            }
         }
     }
 }
diff --git a/src/openvpn/forward.h b/src/openvpn/forward.h
index 48202c0..ac3e932 100644
--- a/src/openvpn/forward.h
+++ b/src/openvpn/forward.h
@@ -61,6 +61,7 @@ 
 #define IOW_MBUF            (1<<7)
 #define IOW_READ_TUN_FORCE  (1<<8)
 #define IOW_WAIT_SIGNAL     (1<<9)
+#define IOW_READ_WINTUN_BUF (1<<10)
 
 #define IOW_READ            (IOW_READ_TUN|IOW_READ_LINK)
 
@@ -375,6 +376,12 @@  p2p_iow_flags(const struct context *c)
     {
         flags |= IOW_TO_TUN;
     }
+#ifdef _WIN32
+    if (c->c2.wintun_buf.len > 0)
+    {
+        flags |= IOW_READ_WINTUN_BUF;
+    }
+#endif
     return flags;
 }
 
@@ -401,6 +408,14 @@  io_wait(struct context *c, const unsigned int flags)
         }
         c->c2.event_set_status = ret;
     }
+#ifdef _WIN32
+    /* make sure we write to link before reading from wintun buffer,
+     * otherwise we'll overwrite c2->to_link buffer and lose a packet */
+    else if (!(flags & IOW_TO_LINK) && (flags & IOW_READ_WINTUN_BUF))
+    {
+        c->c2.event_set_status = TUN_READ;
+    }
+#endif
     else
     {
         /* slow path */
diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index ef26503..1625272 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -1696,6 +1696,10 @@  do_init_tun(struct context *c)
                             !c->options.ifconfig_nowarn,
                             c->c2.es);
 
+#ifdef _WIN32
+    c->c1.tuntap->wintun = c->options.wintun;
+#endif
+
     init_tun_post(c->c1.tuntap,
                   &c->c2.frame,
                   &c->options.tuntap_options);
@@ -1738,6 +1742,9 @@  do_open_tun(struct context *c)
     /* store (hide) interactive service handle in tuntap_options */
     c->c1.tuntap->options.msg_channel = c->options.msg_channel;
     msg(D_ROUTE, "interactive service msg_channel=%u", (unsigned int) c->options.msg_channel);
+
+    c->c1.tuntap->wintun = c->options.wintun;
+
 #endif
 
     /* allocate route list structure */
diff --git a/src/openvpn/openvpn.h b/src/openvpn/openvpn.h
index d11f61d..d694666 100644
--- a/src/openvpn/openvpn.h
+++ b/src/openvpn/openvpn.h
@@ -382,6 +382,10 @@  struct context_2
     struct buffer buf;
     struct buffer to_tun;
     struct buffer to_link;
+#ifdef _WIN32
+    /* contains up to 256 packets */
+    struct buffer wintun_buf;
+#endif
 
     /* should we print R|W|r|w to console on packet transfers? */
     bool log_rw;
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index e34b65b..534a6be 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -845,6 +845,7 @@  init_options(struct options *o, const bool init_gc)
     o->tuntap_options.dhcp_masq_offset = 0;     /* use network address as internal DHCP server address */
     o->route_method = ROUTE_METHOD_ADAPTIVE;
     o->block_outside_dns = false;
+    o->wintun = false;
 #endif
 #if P2MP_SERVER
     o->real_hash_size = 256;
@@ -2937,6 +2938,12 @@  options_postprocess_mutate_invariant(struct options *options)
         options->ifconfig_noexec = false;
     }
 
+    /* for wintun kernel doesn't send DHCP requests, so use ipapi to set IP address and netmask */
+    if (options->wintun)
+    {
+        options->tuntap_options.ip_win32_type = IPW32_SET_IPAPI;
+    }
+
     remap_redirect_gateway_flags(options);
 #endif
 
@@ -3980,6 +3987,26 @@  foreign_option(struct options *o, char *argv[], int len, struct env_set *es)
     }
 }
 
+#ifdef _WIN32
+bool
+parse_windows_driver(const char *str, const int msglevel)
+{
+    if (streq(str, "tap"))
+    {
+        return false;
+    }
+    else if (streq(str, "wintun"))
+    {
+        return true;
+    }
+    else
+    {
+        msg(msglevel, "--windows-driver must be tap-windows6 or wintun");
+        return false;
+    }
+}
+#endif
+
 /*
  * parse/print topology coding
  */
@@ -5220,6 +5247,13 @@  add_option(struct options *options,
         VERIFY_PERMISSION(OPT_P_GENERAL);
         options->dev_type = p[1];
     }
+#ifdef _WIN32
+    else if (streq(p[0], "windows-driver") && p[1] && !p[2])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL);
+        options->wintun = parse_windows_driver(p[1], M_FATAL);
+    }
+#endif
     else if (streq(p[0], "dev-node") && p[1] && !p[2])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
@@ -7217,7 +7251,7 @@  add_option(struct options *options,
     else if (streq(p[0], "show-adapters") && !p[1])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
-        show_tap_win_adapters(M_INFO|M_NOPREFIX, M_WARN|M_NOPREFIX);
+        show_tap_win_adapters(M_INFO|M_NOPREFIX, M_WARN|M_NOPREFIX, options->wintun);
         openvpn_exit(OPENVPN_EXIT_STATUS_GOOD); /* exit point */
     }
     else if (streq(p[0], "show-net") && !p[1])
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index e2b3893..670b70a 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -614,6 +614,7 @@  struct options
     bool show_net_up;
     int route_method;
     bool block_outside_dns;
+    bool wintun;
 #endif
 
     bool use_peer_id;
diff --git a/src/openvpn/socket.c b/src/openvpn/socket.c
index c472cf1..4352869 100644
--- a/src/openvpn/socket.c
+++ b/src/openvpn/socket.c
@@ -1635,8 +1635,8 @@  static void
 socket_frame_init(const struct frame *frame, struct link_socket *sock)
 {
 #ifdef _WIN32
-    overlapped_io_init(&sock->reads, frame, FALSE, false);
-    overlapped_io_init(&sock->writes, frame, TRUE, false);
+    overlapped_io_init(&sock->reads, frame, true, false, false);
+    overlapped_io_init(&sock->writes, frame, false, false, false);
     sock->rw_handle.read = sock->reads.overlapped.hEvent;
     sock->rw_handle.write = sock->writes.overlapped.hEvent;
 #endif
diff --git a/src/openvpn/syshead.h b/src/openvpn/syshead.h
index 2b4c49f..e7cbf25 100644
--- a/src/openvpn/syshead.h
+++ b/src/openvpn/syshead.h
@@ -39,6 +39,7 @@ 
 #ifdef _WIN32
 #include <windows.h>
 #include <winsock2.h>
+#include <tlhelp32.h>
 #define sleep(x) Sleep((x)*1000)
 #define random rand
 #define srandom srand
diff --git a/src/openvpn/tun.c b/src/openvpn/tun.c
index e929b50..5fd8339 100644
--- a/src/openvpn/tun.c
+++ b/src/openvpn/tun.c
@@ -788,8 +788,8 @@  init_tun_post(struct tuntap *tt,
 {
     tt->options = *options;
 #ifdef _WIN32
-    overlapped_io_init(&tt->reads, frame, FALSE, true);
-    overlapped_io_init(&tt->writes, frame, TRUE, true);
+    overlapped_io_init(&tt->reads, frame, true, true, tt->wintun);
+    overlapped_io_init(&tt->writes, frame, false, true, tt->wintun);
     tt->rw_handle.read = tt->reads.overlapped.hEvent;
     tt->rw_handle.write = tt->writes.overlapped.hEvent;
     tt->adapter_index = TUN_ADAPTER_INDEX_INVALID;
@@ -3430,7 +3430,7 @@  tun_finalize(
 }
 
 const struct tap_reg *
-get_tap_reg(struct gc_arena *gc)
+get_tap_reg(struct gc_arena *gc, bool wintun)
 {
     HKEY adapter_key;
     LONG status;
@@ -3461,6 +3461,20 @@  get_tap_reg(struct gc_arena *gc)
         char net_cfg_instance_id_string[] = "NetCfgInstanceId";
         char net_cfg_instance_id[256];
         DWORD data_type;
+        char expected_component_id[256];
+        char expected_root_component_id[256];
+        char net_luid_index_string[] = "NetLuidIndex";
+        DWORD luid_index;
+
+        if (wintun)
+        {
+            strcpy(expected_component_id, WINTUN_COMPONENT_ID);
+        }
+        else
+        {
+            strcpy(expected_component_id, TAP_WIN_COMPONENT_ID);
+        }
+        openvpn_snprintf(expected_root_component_id, sizeof(expected_root_component_id), "root\\%s", expected_component_id);
 
         len = sizeof(enum_name);
         status = RegEnumKeyEx(
@@ -3525,13 +3539,27 @@  get_tap_reg(struct gc_arena *gc)
 
                 if (status == ERROR_SUCCESS && data_type == REG_SZ)
                 {
-                    if (!strcmp(component_id, TAP_WIN_COMPONENT_ID) ||
-                        !strcmp(component_id, "root\\" TAP_WIN_COMPONENT_ID))
+                    if (!strcmp(component_id, expected_component_id) ||
+                        !strcmp(component_id, expected_root_component_id))
                     {
                         struct tap_reg *reg;
                         ALLOC_OBJ_CLEAR_GC(reg, struct tap_reg, gc);
                         reg->guid = string_alloc(net_cfg_instance_id, gc);
 
+                        len = sizeof(luid_index);
+                        status = RegQueryValueEx(
+                            unit_key,
+                            net_luid_index_string,
+                            NULL,
+                            &data_type,
+                            (LPBYTE)&luid_index,
+                            &len);
+
+                        if (status == ERROR_SUCCESS && data_type == REG_DWORD)
+                        {
+                            reg->luid_index = luid_index;
+                        }
+
                         /* link into return list */
                         if (!first)
                         {
@@ -3744,7 +3772,7 @@  show_valid_win32_tun_subnets(void)
 }
 
 void
-show_tap_win_adapters(int msglev, int warnlev)
+show_tap_win_adapters(int msglev, int warnlev, bool wintun)
 {
     struct gc_arena gc = gc_new();
 
@@ -3758,10 +3786,10 @@  show_tap_win_adapters(int msglev, int warnlev)
     const struct tap_reg *tr1;
     const struct panel_reg *pr;
 
-    const struct tap_reg *tap_reg = get_tap_reg(&gc);
+    const struct tap_reg *tap_reg = get_tap_reg(&gc, wintun);
     const struct panel_reg *panel_reg = get_panel_reg(&gc);
 
-    msg(msglev, "Available TAP-WIN32 adapters [name, GUID]:");
+    msg(msglev, "Available %s adapters [name, GUID]:", wintun ? "WinTun" : "TAP-WIN32");
 
     /* loop through each TAP-Windows adapter registry entry */
     for (tr = tap_reg; tr != NULL; tr = tr->next)
@@ -3883,13 +3911,14 @@  at_least_one_tap_win(const struct tap_reg *tap_reg)
 }
 
 /*
- * Get an adapter GUID and optional actual_name from the
+ * Get an adapter GUID, LUID index 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,
+                            int *luid_index,
                             const struct tap_reg *tap_reg_src,
                             const struct panel_reg *panel_reg_src,
                             struct gc_arena *gc)
@@ -3938,6 +3967,11 @@  get_unspecified_device_guid(const int device_number,
         }
     }
 
+    if (luid_index)
+    {
+        *luid_index = tap_reg->luid_index;
+    }
+
     /* Save GUID for return value */
     ret = alloc_buf_gc(256, gc);
     buf_printf(&ret, "%s", tap_reg->guid);
@@ -4536,7 +4570,7 @@  get_adapter_index_flexible(const char *name)  /* actual name or GUID */
     }
     if (index == TUN_ADAPTER_INDEX_INVALID)
     {
-        const struct tap_reg *tap_reg = get_tap_reg(&gc);
+        const struct tap_reg *tap_reg = get_tap_reg(&gc, false);
         const struct panel_reg *panel_reg = get_panel_reg(&gc);
         const char *guid = name_to_guid(name, tap_reg, panel_reg);
         index = get_adapter_index_method_1(guid);
@@ -4663,7 +4697,7 @@  void
 tap_allow_nonadmin_access(const char *dev_node)
 {
     struct gc_arena gc = gc_new();
-    const struct tap_reg *tap_reg = get_tap_reg(&gc);
+    const struct tap_reg *tap_reg = get_tap_reg(&gc, false);
     const struct panel_reg *panel_reg = get_panel_reg(&gc);
     const char *device_guid = NULL;
     HANDLE hand;
@@ -4717,6 +4751,7 @@  tap_allow_nonadmin_access(const char *dev_node)
                                                       actual_buffer,
                                                       sizeof(actual_buffer),
                                                       tap_reg,
+                                                      NULL,
                                                       panel_reg,
                                                       &gc);
 
@@ -5239,7 +5274,7 @@  out:
 static const char *
 netsh_get_id(const char *dev_node, struct gc_arena *gc)
 {
-    const struct tap_reg *tap_reg = get_tap_reg(gc);
+    const struct tap_reg *tap_reg = get_tap_reg(gc, false);
     const struct panel_reg *panel_reg = get_panel_reg(gc);
     struct buffer actual = alloc_buf_gc(256, gc);
     const char *guid;
@@ -5252,9 +5287,9 @@  netsh_get_id(const char *dev_node, struct gc_arena *gc)
     }
     else
     {
-        guid = get_unspecified_device_guid(0, BPTR(&actual), BCAP(&actual), tap_reg, panel_reg, gc);
+        guid = get_unspecified_device_guid(0, BPTR(&actual), BCAP(&actual), NULL, tap_reg, panel_reg, gc);
 
-        if (get_unspecified_device_guid(1, NULL, 0, tap_reg, panel_reg, gc)) /* ambiguous if more than one TAP-Windows adapter */
+        if (get_unspecified_device_guid(1, NULL, 0, NULL, tap_reg, panel_reg, gc)) /* ambiguous if more than one TAP-Windows adapter */
         {
             guid = NULL;
         }
@@ -5528,6 +5563,7 @@  open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tun
     struct gc_arena gc = gc_new();
     char device_path[256];
     const char *device_guid = NULL;
+    DWORD device_luid_index = 0;
     DWORD len;
     bool dhcp_masq = false;
     bool dhcp_masq_post = false;
@@ -5554,7 +5590,7 @@  open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tun
      * 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 tap_reg *tap_reg = get_tap_reg(&gc, tt->wintun);
         const struct panel_reg *panel_reg = get_panel_reg(&gc);
         char actual_buffer[256];
 
@@ -5601,6 +5637,7 @@  open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tun
                 device_guid = get_unspecified_device_guid(device_number,
                                                           actual_buffer,
                                                           sizeof(actual_buffer),
+                                                          &device_luid_index,
                                                           tap_reg,
                                                           panel_reg,
                                                           &gc);
@@ -5610,11 +5647,28 @@  open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tun
                     msg(M_FATAL, "All TAP-Windows adapters on this system are currently in use.");
                 }
 
-                /* Open Windows TAP-Windows adapter */
-                openvpn_snprintf(device_path, sizeof(device_path), "%s%s%s",
-                                 USERMODEDEVICEDIR,
-                                 device_guid,
-                                 TAP_WIN_SUFFIX);
+                /* Open wintun / tap-windows6 adapter */
+                if (tt->wintun)
+                {
+                    openvpn_snprintf(device_path, sizeof(device_path), "%sWINTUN%lu",
+                                     USERMODEDEVICEDIR,
+                                     device_luid_index);
+                }
+                else
+                {
+                    openvpn_snprintf(device_path, sizeof(device_path), "%s%s%s",
+                                     USERMODEDEVICEDIR,
+                                     device_guid,
+                                     TAP_WIN_SUFFIX);
+                }
+
+                if (tt->wintun)
+                {
+                    if (!impersonate_as_system())
+                    {
+                        msg(M_FATAL, "ERROR:  Failed to impersonate as SYSTEM, make sure process is running under privileged account");
+                    }
+                }
 
                 tt->hand = CreateFile(
                     device_path,
@@ -5626,6 +5680,14 @@  open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tun
                     0
                     );
 
+                if (tt->wintun)
+                {
+                    if (!RevertToSelf())
+                    {
+                        msg(M_FATAL, "ERROR:  RevertToSelf error: %lu", GetLastError());
+                    }
+                }
+
                 if (tt->hand == INVALID_HANDLE_VALUE)
                 {
                     msg(D_TUNTAP_INFO, "CreateFile failed on TAP device: %s", device_path);
@@ -5644,12 +5706,14 @@  open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tun
         tt->actual_name = string_alloc(actual_buffer, NULL);
     }
 
-    msg(M_INFO, "TAP-WIN32 device [%s] opened: %s", tt->actual_name, device_path);
+    msg(M_INFO, "%s device device [%s] opened: %s", tt->wintun ? "Wintun" : "TAP - WIN32", tt->actual_name, device_path);
     tt->adapter_index = get_adapter_index(device_guid);
 
-    /* get driver version info */
+    if (!tt->wintun)
     {
+        /* get driver version info */
         ULONG info[3];
+        ULONG mtu;
         CLEAR(info);
         if (DeviceIoControl(tt->hand, TAP_WIN_IOCTL_GET_VERSION,
                             &info, sizeof(info),
@@ -5684,11 +5748,8 @@  open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tun
         {
             msg( M_FATAL, "ERROR:  Tap-Win32 driver version %d.%d is buggy regarding small IPv4 packets in TUN mode. Upgrade your Tap-Win32 driver.", (int) info[0], (int) info[1] );
         }
-    }
 
-    /* get driver MTU */
-    {
-        ULONG mtu;
+        /* get driver MTU */
         if (DeviceIoControl(tt->hand, TAP_WIN_IOCTL_GET_MTU,
                             &mtu, sizeof(mtu),
                             &mtu, sizeof(mtu), &len, NULL))
@@ -5746,7 +5807,7 @@  open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tun
 
     /* set point-to-point mode if TUN device */
 
-    if (tt->type == DEV_TYPE_TUN)
+    if (tt->type == DEV_TYPE_TUN && !tt->wintun)
     {
         if (!tt->did_ifconfig_setup && !tt->did_ifconfig_ipv6_setup)
         {
@@ -5834,71 +5895,80 @@  open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tun
             ep[2] = dhcp_masq_addr(tt->local, tt->adapter_netmask, tt->options.dhcp_masq_custom_offset ? tt->options.dhcp_masq_offset : 0);
         }
 
-        /* lease time in seconds */
-        ep[3] = (uint32_t) tt->options.dhcp_lease_time;
+        if (!tt->wintun)
+        {
+            /* lease time in seconds */
+            ep[3] = (uint32_t)tt->options.dhcp_lease_time;
 
-        ASSERT(ep[3] > 0);
+            ASSERT(ep[3] > 0);
 
 #ifndef SIMULATE_DHCP_FAILED /* this code is disabled to simulate bad DHCP negotiation */
-        if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_DHCP_MASQ,
-                             ep, sizeof(ep),
-                             ep, sizeof(ep), &len, NULL))
-        {
-            msg(M_FATAL, "ERROR: The TAP-Windows driver rejected a DeviceIoControl call to set TAP_WIN_IOCTL_CONFIG_DHCP_MASQ mode");
-        }
+            if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_DHCP_MASQ,
+                ep, sizeof(ep),
+                ep, sizeof(ep), &len, NULL))
+            {
+                msg(M_FATAL, "ERROR: The TAP-Windows driver rejected a DeviceIoControl call to set TAP_WIN_IOCTL_CONFIG_DHCP_MASQ mode");
+            }
 
-        msg(M_INFO, "Notified TAP-Windows 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]
+            msg(M_INFO, "Notified TAP-Windows 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);
-            if (build_dhcp_options_string(&buf, &tt->options))
+            /* user-supplied DHCP options capability */
+            if (tt->options.dhcp_options)
             {
-                msg(D_DHCP_OPT, "DHCP option string: %s", format_hex(BPTR(&buf), BLEN(&buf), 0, &gc));
-                if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT,
-                                     BPTR(&buf), BLEN(&buf),
-                                     BPTR(&buf), BLEN(&buf), &len, NULL))
+                struct buffer buf = alloc_buf(256);
+                if (build_dhcp_options_string(&buf, &tt->options))
                 {
-                    msg(M_FATAL, "ERROR: The TAP-Windows driver rejected a TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT DeviceIoControl call");
+                    msg(D_DHCP_OPT, "DHCP option string: %s", format_hex(BPTR(&buf), BLEN(&buf), 0, &gc));
+                    if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT,
+                        BPTR(&buf), BLEN(&buf),
+                        BPTR(&buf), BLEN(&buf), &len, NULL))
+                    {
+                        msg(M_FATAL, "ERROR: The TAP-Windows driver rejected a TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT DeviceIoControl call");
+                    }
                 }
+                else
+                {
+                    msg(M_WARN, "DHCP option string not set due to error");
+                }
+                free_buf(&buf);
             }
-            else
-            {
-                msg(M_WARN, "DHCP option string not set due to error");
-            }
-            free_buf(&buf);
-        }
 #endif /* ifndef SIMULATE_DHCP_FAILED */
+        }
+
+
     }
 
-    /* set driver media status to 'connected' */
+    if (!tt->wintun)
     {
-        ULONG status = TRUE;
-        if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_SET_MEDIA_STATUS,
-                             &status, sizeof(status),
-                             &status, sizeof(status), &len, NULL))
+        /* set driver media status to 'connected' */
         {
-            msg(M_WARN, "WARNING: The TAP-Windows driver rejected a TAP_WIN_IOCTL_SET_MEDIA_STATUS DeviceIoControl call.");
+            ULONG status = TRUE;
+            if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_SET_MEDIA_STATUS,
+                &status, sizeof(status),
+                &status, sizeof(status), &len, NULL))
+            {
+              msg(M_WARN, "WARNING: The TAP-Windows driver rejected a TAP_WIN_IOCTL_SET_MEDIA_STATUS DeviceIoControl call.");
+            }
         }
-    }
 
-    /* possible wait for adapter to come up */
-    {
-        int s = tt->options.tap_sleep;
-        if (s > 0)
+        /* possible wait for adapter to come up */
         {
-            msg(M_INFO, "Sleeping for %d seconds...", s);
-            management_sleep(s);
+            int s = tt->options.tap_sleep;
+            if (s > 0)
+            {
+                msg(M_INFO, "Sleeping for %d seconds...", s);
+                management_sleep(s);
+            }
         }
     }
 
+
     /* possibly use IP Helper API to set IP address on adapter */
     {
         const DWORD index = tt->adapter_index;
diff --git a/src/openvpn/tun.h b/src/openvpn/tun.h
index 9ed8ef0..6591d59 100644
--- a/src/openvpn/tun.h
+++ b/src/openvpn/tun.h
@@ -37,6 +37,10 @@ 
 #include "proto.h"
 #include "misc.h"
 
+#ifdef _WIN32
+#define WINTUN_COMPONENT_ID "wintun"
+#endif
+
 #if defined(_WIN32) || defined(TARGET_ANDROID)
 
 #define TUN_ADAPTER_INDEX_INVALID ((DWORD)-1)
@@ -174,6 +178,9 @@  struct tuntap
      * ~0 if undefined */
     DWORD adapter_index;
 
+    bool wintun; /* true if wintun is used instead of tap-windows6 */
+    char wintun_padding[16];
+
     int standby_iter;
 #else  /* ifdef _WIN32 */
     int fd; /* file descriptor for TUN/TAP dev */
@@ -337,6 +344,7 @@  route_order(void)
 struct tap_reg
 {
     const char *guid;
+    DWORD luid_index;
     struct tap_reg *next;
 };
 
@@ -374,7 +382,7 @@  DWORD adapter_index_of_ip(const IP_ADAPTER_INFO *list,
                           int *count,
                           in_addr_t *netmask);
 
-void show_tap_win_adapters(int msglev, int warnlev);
+void show_tap_win_adapters(int msglev, int warnlev, bool wintun);
 
 void show_adapters(int msglev);
 
@@ -465,6 +473,30 @@  read_tun_buffered(struct tuntap *tt, struct buffer *buf)
 static inline int
 write_tun_buffered(struct tuntap *tt, struct buffer *buf)
 {
+    if (tt->wintun)
+    {
+        int len = BLEN(buf);
+
+        /* variable len end padding */
+        int end_padding_len = 16 - (len & 15);
+        if (!buf_write(buf, tt->wintun_padding, end_padding_len))
+        {
+            return -1;
+        }
+
+        /* 12 bytes start padding */
+        if (!buf_write_prepend(buf, tt->wintun_padding, 12))
+        {
+            return -1;
+        }
+
+        /* 4 bytes size */
+        if (!buf_write_prepend(buf, &len, 4))
+        {
+            return -1;
+        }
+    }
+
     return tun_write_win32(tt, buf);
 }
 
diff --git a/src/openvpn/win32.c b/src/openvpn/win32.c
index eb4c030..4b4a2b1 100644
--- a/src/openvpn/win32.c
+++ b/src/openvpn/win32.c
@@ -164,20 +164,34 @@  init_security_attributes_allow_all(struct security_attributes *obj)
 void
 overlapped_io_init(struct overlapped_io *o,
                    const struct frame *frame,
-                   BOOL event_state,
-                   bool tuntap_buffer)  /* if true: tuntap buffer, if false: socket buffer */
+                   bool reads, /* if true: reads buffer, if false: writes buffer */
+                   bool tuntap_buffer,  /* if true: tuntap buffer, if false: socket buffer */
+                   bool wintun)
 {
     CLEAR(*o);
 
     /* manual reset event, initially set according to event_state */
-    o->overlapped.hEvent = CreateEvent(NULL, TRUE, event_state, NULL);
+    o->overlapped.hEvent = CreateEvent(NULL, TRUE, reads ? FALSE : TRUE, NULL);
     if (o->overlapped.hEvent == NULL)
     {
         msg(M_ERR, "Error: overlapped_io_init: CreateEvent failed");
     }
 
-    /* allocate buffer for overlapped I/O */
-    alloc_buf_sock_tun(&o->buf_init, frame, tuntap_buffer, 0);
+    if (wintun && tuntap_buffer && reads)
+    {
+        /*
+         * wintun could return up to 256 packets,
+         * each packet is accompanied with size, start and end padding
+        */
+        int buf_size = (MAX_RW_SIZE_TUN(frame) + 4 + 12 + 15) * 256;
+        o->buf_init = alloc_buf(buf_size);
+        o->buf_init.len = buf_size;
+    }
+    else
+    {
+        /* allocate buffer for overlapped I/O */
+        alloc_buf_sock_tun(&o->buf_init, frame, tuntap_buffer, 0);
+    }
 }
 
 void
@@ -1493,4 +1507,99 @@  send_msg_iservice(HANDLE pipe, const void *data, size_t size,
     return ret;
 }
 
+bool
+impersonate_as_system()
+{
+    HANDLE thread_token, process_snapshot, winlogon_process, winlogon_token, duplicated_token, file_handle;
+    PROCESSENTRY32 entry;
+    BOOL ret;
+    DWORD pid = 0;
+    TOKEN_PRIVILEGES privileges;
+
+    CLEAR(entry);
+    CLEAR(privileges);
+
+    entry.dwSize = sizeof(PROCESSENTRY32);
+
+    privileges.PrivilegeCount = 1;
+    privileges.Privileges->Attributes = SE_PRIVILEGE_ENABLED;
+
+    if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &privileges.Privileges[0].Luid))
+    {
+        return false;
+    }
+
+    if (!ImpersonateSelf(SecurityImpersonation))
+    {
+        return false;
+    }
+
+    if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &thread_token))
+    {
+        RevertToSelf();
+        return false;
+    }
+    if (!AdjustTokenPrivileges(thread_token, FALSE, &privileges, sizeof(privileges), NULL, NULL))
+    {
+        CloseHandle(thread_token);
+        RevertToSelf();
+        return false;
+    }
+    CloseHandle(thread_token);
+
+    process_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+    if (process_snapshot == INVALID_HANDLE_VALUE)
+    {
+        RevertToSelf();
+        return false;
+    }
+    for (ret = Process32First(process_snapshot, &entry); ret; ret = Process32Next(process_snapshot, &entry))
+    {
+        if (!_stricmp(entry.szExeFile, "winlogon.exe"))
+        {
+            pid = entry.th32ProcessID;
+            break;
+        }
+    }
+    CloseHandle(process_snapshot);
+    if (!pid)
+    {
+        RevertToSelf();
+        return false;
+    }
+
+    winlogon_process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
+    if (!winlogon_process)
+    {
+        RevertToSelf();
+        return false;
+    }
+
+    if (!OpenProcessToken(winlogon_process, TOKEN_IMPERSONATE | TOKEN_DUPLICATE, &winlogon_token))
+    {
+        CloseHandle(winlogon_process);
+        RevertToSelf();
+        return false;
+    }
+    CloseHandle(winlogon_process);
+
+    if (!DuplicateToken(winlogon_token, SecurityImpersonation, &duplicated_token))
+    {
+        CloseHandle(winlogon_token);
+        RevertToSelf();
+        return false;
+    }
+    CloseHandle(winlogon_token);
+
+    if (!SetThreadToken(NULL, duplicated_token))
+    {
+        CloseHandle(duplicated_token);
+        RevertToSelf();
+        return false;
+    }
+    CloseHandle(duplicated_token);
+
+    return true;
+}
+
 #endif /* ifdef _WIN32 */
diff --git a/src/openvpn/win32.h b/src/openvpn/win32.h
index 4814bbc..e511b56 100644
--- a/src/openvpn/win32.h
+++ b/src/openvpn/win32.h
@@ -212,10 +212,12 @@  struct overlapped_io {
     struct buffer buf;
 };
 
-void overlapped_io_init(struct overlapped_io *o,
-                        const struct frame *frame,
-                        BOOL event_state,
-                        bool tuntap_buffer);
+void
+overlapped_io_init(struct overlapped_io *o,
+                   const struct frame *frame,
+                   bool reads, /* if true: reads buffer, if false: writes buffer */
+                   bool tuntap_buffer,  /* if true: tuntap buffer, if false: socket buffer */
+                   bool wintun);
 
 void overlapped_io_close(struct overlapped_io *o);
 
@@ -323,5 +325,14 @@  bool send_msg_iservice(HANDLE pipe, const void *data, size_t size,
 int
 openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned int flags);
 
+/**
+ * Impersonates current thread as SYSTEM, required
+ * to open Wintun device.
+ *
+ * @returns     True if it succeeds, false if it fails.
+ */
+bool
+impersonate_as_system();
+
 #endif /* ifndef OPENVPN_WIN32_H */
 #endif /* ifdef _WIN32 */