openconnect-unknown/tun-win32.c

1 /*
2  * OpenConnect (SSL + DTLS) VPN client
3  *
4  * Copyright © 2008-2015 Intel Corporation.
5  *
6  * Author: David Woodhouse <dwmw2@infradead.org>
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public License
10  * version 2.1, as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  */
17 
18 #include <config.h>
19 
20 #define WIN32_LEAN_AND_MEAN
21 #include <windows.h>
22 #include <winioctl.h>
23 #include <iphlpapi.h>
24 
25 #include <errno.h>
26 #include <stdio.h>
27 
28 #include <openconnect-internal.h>
29 
30 /*
31  * TAP-Windows support inspired by http://i3.cs.berkeley.edu/ with
32  * permission.
33  */
34 #define _TAP_IOCTL(nr) CTL_CODE(FILE_DEVICE_UNKNOWN, nr, METHOD_BUFFERED, \
35 				FILE_ANY_ACCESS)
36 
37 #define TAP_IOCTL_GET_MAC               _TAP_IOCTL(1)
38 #define TAP_IOCTL_GET_VERSION           _TAP_IOCTL(2)
39 #define TAP_IOCTL_GET_MTU               _TAP_IOCTL(3)
40 #define TAP_IOCTL_GET_INFO              _TAP_IOCTL(4)
41 #define TAP_IOCTL_CONFIG_POINT_TO_POINT _TAP_IOCTL(5)
42 #define TAP_IOCTL_SET_MEDIA_STATUS      _TAP_IOCTL(6)
43 #define TAP_IOCTL_CONFIG_DHCP_MASQ      _TAP_IOCTL(7)
44 #define TAP_IOCTL_GET_LOG_LINE          _TAP_IOCTL(8)
45 #define TAP_IOCTL_CONFIG_DHCP_SET_OPT   _TAP_IOCTL(9)
46 #define TAP_IOCTL_CONFIG_TUN            _TAP_IOCTL(10)
47 
48 #define TAP_COMPONENT_ID "tap0901"
49 
50 #define DEVTEMPLATE "\\\\.\\Global\\%s.tap"
51 
52 #define NETDEV_GUID "{4D36E972-E325-11CE-BFC1-08002BE10318}"
53 #define CONTROL_KEY "SYSTEM\\CurrentControlSet\\Control\\"
54 
55 #define ADAPTERS_KEY CONTROL_KEY "Class\\" NETDEV_GUID
56 #define CONNECTIONS_KEY CONTROL_KEY "Network\\" NETDEV_GUID
57 
58 typedef intptr_t (tap_callback)(struct openconnect_info *vpninfo, char *idx, char *name);
59 
60 static intptr_t search_taps(struct openconnect_info *vpninfo, tap_callback *cb, int all)
61 {
62 	LONG status;
63 	HKEY adapters_key, hkey;
64 	DWORD len, type;
65 	char buf[40];
66 	wchar_t name[40];
67 	char keyname[strlen(CONNECTIONS_KEY) + sizeof(buf) + 1 + strlen("\\Connection")];
68 	int i = 0, found = 0;
69 	intptr_t ret = -1;
70 	struct oc_text_buf *namebuf = buf_alloc();
71 
72 	status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, ADAPTERS_KEY, 0,
73 			       KEY_READ, &adapters_key);
74 	if (status) {
75 		vpn_progress(vpninfo, PRG_ERR,
76 			     _("Error accessing registry key for network adapters\n"));
77 		return -EIO;
78 	}
79 	while (1) {
80 		len = sizeof(buf);
81 		status = RegEnumKeyExA(adapters_key, i++, buf, &len,
82 				       NULL, NULL, NULL, NULL);
83 		if (status) {
84 			if (status != ERROR_NO_MORE_ITEMS)
85 				ret = -1;
86 			break;
87 		}
88 
89 		snprintf(keyname, sizeof(keyname), "%s\\%s",
90 			 ADAPTERS_KEY, buf);
91 
92 		status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyname, 0,
93 				       KEY_QUERY_VALUE, &hkey);
94 		if (status)
95 			continue;
96 
97 		len = sizeof(buf);
98 		status = RegQueryValueExA(hkey, "ComponentId", NULL, &type,
99 					  (unsigned char *)buf, &len);
100 		if (status || type != REG_SZ || strcmp(buf, TAP_COMPONENT_ID)) {
101 			RegCloseKey(hkey);
102 			continue;
103 		}
104 
105 		len = sizeof(buf);
106 		status = RegQueryValueExA(hkey, "NetCfgInstanceId", NULL,
107 					  &type, (unsigned char *)buf, &len);
108 		RegCloseKey(hkey);
109 		if (status || type != REG_SZ)
110 			continue;
111 
112 		snprintf(keyname, sizeof(keyname), "%s\\%s\\Connection",
113 			 CONNECTIONS_KEY, buf);
114 
115 		status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyname, 0,
116 				       KEY_QUERY_VALUE, &hkey);
117 		if (status)
118 			continue;
119 
120 		len = sizeof(name);
121 		status = RegQueryValueExW(hkey, L"Name", NULL, &type,
122 					 (unsigned char *)name, &len);
123 		RegCloseKey(hkey);
124 		if (status || type != REG_SZ)
125 			continue;
126 
127 		buf_truncate(namebuf);
128 		buf_append_from_utf16le(namebuf, name);
129 		if (buf_error(namebuf)) {
130 			ret = buf_free(namebuf);
131 			namebuf = NULL;
132 			break;
133 		}
134 
135 		found++;
136 
137 		if (vpninfo->ifname && strcmp(namebuf->data, vpninfo->ifname)) {
138 			vpn_progress(vpninfo, PRG_DEBUG,
139 				     _("Ignoring non-matching TAP interface \"%s\"\n"),
140 				     namebuf->data);
141 			continue;
142 		}
143 
144 		ret = cb(vpninfo, buf, namebuf->data);
145 		if (!all)
146 			break;
147 	}
148 
149 	RegCloseKey(adapters_key);
150 	buf_free(namebuf);
151 
152 	if (!found)
153 		vpn_progress(vpninfo, PRG_ERR,
154 			     _("No Windows-TAP adapters found. Is the driver installed?\n"));
155 
156 	return ret;
157 }
158 
159 static int get_adapter_index(struct openconnect_info *vpninfo, char *guid)
160 {
161 	struct oc_text_buf *buf = buf_alloc();
162 	IP_ADAPTER_INFO *adapter;
163 	void *adapters_buf;
164 	ULONG idx;
165 	DWORD status;
166 	int ret = -EINVAL;
167 
168 	vpninfo->tun_idx = -1;
169 
170 	buf_append_utf16le(buf, "\\device\\tcpip_");
171 	buf_append_utf16le(buf, guid);
172 	if (buf_error(buf)) {
173 		/* If we didn't manage to malloc for this, we're never
174 		 * going to manage for GetAdaptersInfo(). Give up. */
175 		return buf_free(buf);
176 	}
177 
178 	status = GetAdapterIndex((void *)buf->data, &idx);
179 	buf_free(buf);
180 	if (status == NO_ERROR) {
181 		vpninfo->tun_idx = idx;
182 		return 0;
183 	} else {
184 		char *errstr = openconnect__win32_strerror(status);
185 		vpn_progress(vpninfo, PRG_INFO,
186 			     _("GetAdapterIndex() failed: %s\nFalling back to GetAdaptersInfo()\n"),
187 			     errstr);
188 		free(errstr);
189 	}
190 	idx = 0;
191 	status = GetAdaptersInfo(NULL, &idx);
192 	if (status != ERROR_BUFFER_OVERFLOW)
193 		return -EIO;
194 	adapters_buf = malloc(idx);
195 	if (!adapters_buf)
196 		return -ENOMEM;
197 	status = GetAdaptersInfo(adapters_buf, &idx);
198 	if (status != NO_ERROR) {
199 		char *errstr = openconnect__win32_strerror(status);
200 		vpn_progress(vpninfo, PRG_ERR, _("GetAdaptersInfo() failed: %s\n"), errstr);
201 		free(errstr);
202 		free(adapters_buf);
203 		return -EIO;
204 	}
205 
206 	for (adapter = adapters_buf; adapter; adapter = adapter->Next) {
207 		if (!strcmp(adapter->AdapterName, guid)) {
208 			vpninfo->tun_idx = adapter->Index;
209 			ret = 0;
210 			break;
211 		}
212 	}
213 
214 	free(adapters_buf);
215 	return ret;
216 }
217 
218 static intptr_t open_tun(struct openconnect_info *vpninfo, char *guid, char *name)
219 {
220 	char devname[80];
221 	HANDLE tun_fh;
222 	ULONG data[3];
223 	DWORD len;
224 
225 	snprintf(devname, sizeof(devname), DEVTEMPLATE, guid);
226 	tun_fh = CreateFileA(devname, GENERIC_WRITE|GENERIC_READ, 0, 0,
227 			     OPEN_EXISTING,
228 			     FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED,
229 			     0);
230 	if (tun_fh == INVALID_HANDLE_VALUE) {
231 		vpn_progress(vpninfo, PRG_ERR, _("Failed to open %s\n"),
232 			     devname);
233 		return -1;
234 
235 	}
236 	vpn_progress(vpninfo, PRG_DEBUG, _("Opened tun device %s\n"), name);
237 
238 	if (!DeviceIoControl(tun_fh, TAP_IOCTL_GET_VERSION,
239 			     data, sizeof(&data), data, sizeof(data),
240 			     &len, NULL)) {
241 		char *errstr = openconnect__win32_strerror(GetLastError());
242 
243 		vpn_progress(vpninfo, PRG_ERR,
244 			     _("Failed to obtain TAP driver version: %s\n"), errstr);
245 		free(errstr);
246 		return -1;
247 	}
248 	if (data[0] < 9 || (data[0] == 9 && data[1] < 9)) {
249 		vpn_progress(vpninfo, PRG_ERR,
250 			     _("Error: TAP-Windows driver v9.9 or greater is required (found %ld.%ld)\n"),
251 			     data[0], data[1]);
252 		return -1;
253 	}
254 	vpn_progress(vpninfo, PRG_DEBUG, "TAP-Windows driver v%ld.%ld (%ld)\n",
255 		     data[0], data[1], data[2]);
256 
257 	data[0] = inet_addr(vpninfo->ip_info.addr);
258 	/* Set network and mask both to 0.0.0.0. It's not about routing;
259 	 * it just ensures that the TAP driver fakes ARP responses for
260 	 * *everything* we throw at it, and we can just configure them
261 	 * as on-link routes. */
262 	data[1] = 0;
263 	data[2] = 0;
264 
265 	if (!DeviceIoControl(tun_fh, TAP_IOCTL_CONFIG_TUN,
266 			     data, sizeof(data), data, sizeof(data),
267 			     &len, NULL)) {
268 		char *errstr = openconnect__win32_strerror(GetLastError());
269 
270 		vpn_progress(vpninfo, PRG_ERR,
271 			     _("Failed to set TAP IP addresses: %s\n"), errstr);
272 		free(errstr);
273 		return -1;
274 	}
275 
276 	data[0] = 1;
277 	if (!DeviceIoControl(tun_fh, TAP_IOCTL_SET_MEDIA_STATUS,
278 			     data, sizeof(data[0]), data, sizeof(data[0]),
279 			     &len, NULL)) {
280 		char *errstr = openconnect__win32_strerror(GetLastError());
281 
282 		vpn_progress(vpninfo, PRG_ERR,
283 			     _("Failed to set TAP media status: %s\n"), errstr);
284 		free(errstr);
285 		return -1;
286 	}
287 	if (!vpninfo->ifname)
288 		vpninfo->ifname = strdup(name);
289 
290 	get_adapter_index(vpninfo, guid);
291 
292 	return (intptr_t)tun_fh;
293 }
294 
295 intptr_t os_setup_tun(struct openconnect_info *vpninfo)
296 {
297 	return search_taps(vpninfo, open_tun, 0);
298 }
299 
300 int os_read_tun(struct openconnect_info *vpninfo, struct pkt *pkt)
301 {
302 	DWORD pkt_size;
303 
304  reread:
305 	if (!vpninfo->tun_rd_pending &&
306 	    !ReadFile(vpninfo->tun_fh, pkt->data, pkt->len, &pkt_size,
307 		      &vpninfo->tun_rd_overlap)) {
308 		DWORD err = GetLastError();
309 
310 		if (err == ERROR_IO_PENDING)
311 			vpninfo->tun_rd_pending = 1;
312 		else if (err == ERROR_OPERATION_ABORTED) {
313 			vpninfo->quit_reason = "TAP device aborted";
314 			vpn_progress(vpninfo, PRG_ERR,
315 				     _("TAP device aborted connectivity. Disconnecting.\n"));
316 			return -1;
317 		} else {
318 			char *errstr = openconnect__win32_strerror(err);
319 			vpn_progress(vpninfo, PRG_ERR,
320 				     _("Failed to read from TAP device: %s\n"),
321 				     errstr);
322 			free(errstr);
323 		}
324 		return -1;
325 	} else if (!GetOverlappedResult(vpninfo->tun_fh,
326 					&vpninfo->tun_rd_overlap, &pkt_size,
327 					FALSE)) {
328 		DWORD err = GetLastError();
329 
330 		if (err != ERROR_IO_INCOMPLETE) {
331 			char *errstr = openconnect__win32_strerror(err);
332 			vpninfo->tun_rd_pending = 0;
333 			vpn_progress(vpninfo, PRG_ERR,
334 				     _("Failed to complete read from TAP device: %s\n"),
335 				     errstr);
336 			free(errstr);
337 			goto reread;
338 		}
339 		return -1;
340 	}
341 
342 	/* Either a straight ReadFile() or a subsequent GetOverlappedResult()
343 	   succeeded... */
344 	vpninfo->tun_rd_pending = 0;
345 	pkt->len = pkt_size;
346 	return 0;
347 }
348 
349 int os_write_tun(struct openconnect_info *vpninfo, struct pkt *pkt)
350 {
351 	DWORD pkt_size = 0;
352 	DWORD err;
353 	char *errstr;
354 
355 	if (WriteFile(vpninfo->tun_fh, pkt->data, pkt->len, &pkt_size, &vpninfo->tun_wr_overlap)) {
356 		vpn_progress(vpninfo, PRG_TRACE,
357 			     _("Wrote %ld bytes to tun\n"), pkt_size);
358 		return 0;
359 	}
360 
361 	err = GetLastError();
362 	if (err == ERROR_IO_PENDING) {
363 		/* Theoretically we should let the mainloop handle this blocking,
364 		   but that's non-trivial and it doesn't ever seem to happen in
365 		   practice anyway. */
366 		vpn_progress(vpninfo, PRG_TRACE,
367 			     _("Waiting for tun write...\n"));
368 		if (GetOverlappedResult(vpninfo->tun_fh, &vpninfo->tun_wr_overlap, &pkt_size, TRUE)) {
369 			vpn_progress(vpninfo, PRG_TRACE,
370 				     _("Wrote %ld bytes to tun after waiting\n"), pkt_size);
371 			return 0;
372 		}
373 		err = GetLastError();
374 	}
375 	errstr = openconnect__win32_strerror(err);
376 	vpn_progress(vpninfo, PRG_ERR,
377 		     _("Failed to write to TAP device: %s\n"), errstr);
378 	free(errstr);
379 	return -1;
380 }
381 
382 void os_shutdown_tun(struct openconnect_info *vpninfo)
383 {
384 	script_config_tun(vpninfo, "disconnect");
385 	CloseHandle(vpninfo->tun_fh);
386 	vpninfo->tun_fh = NULL;
387 	CloseHandle(vpninfo->tun_rd_overlap.hEvent);
388 	vpninfo->tun_rd_overlap.hEvent = NULL;
389 }
390 
391 int openconnect_setup_tun_fd(struct openconnect_info *vpninfo, intptr_t tun_fd)
392 {
393 	vpninfo->tun_fh = (HANDLE)tun_fd;
394 	vpninfo->tun_rd_overlap.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
395 	monitor_read_fd(vpninfo, tun);
396 
397 	return 0;
398 }
399 
400 int openconnect_setup_tun_script(struct openconnect_info *vpninfo,
401 				 const char *tun_script)
402 {
403 	vpn_progress(vpninfo, PRG_ERR,
404 		     _("Spawning tunnel scripts is not yet supported on Windows\n"));
405 	return -1;
406 }