2 device.c -- Interaction with Windows tap driver in a MinGW environment
3 Copyright (C) 2002-2005 Ivo Timmermans,
4 2002-2022 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";
48 static void device_issue_read(void) {
52 ResetEvent(device_read_overlapped.hEvent);
55 status = ReadFile(device_handle, (void *)device_read_packet.data, MTU, &len, &device_read_overlapped);
58 if(GetLastError() != ERROR_IO_PENDING)
59 logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info,
60 device, strerror(errno));
65 device_read_packet.len = len;
66 device_read_packet.priority = 0;
67 route(myself, &device_read_packet);
71 static void device_handle_read(void *data, int flags) {
77 if(!GetOverlappedResult(device_handle, &device_read_overlapped, &len, FALSE)) {
78 logger(DEBUG_ALWAYS, LOG_ERR, "Error getting read result from %s %s: %s", device_info,
79 device, strerror(errno));
81 if(GetLastError() != ERROR_IO_INCOMPLETE) {
82 /* Must reset event or it will keep firing. */
83 ResetEvent(device_read_overlapped.hEvent);
89 device_read_packet.len = len;
90 device_read_packet.priority = 0;
91 route(myself, &device_read_packet);
95 static bool setup_device(void) {
100 char adapterid[1024];
101 char adaptername[1024];
109 get_config_string(lookup_config(&config_tree, "Device"), &device);
110 get_config_string(lookup_config(&config_tree, "Interface"), &iface);
112 if(device && iface) {
113 logger(DEBUG_ALWAYS, LOG_WARNING, "Warning: both Device and Interface specified, results may not be as expected");
116 /* Open registry and look for network adapters */
118 if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, NETWORK_CONNECTIONS_KEY, 0, KEY_READ, &key)) {
119 logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read registry: %s", winerror(GetLastError()));
124 len = sizeof(adapterid);
126 if(RegEnumKeyEx(key, i, adapterid, &len, 0, 0, 0, NULL)) {
130 /* Find out more about this adapter */
132 snprintf(regpath, sizeof(regpath), "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, adapterid);
134 if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, regpath, 0, KEY_READ, &key2)) {
138 len = sizeof(adaptername);
139 err = RegQueryValueEx(key2, "Name", 0, 0, (LPBYTE)adaptername, &len);
148 if(!strcmp(device, adapterid)) {
157 if(!strcmp(iface, adaptername)) {
165 snprintf(tapname, sizeof(tapname), USERMODEDEVICEDIR "%s" TAPSUFFIX, adapterid);
166 device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
168 if(device_handle != INVALID_HANDLE_VALUE) {
177 logger(DEBUG_ALWAYS, LOG_ERR, "No Windows tap device found!");
182 device = xstrdup(adapterid);
186 iface = xstrdup(adaptername);
189 /* Try to open the corresponding tap device */
191 if(device_handle == INVALID_HANDLE_VALUE) {
192 snprintf(tapname, sizeof(tapname), USERMODEDEVICEDIR "%s" TAPSUFFIX, device);
193 device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
196 if(device_handle == INVALID_HANDLE_VALUE) {
197 logger(DEBUG_ALWAYS, LOG_ERR, "%s (%s) is not a usable Windows tap device: %s", device, iface, winerror(GetLastError()));
201 /* Get version information from tap device */
207 if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_VERSION, &info, sizeof(info), &info, sizeof(info), &len, NULL)) {
208 logger(DEBUG_ALWAYS, LOG_WARNING, "Could not get version information from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError()));
210 logger(DEBUG_ALWAYS, LOG_INFO, "TAP-Windows driver version: %lu.%lu%s", info[0], info[1], info[2] ? " (DEBUG)" : "");
212 /* Warn if using >=9.21. This is because starting from 9.21, TAP-Win32 seems to use a different, less efficient write path. */
213 if(info[0] == 9 && info[1] >= 21)
214 logger(DEBUG_ALWAYS, LOG_WARNING,
215 "You are using the newer (>= 9.0.0.21, NDIS6) series of TAP-Win32 drivers. "
216 "Using these drivers with tinc is not recommended as it can result in poor performance. "
217 "You might want to revert back to 9.0.0.9 instead.");
221 /* Get MAC address from tap device */
223 if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_MAC, mymac.x, sizeof(mymac.x), mymac.x, sizeof(mymac.x), &len, 0)) {
224 logger(DEBUG_ALWAYS, LOG_ERR, "Could not get MAC address from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError()));
228 if(routing_mode == RMODE_ROUTER) {
232 device_info = "Windows tap device";
234 logger(DEBUG_ALWAYS, LOG_INFO, "%s (%s) is a %s", device, iface, device_info);
236 device_read_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
237 device_write_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
242 static void enable_device(void) {
243 logger(DEBUG_ALWAYS, LOG_INFO, "Enabling %s", device_info);
247 DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL);
249 /* We don't use the write event directly, but GetOverlappedResult() does, internally. */
251 io_add_event(&device_read_io, device_handle_read, NULL, device_read_overlapped.hEvent);
255 static void disable_device(void) {
256 logger(DEBUG_ALWAYS, LOG_INFO, "Disabling %s", device_info);
258 io_del(&device_read_io);
262 DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL);
264 /* Note that we don't try to cancel ongoing I/O here - we just stop listening.
265 This is because some TAP-Win32 drivers don't seem to handle cancellation very well,
266 especially when combined with other events such as the computer going to sleep - cases
267 were observed where the GetOverlappedResult() would just block indefinitely and never
268 return in that case. */
271 static void close_device(void) {
272 CancelIo(device_handle);
274 /* According to MSDN, CancelIo() does not necessarily wait for the operation to complete.
275 To prevent race conditions, make sure the operation is complete
276 before we close the event it's referencing. */
280 if(!GetOverlappedResult(device_handle, &device_read_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED) {
281 logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s read to cancel: %s", device_info, device, winerror(GetLastError()));
284 if(device_write_packet.len > 0 && !GetOverlappedResult(device_handle, &device_write_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED) {
285 logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s write to cancel: %s", device_info, device, winerror(GetLastError()));
288 device_write_packet.len = 0;
290 CloseHandle(device_read_overlapped.hEvent);
291 CloseHandle(device_write_overlapped.hEvent);
293 CloseHandle(device_handle);
294 device_handle = INVALID_HANDLE_VALUE;
303 static bool read_packet(vpn_packet_t *packet) {
308 static bool write_packet(vpn_packet_t *packet) {
311 logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s",
312 packet->len, device_info);
314 if(device_write_packet.len > 0) {
315 /* Make sure the previous write operation is finished before we start the next one;
316 otherwise we end up with multiple write ops referencing the same OVERLAPPED structure,
317 which according to MSDN is a no-no. */
319 if(!GetOverlappedResult(device_handle, &device_write_overlapped, &outlen, FALSE)) {
320 if(GetLastError() != ERROR_IO_INCOMPLETE) {
321 logger(DEBUG_ALWAYS, LOG_ERR, "Error completing previously queued write to %s %s: %s", device_info, device, winerror(GetLastError()));
323 logger(DEBUG_TRAFFIC, LOG_ERR, "Previous overlapped write to %s %s still in progress", device_info, device);
330 /* Copy the packet, since the write operation might still be ongoing after we return. */
332 memcpy(&device_write_packet, packet, sizeof(*packet));
334 ResetEvent(device_write_overlapped.hEvent);
336 if(WriteFile(device_handle, DATA(&device_write_packet), device_write_packet.len, &outlen, &device_write_overlapped)) {
337 // Write was completed immediately.
338 device_write_packet.len = 0;
339 } else if(GetLastError() != ERROR_IO_PENDING) {
340 logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError()));
341 device_write_packet.len = 0;
348 const devops_t os_devops = {
349 .setup = setup_device,
350 .close = close_device,
352 .write = write_packet,
353 .enable = enable_device,
354 .disable = disable_device,