2 device.c -- Interaction with Windows tap driver in a MinGW environment
3 Copyright (C) 2002-2005 Ivo Timmermans,
4 2002-2014 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 char *device_info = NULL;
50 static void device_issue_read() {
51 device_read_overlapped.Offset = 0;
52 device_read_overlapped.OffsetHigh = 0;
57 status = ReadFile(device_handle, (void *)device_read_packet.data, MTU, &len, &device_read_overlapped);
59 if (GetLastError() != ERROR_IO_PENDING)
60 logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info,
61 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) {
72 ResetEvent(device_read_overlapped.hEvent);
75 if (!GetOverlappedResult(device_handle, &device_read_overlapped, &len, FALSE)) {
76 logger(DEBUG_ALWAYS, LOG_ERR, "Error getting read result from %s %s: %s", device_info,
77 device, strerror(errno));
81 device_read_packet.len = len;
82 device_read_packet.priority = 0;
83 route(myself, &device_read_packet);
87 static bool setup_device(void) {
93 char adaptername[1024];
101 get_config_string(lookup_config(config_tree, "Device"), &device);
102 get_config_string(lookup_config(config_tree, "Interface"), &iface);
105 logger(DEBUG_ALWAYS, LOG_WARNING, "Warning: both Device and Interface specified, results may not be as expected");
107 /* Open registry and look for network adapters */
109 if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, NETWORK_CONNECTIONS_KEY, 0, KEY_READ, &key)) {
110 logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read registry: %s", winerror(GetLastError()));
115 len = sizeof adapterid;
116 if(RegEnumKeyEx(key, i, adapterid, &len, 0, 0, 0, NULL))
119 /* Find out more about this adapter */
121 snprintf(regpath, sizeof regpath, "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, adapterid);
123 if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, regpath, 0, KEY_READ, &key2))
126 len = sizeof adaptername;
127 err = RegQueryValueEx(key2, "Name", 0, 0, (LPBYTE)adaptername, &len);
135 if(!strcmp(device, adapterid)) {
143 if(!strcmp(iface, adaptername)) {
150 snprintf(tapname, sizeof tapname, USERMODEDEVICEDIR "%s" TAPSUFFIX, adapterid);
151 device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
152 if(device_handle != INVALID_HANDLE_VALUE) {
161 logger(DEBUG_ALWAYS, LOG_ERR, "No Windows tap device found!");
166 device = xstrdup(adapterid);
169 iface = xstrdup(adaptername);
171 /* Try to open the corresponding tap device */
173 if(device_handle == INVALID_HANDLE_VALUE) {
174 snprintf(tapname, sizeof tapname, USERMODEDEVICEDIR "%s" TAPSUFFIX, device);
175 device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
178 if(device_handle == INVALID_HANDLE_VALUE) {
179 logger(DEBUG_ALWAYS, LOG_ERR, "%s (%s) is not a usable Windows tap device: %s", device, iface, winerror(GetLastError()));
183 /* Get version information from tap device */
188 if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_VERSION, &info, sizeof info, &info, sizeof info, &len, NULL))
189 logger(DEBUG_ALWAYS, LOG_WARNING, "Could not get version information from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError()));
191 logger(DEBUG_ALWAYS, LOG_INFO, "TAP-Windows driver version: %lu.%lu%s", info[0], info[1], info[2] ? " (DEBUG)" : "");
193 /* Warn if using >=9.21. This is because starting from 9.21, TAP-Win32 seems to use a different, less efficient write path. */
194 if(info[0] == 9 && info[1] >= 21)
195 logger(DEBUG_ALWAYS, LOG_WARNING,
196 "You are using the newer (>= 9.0.0.21, NDIS6) series of TAP-Win32 drivers. "
197 "Using these drivers with tinc is not recommanded as it can result in poor performance. "
198 "You might want to revert back to 9.0.0.9 instead.");
202 /* Get MAC address from tap device */
204 if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_MAC, mymac.x, sizeof mymac.x, mymac.x, sizeof mymac.x, &len, 0)) {
205 logger(DEBUG_ALWAYS, LOG_ERR, "Could not get MAC address from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError()));
209 if(routing_mode == RMODE_ROUTER) {
213 device_info = "Windows tap device";
215 logger(DEBUG_ALWAYS, LOG_INFO, "%s (%s) is a %s", device, iface, device_info);
217 device_read_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
218 device_write_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
223 static void enable_device(void) {
224 logger(DEBUG_ALWAYS, LOG_INFO, "Enabling %s", device_info);
228 DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof status, &status, sizeof status, &len, NULL);
230 /* We don't use the write event directly, but GetOverlappedResult() does, internally. */
232 io_add_event(&device_read_io, device_handle_read, NULL, device_read_overlapped.hEvent);
236 static void disable_device(void) {
237 logger(DEBUG_ALWAYS, LOG_INFO, "Disabling %s", device_info);
239 io_del(&device_read_io);
243 DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof status, &status, sizeof status, &len, NULL);
245 /* Note that we don't try to cancel ongoing I/O here - we just stop listening.
246 This is because some TAP-Win32 drivers don't seem to handle cancellation very well,
247 especially when combined with other events such as the computer going to sleep - cases
248 were observed where the GetOverlappedResult() would just block indefinitely and never
249 return in that case. */
252 static void close_device(void) {
253 CancelIo(device_handle);
255 /* According to MSDN, CancelIo() does not necessarily wait for the operation to complete.
256 To prevent race conditions, make sure the operation is complete
257 before we close the event it's referencing. */
260 if(!GetOverlappedResult(device_handle, &device_read_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED)
261 logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s read to cancel: %s", device_info, device, winerror(GetLastError()));
262 if(device_write_packet.len > 0 && !GetOverlappedResult(device_handle, &device_write_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED)
263 logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s write to cancel: %s", device_info, device, winerror(GetLastError()));
264 device_write_packet.len = 0;
266 CloseHandle(device_read_overlapped.hEvent);
267 CloseHandle(device_write_overlapped.hEvent);
269 CloseHandle(device_handle); device_handle = INVALID_HANDLE_VALUE;
271 free(device); device = NULL;
272 free(iface); iface = NULL;
276 static bool read_packet(vpn_packet_t *packet) {
280 static bool write_packet(vpn_packet_t *packet) {
283 logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s",
284 packet->len, device_info);
286 if(device_write_packet.len > 0) {
287 /* Make sure the previous write operation is finished before we start the next one;
288 otherwise we end up with multiple write ops referencing the same OVERLAPPED structure,
289 which according to MSDN is a no-no. */
291 if(!GetOverlappedResult(device_handle, &device_write_overlapped, &outlen, FALSE)) {
292 int log_level = (GetLastError() == ERROR_IO_INCOMPLETE) ? DEBUG_TRAFFIC : DEBUG_ALWAYS;
293 logger(log_level, LOG_ERR, "Error while checking previous write to %s %s: %s", device_info, device, winerror(GetLastError()));
298 /* Copy the packet, since the write operation might still be ongoing after we return. */
300 memcpy(&device_write_packet, packet, sizeof *packet);
302 if(WriteFile(device_handle, DATA(&device_write_packet), device_write_packet.len, &outlen, &device_write_overlapped))
303 device_write_packet.len = 0;
304 else if (GetLastError() != ERROR_IO_PENDING) {
305 logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError()));
312 const devops_t os_devops = {
313 .setup = setup_device,
314 .close = close_device,
316 .write = write_packet,
317 .enable = enable_device,
318 .disable = disable_device,