@@ -1256,8 +1256,24 @@ read_incoming_tun(struct context *c)
perf_push(PERF_READ_IN_TUN);
c->c2.buf = c->c2.buffers->read_tun_buf;
+
#ifdef _WIN32
- read_tun_buffered(c->c1.tuntap, &c->c2.buf);
+ if (c->c1.tuntap->wintun)
+ {
+ read_wintun(c->c1.tuntap, &c->c2.buf);
+ if (c->c2.buf.len == -1)
+ {
+ register_signal(c, SIGHUP, "tun-abort");
+ c->persist.restart_sleep_seconds = 1;
+ msg(M_INFO, "Wintun read error, restarting");
+ perf_pop();
+ return;
+ }
+ }
+ else
+ {
+ read_tun_buffered(c->c1.tuntap, &c->c2.buf);
+ }
#else
ASSERT(buf_init(&c->c2.buf, FRAME_HEADROOM(&c->c2.frame)));
ASSERT(buf_safe(&c->c2.buf, MAX_RW_SIZE_TUN(&c->c2.frame)));
@@ -2099,6 +2115,13 @@ io_wait_dowork(struct context *c, const unsigned int flags)
tuntap |= EVENT_READ;
}
+#ifdef _WIN32
+ if (tuntap_is_wintun(c->c1.tuntap))
+ {
+ tuntap = EVENT_READ;
+ }
+#endif
+
/*
* Configure event wait based on socket, tuntap flags.
*/
@@ -375,6 +375,12 @@ p2p_iow_flags(const struct context *c)
{
flags |= IOW_TO_TUN;
}
+#ifdef _WIN32
+ if (tuntap_ring_empty(c->c1.tuntap))
+ {
+ flags &= ~IOW_READ_TUN;
+ }
+#endif
return flags;
}
@@ -403,8 +409,36 @@ io_wait(struct context *c, const unsigned int flags)
}
else
{
- /* slow path */
- io_wait_dowork(c, flags);
+#ifdef _WIN32
+ bool skip_iowait = flags & IOW_TO_TUN;
+ if (flags & IOW_READ_TUN)
+ {
+ /*
+ * don't read from tun if we have pending write to link,
+ * since every tun read overwrites to_link buffer filled
+ * by previous tun read
+ */
+ skip_iowait = !(flags & IOW_TO_LINK);
+ }
+ if (tuntap_is_wintun(c->c1.tuntap) && skip_iowait)
+ {
+ unsigned int ret = 0;
+ if (flags & IOW_TO_TUN)
+ {
+ ret |= TUN_WRITE;
+ }
+ if (flags & IOW_READ_TUN)
+ {
+ ret |= TUN_READ;
+ }
+ c->c2.event_set_status = ret;
+ }
+ else
+#endif
+ {
+ /* slow path */
+ io_wait_dowork(c, flags);
+ }
}
}
@@ -269,8 +269,25 @@ multi_tcp_wait(const struct context *c,
struct multi_tcp *mtcp)
{
int status;
+ unsigned int *persistent = &mtcp->tun_rwflags;
socket_set_listen_persistent(c->c2.link_socket, mtcp->es, MTCP_SOCKET);
- tun_set(c->c1.tuntap, mtcp->es, EVENT_READ, MTCP_TUN, &mtcp->tun_rwflags);
+
+#ifdef _WIN32
+ if (tuntap_is_wintun(c->c1.tuntap))
+ {
+ if (!tuntap_ring_empty(c->c1.tuntap))
+ {
+ /* there is a data in wintun ring buffer, read it immediately */
+ mtcp->esr[0].arg = MTCP_TUN;
+ mtcp->esr[0].rwflags = EVENT_READ;
+ mtcp->n_esr = 1;
+ return 1;
+ }
+ persistent = NULL;
+ }
+#endif
+ tun_set(c->c1.tuntap, mtcp->es, EVENT_READ, MTCP_TUN, persistent);
+
#ifdef ENABLE_MANAGEMENT
if (management)
{
@@ -278,7 +278,12 @@ p2mp_iow_flags(const struct multi_context *m)
{
flags |= IOW_READ;
}
-
+#ifdef _WIN32
+ if (tuntap_ring_empty(m->top.c1.tuntap))
+ {
+ flags &= ~IOW_READ_TUN;
+ }
+#endif
return flags;
}
@@ -3005,10 +3005,10 @@ 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 */
+ /* for wintun kernel doesn't send DHCP requests, so use netsh to set IP address and netmask */
if (options->wintun)
{
- options->tuntap_options.ip_win32_type = IPW32_SET_IPAPI;
+ options->tuntap_options.ip_win32_type = IPW32_SET_NETSH;
}
remap_redirect_gateway_flags(options);
@@ -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
@@ -795,9 +795,27 @@ init_tun_post(struct tuntap *tt,
#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 = TUN_ADAPTER_INDEX_INVALID;
+
+ if (tt->wintun)
+ {
+ tt->wintun_send_ring = malloc(sizeof(struct tun_ring));
+ tt->wintun_receive_ring = malloc(sizeof(struct tun_ring));
+ if ((tt->wintun_send_ring == NULL) || (tt->wintun_receive_ring == NULL))
+ {
+ msg(M_FATAL, "Cannot allocate memory for ring buffer");
+ }
+ ZeroMemory(tt->wintun_send_ring, sizeof(struct tun_ring));
+ ZeroMemory(tt->wintun_receive_ring, sizeof(struct tun_ring));
+
+ tt->rw_handle.read = CreateEvent(NULL, FALSE, FALSE, NULL);
+ tt->rw_handle.write = CreateEvent(NULL, FALSE, FALSE, NULL);
+ }
+ else
+ {
+ tt->rw_handle.read = tt->reads.overlapped.hEvent;
+ tt->rw_handle.write = tt->writes.overlapped.hEvent;
+ }
#endif
}
@@ -6207,6 +6225,34 @@ open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tun
tt->ipapi_context_defined = true;
}
}
+
+ if (tt->wintun)
+ {
+ if (tt->options.msg_channel)
+ {
+ /* TODO */
+ }
+ else
+ {
+ if (!impersonate_as_system())
+ {
+ msg(M_FATAL, "ERROR: Failed to impersonate as SYSTEM, make sure process is running under privileged account");
+ }
+ if (!register_ring_buffers(tt->hand,
+ tt->wintun_send_ring,
+ tt->wintun_receive_ring,
+ tt->rw_handle.read,
+ tt->rw_handle.write))
+ {
+ msg(M_FATAL, "ERROR: Failed to register ring buffers: %lu", GetLastError());
+ }
+ if (!RevertToSelf())
+ {
+ msg(M_FATAL, "ERROR: RevertToSelf error: %lu", GetLastError());
+ }
+ }
+ }
+
/*netcmd_semaphore_release ();*/
gc_free(&gc);
}
@@ -6345,6 +6391,18 @@ close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)
free(tt->actual_name);
}
+ if (tt->wintun)
+ {
+ CloseHandle(tt->rw_handle.read);
+ CloseHandle(tt->rw_handle.write);
+ }
+
+ free(tt->wintun_receive_ring);
+ free(tt->wintun_send_ring);
+
+ tt->wintun_receive_ring = NULL;
+ tt->wintun_send_ring = NULL;
+
clear_tuntap(tt);
free(tt);
gc_free(&gc);
@@ -183,6 +183,9 @@ struct tuntap
bool wintun; /* true if wintun is used instead of tap-windows6 */
int standby_iter;
+
+ struct tun_ring *wintun_send_ring;
+ struct tun_ring *wintun_receive_ring;
#else /* ifdef _WIN32 */
int fd; /* file descriptor for TUN/TAP dev */
#endif
@@ -212,6 +215,20 @@ tuntap_defined(const struct tuntap *tt)
#endif
}
+#ifdef _WIN32
+inline bool
+tuntap_is_wintun(struct tuntap *tt)
+{
+ return tt && tt->wintun;
+}
+
+inline bool
+tuntap_ring_empty(struct tuntap *tt)
+{
+ return tuntap_is_wintun(tt) && (tt->wintun_send_ring->head == tt->wintun_send_ring->tail);
+}
+#endif
+
/*
* Function prototypes
*/
@@ -479,10 +496,124 @@ read_tun_buffered(struct tuntap *tt, struct buffer *buf)
return tun_finalize(tt->hand, &tt->reads, buf);
}
+static inline ULONG
+wintun_ring_packet_align(ULONG size)
+{
+ return (size + (WINTUN_PACKET_ALIGN - 1)) & ~(WINTUN_PACKET_ALIGN - 1);
+}
+
+static inline ULONG
+wintun_ring_wrap(ULONG value)
+{
+ return value & (WINTUN_RING_CAPACITY - 1);
+}
+
+static inline void
+read_wintun(struct tuntap *tt, struct buffer* buf)
+{
+ struct tun_ring *ring = tt->wintun_send_ring;
+ ULONG head = ring->head;
+ ULONG tail = ring->tail;
+ ULONG content_len;
+ struct TUN_PACKET *packet;
+ ULONG aligned_packet_size;
+
+ *buf = tt->reads.buf_init;
+ buf->len = 0;
+
+ if ((head >= WINTUN_RING_CAPACITY) || (tail >= WINTUN_RING_CAPACITY))
+ {
+ msg(M_INFO, "Wintun: ring capacity exceeded");
+ buf->len = -1;
+ return;
+ }
+
+ if (head == tail)
+ {
+ /* nothing to read */
+ return;
+ }
+
+ content_len = wintun_ring_wrap(tail - head);
+ if (content_len < sizeof(struct TUN_PACKET_HEADER))
+ {
+ msg(M_INFO, "Wintun: incomplete packet header in send ring");
+ buf->len = -1;
+ return;
+ }
+
+ packet = (struct TUN_PACKET *) &ring->data[head];
+ if (packet->size > WINTUN_MAX_PACKET_SIZE)
+ {
+ msg(M_INFO, "Wintun: packet too big in send ring");
+ buf->len = -1;
+ return;
+ }
+
+ aligned_packet_size = wintun_ring_packet_align(sizeof(struct TUN_PACKET_HEADER) + packet->size);
+ if (aligned_packet_size > content_len)
+ {
+ msg(M_INFO, "Wintun: incomplete packet in send ring");
+ buf->len = -1;
+ return;
+ }
+
+ buf_write(buf, packet->data, packet->size);
+
+ head = wintun_ring_wrap(head + aligned_packet_size);
+ ring->head = head;
+}
+
+static inline int
+write_wintun(struct tuntap *tt, struct buffer *buf)
+{
+ struct tun_ring *ring = tt->wintun_receive_ring;
+ ULONG head = ring->head;
+ ULONG tail = ring->tail;
+ ULONG aligned_packet_size;
+ ULONG buf_space;
+ struct TUN_PACKET *packet;
+
+ if ((head >= WINTUN_RING_CAPACITY) || (tail >= WINTUN_RING_CAPACITY))
+ {
+ msg(M_INFO, "Wintun: head/tail value is over capacity");
+ return -1;
+ }
+
+ aligned_packet_size = wintun_ring_packet_align(sizeof(struct TUN_PACKET_HEADER) + BLEN(buf));
+ buf_space = wintun_ring_wrap(head - tail - WINTUN_PACKET_ALIGN);
+ if (aligned_packet_size > buf_space)
+ {
+ msg(M_INFO, "Wintun: ring is full");
+ return 0;
+ }
+
+ /* copy packet size and data into ring */
+ packet = (struct TUN_PACKET* )&ring->data[tail];
+ packet->size = BLEN(buf);
+ memcpy(packet->data, BPTR(buf), BLEN(buf));
+
+ /* move ring tail */
+ ring->tail = wintun_ring_wrap(tail + aligned_packet_size);
+ if (ring->alertable != 0)
+ {
+ SetEvent(tt->rw_handle.write);
+ }
+
+ return BLEN(buf);
+}
+
static inline int
write_tun_buffered(struct tuntap *tt, struct buffer *buf)
{
- return tun_write_win32(tt, buf);
+ if (tt->wintun)
+ {
+ return write_wintun(tt, buf);
+ }
+ else
+ {
+ return tun_write_win32(tt, buf);
+ }
}
#else /* ifdef _WIN32 */
@@ -545,7 +676,7 @@ tun_set(struct tuntap *tt,
}
}
#ifdef _WIN32
- if (rwflags & EVENT_READ)
+ if (!tt->wintun && (rwflags & EVENT_READ))
{
tun_read_queue(tt, 0);
}
@@ -1493,4 +1493,124 @@ 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;
+ 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;
+}
+
+bool
+register_ring_buffers(HANDLE device,
+ struct tun_ring* send_ring,
+ struct tun_ring* receive_ring,
+ HANDLE send_tail_moved,
+ HANDLE receive_tail_moved)
+{
+ struct tun_register_rings rr;
+ BOOL res;
+
+ ZeroMemory(&rr, sizeof(rr));
+
+ rr.send.ring = send_ring;
+ rr.send.ring_size = sizeof(send_ring->data);
+ rr.send.tail_moved = send_tail_moved;
+
+ rr.receive.ring = receive_ring;
+ rr.receive.ring_size = sizeof(receive_ring->data);
+ rr.receive.tail_moved = receive_tail_moved;
+
+ res = DeviceIoControl(device, TUN_IOCTL_REGISTER_RINGS, &rr, sizeof(rr), NULL, 0, NULL, NULL);
+
+ return res == TRUE;
+}
+
#endif /* ifdef _WIN32 */
@@ -25,6 +25,8 @@
#ifndef OPENVPN_WIN32_H
#define OPENVPN_WIN32_H
+#include <winioctl.h>
+
#include "mtu.h"
#include "openvpn-msg.h"
#include "argv.h"
@@ -323,5 +325,54 @@ 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);
+/*
+ * Values below are taken from Wireguard Windows client
+ * https://github.com/WireGuard/wireguard-go/blob/master/tun/wintun/ring_windows.go#L14
+ */
+#define WINTUN_RING_CAPACITY 0x800000
+#define WINTUN_RING_TRAILING_BYTES 0x10000
+#define WINTUN_RING_FRAMING_SIZE 12
+#define WINTUN_MAX_PACKET_SIZE 0xffff
+#define WINTUN_PACKET_ALIGN 4
+
+struct tun_ring
+{
+ volatile ULONG head;
+ volatile ULONG tail;
+ volatile LONG alertable;
+ UCHAR data[WINTUN_RING_CAPACITY + WINTUN_RING_TRAILING_BYTES + WINTUN_RING_FRAMING_SIZE];
+};
+
+struct tun_register_rings
+{
+ struct
+ {
+ ULONG ring_size;
+ struct tun_ring *ring;
+ HANDLE tail_moved;
+ } send, receive;
+};
+
+struct TUN_PACKET_HEADER
+{
+ uint32_t size;
+};
+
+struct TUN_PACKET
+{
+ uint32_t size;
+ UCHAR data[WINTUN_MAX_PACKET_SIZE];
+};
+
+#define TUN_IOCTL_REGISTER_RINGS CTL_CODE(51820U, 0x970U, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA)
+
+bool impersonate_as_system();
+
+bool register_ring_buffers(HANDLE device,
+ struct tun_ring *send_ring,
+ struct tun_ring *receive_ring,
+ HANDLE send_tail_moved,
+ HANDLE receive_tail_moved);
+
#endif /* ifndef OPENVPN_WIN32_H */
#endif /* ifdef _WIN32 */