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?