2 device.c -- Interaction with Windows tap driver in a MinGW environment
3 Copyright (C) 2002-2005 Ivo Timmermans,
4 2002-2018 Guus Sliepen <guus@tinc-vpn.org>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "../system.h"
27 #include "../device.h"
28 #include "../logger.h"
33 #include "../xalloc.h"
38 static HANDLE device_handle = INVALID_HANDLE_VALUE;
39 static io_t device_read_io;
40 static OVERLAPPED device_read_overlapped;
41 static OVERLAPPED device_write_overlapped;
42 static vpn_packet_t device_read_packet;
43 static vpn_packet_t device_write_packet;
46 static const char *device_info = "Windows tap device";
50 static void device_issue_read() {
54 ResetEvent(device_read_overlapped.hEvent);
57 status = ReadFile(device_handle, (void *)device_read_packet.data, MTU, &len, &device_read_overlapped);
60 if(GetLastError() != ERROR_IO_PENDING)
61 logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info,
62 device, strerror(errno));
67 device_read_packet.len = len;
68 device_read_packet.priority = 0;
69 route(myself, &device_read_packet);
73 static void device_handle_read(void *data, int flags) {
79 if(!GetOverlappedResult(device_handle, &device_read_overlapped, &len, FALSE)) {
80 logger(DEBUG_ALWAYS, LOG_ERR, "Error getting read result from %s %s: %s", device_info,
81 device, strerror(errno));
83 if(GetLastError() != ERROR_IO_INCOMPLETE) {
84 /* Must reset event or it will keep firing. */
85 ResetEvent(device_read_overlapped.hEvent);
91 device_read_packet.len = len;
92 device_read_packet.priority = 0;
93 route(myself, &device_read_packet);
97 static bool setup_device(void) {
102 char adapterid[1024];
103 char adaptername[1024];
111 get_config_string(lookup_config(config_tree, "Device"), &device);
112 get_config_string(lookup_config(config_tree, "Interface"), &iface);
114 if(device && iface) {
115 logger(DEBUG_ALWAYS, LOG_WARNING, "Warning: both Device and Interface specified, results may not be as expected");
118 /* Open registry and look for network adapters */
120 if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, NETWORK_CONNECTIONS_KEY, 0, KEY_READ, &key)) {
121 logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read registry: %s", winerror(GetLastError()));
126 len = sizeof(adapterid);
128 if(RegEnumKeyEx(key, i, adapterid, &len, 0, 0, 0, NULL)) {
132 /* Find out more about this adapter */
134 snprintf(regpath, sizeof(regpath), "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, adapterid);
136 if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, regpath, 0, KEY_READ, &key2)) {
140 len = sizeof(adaptername);
141 err = RegQueryValueEx(key2, "Name", 0, 0, (LPBYTE)adaptername, &len);
150 if(!strcmp(device, adapterid)) {
159 if(!strcmp(iface, adaptername)) {
167 snprintf(tapname, sizeof(tapname), USERMODEDEVICEDIR "%s" TAPSUFFIX, adapterid);
168 device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
170 if(device_handle != INVALID_HANDLE_VALUE) {
179 logger(DEBUG_ALWAYS, LOG_ERR, "No Windows tap device found!");
184 device = xstrdup(adapterid);
188 iface = xstrdup(adaptername);
191 /* Try to open the corresponding tap device */
193 if(device_handle == INVALID_HANDLE_VALUE) {
194 snprintf(tapname, sizeof(tapname), USERMODEDEVICEDIR "%s" TAPSUFFIX, device);
195 device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
198 if(device_handle == INVALID_HANDLE_VALUE) {
199 logger(DEBUG_ALWAYS, LOG_ERR, "%s (%s) is not a usable Windows tap device: %s", device, iface, winerror(GetLastError()));
203 /* Get version information from tap device */
209 if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_VERSION, &info, sizeof(info), &info, sizeof(info), &len, NULL)) {
210 logger(DEBUG_ALWAYS, LOG_WARNING, "Could not get version information from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError()));
212 logger(DEBUG_ALWAYS, LOG_INFO, "TAP-Windows driver version: %lu.%lu%s", info[0], info[1], info[2] ? " (DEBUG)" : "");
214 /* Warn if using >=9.21. This is because starting from 9.21, TAP-Win32 seems to use a different, less efficient write path. */
215 if(info[0] == 9 && info[1] >= 21)
216 logger(DEBUG_ALWAYS, LOG_WARNING,
217 "You are using the newer (>= 9.0.0.21, NDIS6) series of TAP-Win32 drivers. "
218 "Using these drivers with tinc is not recommended as it can result in poor performance. "
219 "You might want to revert back to 9.0.0.9 instead.");
223 /* Get MAC address from tap device */
225 if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_MAC, mymac.x, sizeof(mymac.x), mymac.x, sizeof(mymac.x), &len, 0)) {
226 logger(DEBUG_ALWAYS, LOG_ERR, "Could not get MAC address from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError()));
230 if(routing_mode == RMODE_ROUTER) {
234 device_info = "Windows tap device";
236 logger(DEBUG_ALWAYS, LOG_INFO, "%s (%s) is a %s", device, iface, device_info);
238 device_read_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
239 device_write_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
244 static void enable_device(void) {
245 logger(DEBUG_ALWAYS, LOG_INFO, "Enabling %s", device_info);
249 DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL);
251 /* We don't use the write event directly, but GetOverlappedResult() does, internally. */
253 io_add_event(&device_read_io, device_handle_read, NULL, device_read_overlapped.hEvent);
257 static void disable_device(void) {
258 logger(DEBUG_ALWAYS, LOG_INFO, "Disabling %s", device_info);
260 io_del(&device_read_io);
264 DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL);
266 /* Note that we don't try to cancel ongoing I/O here - we just stop listening.
267 This is because some TAP-Win32 drivers don't seem to handle cancellation very well,
268 especially when combined with other events such as the computer going to sleep - cases
269 were observed where the GetOverlappedResult() would just block indefinitely and never
270 return in that case. */
273 static void close_device(void) {
274 CancelIo(device_handle);
276 /* According to MSDN, CancelIo() does not necessarily wait for the operation to complete.
277 To prevent race conditions, make sure the operation is complete
278 before we close the event it's referencing. */
282 if(!GetOverlappedResult(device_handle, &device_read_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED) {
283 logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s read to cancel: %s", device_info, device, winerror(GetLastError()));
286 if(device_write_packet.len > 0 && !GetOverlappedResult(device_handle, &device_write_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED) {
287 logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s write to cancel: %s", device_info, device, winerror(GetLastError()));
290 device_write_packet.len = 0;
292 CloseHandle(device_read_overlapped.hEvent);
293 CloseHandle(device_write_overlapped.hEvent);
295 CloseHandle(device_handle);
296 device_handle = INVALID_HANDLE_VALUE;
305 static bool read_packet(vpn_packet_t *packet) {
310 static bool write_packet(vpn_packet_t *packet) {
313 logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s",
314 packet->len, device_info);
316 if(device_write_packet.len > 0) {
317 /* Make sure the previous write operation is finished before we start the next one;
318 otherwise we end up with multiple write ops referencing the same OVERLAPPED structure,
319 which according to MSDN is a no-no. */
321 if(!GetOverlappedResult(device_handle, &device_write_overlapped, &outlen, FALSE)) {
322 if(GetLastError() != ERROR_IO_INCOMPLETE) {
323 logger(DEBUG_ALWAYS, LOG_ERR, "Error completing previously queued write to %s %s: %s", device_info, device, winerror(GetLastError()));
325 logger(DEBUG_TRAFFIC, LOG_ERR, "Previous overlapped write to %s %s still in progress", device_info, device);
332 /* Copy the packet, since the write operation might still be ongoing after we return. */
334 memcpy(&device_write_packet, packet, sizeof(*packet));
336 ResetEvent(device_write_overlapped.hEvent);
338 if(WriteFile(device_handle, DATA(&device_write_packet), device_write_packet.len, &outlen, &device_write_overlapped)) {
339 // Write was completed immediately.
340 device_write_packet.len = 0;
341 } else if(GetLastError() != ERROR_IO_PENDING) {
342 logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError()));
343 device_write_packet.len = 0;
350 const devops_t os_devops = {
351 .setup = setup_device,
352 .close = close_device,
354 .write = write_packet,
355 .enable = enable_device,
356 .disable = disable_device,