From a12ac9e6309293d0f1a8dedbfd4840359e91c34d Mon Sep 17 00:00:00 2001
From: David Woodhouse
Date: Tue, 11 Feb 2014 14:41:04 +0000
Subject: [PATCH] Finally add tun handling for Windows
Signed-off-by: David Woodhouse
---
library.c | 5 ++-
mainloop.c | 3 ++
openconnect-internal.h | 6 ++--
tun-win32.c | 7 +++-
tun.c | 75 +++++++++++++++++++++++++++++++++++++++---
www/platforms.xml | 6 ++--
6 files changed, 91 insertions(+), 11 deletions(-)
diff --git a/library.c b/library.c
index dde64a1b..c1130148 100644
--- a/library.c
+++ b/library.c
@@ -47,7 +47,10 @@ struct openconnect_info *openconnect_vpninfo_new(char *useragent,
if (!vpninfo)
return NULL;
- vpninfo->tun_fd = vpninfo->ssl_fd = vpninfo->dtls_fd = -1;
+#ifndef _WIN32
+ vpninfo->tun_fd = -1;
+#endif
+ vpninfo->ssl_fd = vpninfo->dtls_fd = -1;
vpninfo->cmd_fd = vpninfo->cmd_fd_write = -1;
vpninfo->cert_expire_warning = 60 * 86400;
vpninfo->deflate = 1;
diff --git a/mainloop.c b/mainloop.c
index d473ca86..c6eb4b2d 100644
--- a/mainloop.c
+++ b/mainloop.c
@@ -137,6 +137,9 @@ int openconnect_mainloop(struct openconnect_info *vpninfo,
WSAEventSelect(vpninfo->cmd_fd, vpninfo->cmd_event, vpninfo->cmd_monitored);
events[nr_events++] = vpninfo->cmd_event;
}
+ if (vpninfo->tun_monitored) {
+ events[nr_events++] = vpninfo->tun_rd_overlap.hEvent;
+ }
if (WaitForMultipleObjects(nr_events, events, FALSE, timeout) == WAIT_FAILED) {
vpn_progress(vpninfo, PRG_ERR,
_("WaitForMultipleObjects failed: %lx\n"),
diff --git a/openconnect-internal.h b/openconnect-internal.h
index 6e4ca2db..ce5d7c1b 100644
--- a/openconnect-internal.h
+++ b/openconnect-internal.h
@@ -274,7 +274,7 @@ struct openconnect_info {
#ifdef _WIN32
long dtls_monitored, ssl_monitored, cmd_monitored, tun_monitored;
- HANDLE dtls_event, ssl_event, cmd_event, tun_event;
+ HANDLE dtls_event, ssl_event, cmd_event;
#else
int _select_nfds;
fd_set _select_rfds;
@@ -288,8 +288,10 @@ struct openconnect_info {
#endif
#ifdef _WIN32
HANDLE tun_fh;
-#endif
+ OVERLAPPED tun_rd_overlap, tun_wr_overlap;
+#else
int tun_fd;
+#endif
int ssl_fd;
int dtls_fd;
diff --git a/tun-win32.c b/tun-win32.c
index 18221854..20eea0e3 100644
--- a/tun-win32.c
+++ b/tun-win32.c
@@ -124,7 +124,9 @@ static int open_tun(struct openconnect_info *vpninfo, char *guid, char *name)
snprintf(devname, sizeof(devname), DEVTEMPLATE, guid);
tun_fh = CreateFile(devname, GENERIC_WRITE|GENERIC_READ, 0, 0,
- OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0);
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED,
+ 0);
if (tun_fh == INVALID_HANDLE_VALUE) {
vpn_progress(vpninfo, PRG_ERR, _("Failed to open %s\n"),
devname);
@@ -176,6 +178,9 @@ static int open_tun(struct openconnect_info *vpninfo, char *guid, char *name)
vpninfo->ifname = strdup(name);
vpninfo->tun_fh = tun_fh;
+ vpninfo->tun_rd_overlap.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ monitor_read_fd(vpninfo, tun);
+
return 1;
}
diff --git a/tun.c b/tun.c
index 3b98c7ab..98e20210 100644
--- a/tun.c
+++ b/tun.c
@@ -635,6 +635,12 @@ static int os_setup_tun(struct openconnect_info *vpninfo)
return tun_fd;
}
+#ifdef _WIN32
+int openconnect_setup_tun_fd(struct openconnect_info *vpninfo, int tun_fd)
+{
+ return 0;
+}
+#else
int openconnect_setup_tun_fd(struct openconnect_info *vpninfo, int tun_fd)
{
set_fd_cloexec(tun_fd);
@@ -652,7 +658,6 @@ int openconnect_setup_tun_fd(struct openconnect_info *vpninfo, int tun_fd)
return 0;
}
-#ifndef _WIN32
int openconnect_setup_tun_script(struct openconnect_info *vpninfo, char *tun_script)
{
pid_t child;
@@ -716,6 +721,9 @@ int tun_mainloop(struct openconnect_info *vpninfo, int *timeout)
{
int work_done = 0;
int prefix_size = 0;
+#ifdef _WIN32
+ DWORD pkt_size = 0;
+#endif
#ifdef TUN_HAS_AF_PREFIX
if (!vpninfo->script_tun)
@@ -732,9 +740,34 @@ int tun_mainloop(struct openconnect_info *vpninfo, int *timeout)
vpn_progress(vpninfo, PRG_ERR, "Allocation failed\n");
break;
}
+#ifdef _WIN32
+ if (!ReadFile(vpninfo->tun_fh, out_pkt->data, len, &pkt_size, &vpninfo->tun_rd_overlap)) {
+ DWORD err = GetLastError();
+ if (err != ERROR_IO_PENDING)
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Failed to read from TAP device: %lx\n"),
+ err);
+ break;
+ }
+ len = pkt_size;
+ } else {
+ /* if out_pkt was already non-NULL then there was lready a pending read on it. */
+ if (!GetOverlappedResult(vpninfo->tun_fh, &vpninfo->tun_rd_overlap, &pkt_size, FALSE)) {
+ DWORD err = GetLastError();
+
+ if (err != ERROR_IO_INCOMPLETE)
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Failed to complete read from TAP device: %lx\n"),
+ err);
+ break;
+ }
+ len = pkt_size;
+#endif /* _WIN32 */
}
-
+#ifndef _WIN32
+ /* Sanity. Just non-blocking reads on a select()able file descriptor... */
len = read(vpninfo->tun_fd, out_pkt->data - prefix_size, len + prefix_size);
+#endif
if (len <= prefix_size)
break;
out_pkt->len = len - prefix_size;
@@ -793,7 +826,32 @@ int tun_mainloop(struct openconnect_info *vpninfo, int *timeout)
}
#endif
vpninfo->incoming_queue = this->next;
-
+#ifdef _WIN32
+ if (!WriteFile(vpninfo->tun_fh, data, len, &pkt_size, &vpninfo->tun_wr_overlap)) {
+ DWORD err = GetLastError();
+
+ if (err == ERROR_IO_PENDING) {
+ /* Theoretically we should let the mainloop handle this blocking,
+ but that's non-trivial and it doesn't ever seem to happen in
+ practice anyway. */
+ vpn_progress(vpninfo, PRG_TRACE,
+ _("Waiting for tun write...\n"));
+ if (!GetOverlappedResult(vpninfo->tun_fh, &vpninfo->tun_wr_overlap, &pkt_size, TRUE)) {
+ err = GetLastError();
+ goto report_write_err;
+ }
+ vpn_progress(vpninfo, PRG_TRACE,
+ _("Wrote %ld bytes to tun after waiting\n"), pkt_size);
+ } else {
+ report_write_err:
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Failed to write to TAP device: %lx\n"), err);
+ }
+ } else {
+ vpn_progress(vpninfo, PRG_TRACE,
+ _("Wrote %ld bytes to tun\n"), pkt_size);
+ }
+#else
if (write(vpninfo->tun_fd, data, len) < 0) {
/* Handle death of "script" socket */
if (vpninfo->script_tun && errno == ENOTCONN) {
@@ -804,6 +862,7 @@ int tun_mainloop(struct openconnect_info *vpninfo, int *timeout)
_("Failed to write incoming packet: %s\n"),
strerror(errno));
}
+#endif
free(this);
}
/* Work is not done if we just got rid of packets off the queue */
@@ -812,11 +871,16 @@ int tun_mainloop(struct openconnect_info *vpninfo, int *timeout)
void shutdown_tun(struct openconnect_info *vpninfo)
{
+#ifdef _WIN32
+ script_config_tun(vpninfo, "disconnect");
+ CloseHandle(vpninfo->tun_fh);
+ vpninfo->tun_fh = NULL;
+ CloseHandle(vpninfo->tun_rd_overlap.hEvent);
+ vpninfo->tun_rd_overlap.hEvent = NULL;
+#else
if (vpninfo->script_tun) {
-#ifndef _WIN32
/* nuke the whole process group */
kill(-vpninfo->script_tun, SIGHUP);
-#endif
} else {
script_config_tun(vpninfo, "disconnect");
#ifdef __sun__
@@ -832,4 +896,5 @@ void shutdown_tun(struct openconnect_info *vpninfo)
if (vpninfo->vpnc_script)
close(vpninfo->tun_fd);
vpninfo->tun_fd = -1;
+#endif
}
diff --git a/www/platforms.xml b/www/platforms.xml
index 8ff25d7e..06a2cad8 100644
--- a/www/platforms.xml
+++ b/www/platforms.xml
@@ -38,8 +38,10 @@ Platform support for new UNIX systems is relatively simple to add
— most of the difference is in the TUN/TAP device handling, and
the major variants of that are already supported.
-OpenConnect builds for Windows using MinGW, although support for the TUN/TAP
-device has not yet been completed.
+OpenConnect builds for Windows using MinGW and should work with the
+TAP-Windows driver shipped with OpenVPN (driver version 9.9 or later).
+However, support for automatically configuring IP adddress on the network interface
+is not yet implemented, and it is only tested with Legacy IP not IPv6.
A port to Symbian, to provide VPN connectivity on phone handsets,
would be very useful. Any volunteers?