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 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) {
76 if(!GetOverlappedResult(device_handle, &device_read_overlapped, &len, FALSE)) {
77 logger(DEBUG_ALWAYS, LOG_ERR, "Error getting read result from %s %s: %s", device_info,
78 device, strerror(errno));
82 device_read_packet.len = len;
83 device_read_packet.priority = 0;
84 route(myself, &device_read_packet);
88 static bool setup_device(void) {
94 char adaptername[1024];
102 get_config_string(lookup_config(config_tree, "Device"), &device);
103 get_config_string(lookup_config(config_tree, "Interface"), &iface);
105 if(device && iface) {
106 logger(DEBUG_ALWAYS, LOG_WARNING, "Warning: both Device and Interface specified, results may not be as expected");
109 /* Open registry and look for network adapters */
111 if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, NETWORK_CONNECTIONS_KEY, 0, KEY_READ, &key)) {
112 logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read registry: %s", winerror(GetLastError()));
117 len = sizeof(adapterid);
119 if(RegEnumKeyEx(key, i, adapterid, &len, 0, 0, 0, NULL)) {
123 /* Find out more about this adapter */
125 snprintf(regpath, sizeof(regpath), "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, adapterid);
127 if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, regpath, 0, KEY_READ, &key2)) {
131 len = sizeof(adaptername);
132 err = RegQueryValueEx(key2, "Name", 0, 0, (LPBYTE)adaptername, &len);
141 if(!strcmp(device, adapterid)) {
150 if(!strcmp(iface, adaptername)) {
158 snprintf(tapname, sizeof(tapname), USERMODEDEVICEDIR "%s" TAPSUFFIX, adapterid);
159 device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
161 if(device_handle != INVALID_HANDLE_VALUE) {
170 logger(DEBUG_ALWAYS, LOG_ERR, "No Windows tap device found!");
175 device = xstrdup(adapterid);
179 iface = xstrdup(adaptername);
182 /* Try to open the corresponding tap device */
184 if(device_handle == INVALID_HANDLE_VALUE) {
185 snprintf(tapname, sizeof(tapname), USERMODEDEVICEDIR "%s" TAPSUFFIX, device);
186 device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
189 if(device_handle == INVALID_HANDLE_VALUE) {
190 logger(DEBUG_ALWAYS, LOG_ERR, "%s (%s) is not a usable Windows tap device: %s", device, iface, winerror(GetLastError()));
194 /* Get version information from tap device */
200 if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_VERSION, &info, sizeof(info), &info, sizeof(info), &len, NULL)) {
201 logger(DEBUG_ALWAYS, LOG_WARNING, "Could not get version information from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError()));
203 logger(DEBUG_ALWAYS, LOG_INFO, "TAP-Windows driver version: %lu.%lu%s", info[0], info[1], info[2] ? " (DEBUG)" : "");
205 /* Warn if using >=9.21. This is because starting from 9.21, TAP-Win32 seems to use a different, less efficient write path. */
206 if(info[0] == 9 && info[1] >= 21)
207 logger(DEBUG_ALWAYS, LOG_WARNING,
208 "You are using the newer (>= 9.0.0.21, NDIS6) series of TAP-Win32 drivers. "
209 "Using these drivers with tinc is not recommanded as it can result in poor performance. "
210 "You might want to revert back to 9.0.0.9 instead.");
214 /* Get MAC address from tap device */
216 if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_MAC, mymac.x, sizeof(mymac.x), mymac.x, sizeof(mymac.x), &len, 0)) {
217 logger(DEBUG_ALWAYS, LOG_ERR, "Could not get MAC address from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError()));
221 if(routing_mode == RMODE_ROUTER) {
225 device_info = "Windows tap device";
227 logger(DEBUG_ALWAYS, LOG_INFO, "%s (%s) is a %s", device, iface, device_info);
229 device_read_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
230 device_write_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
235 static void enable_device(void) {
236 logger(DEBUG_ALWAYS, LOG_INFO, "Enabling %s", device_info);
240 DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL);
242 /* We don't use the write event directly, but GetOverlappedResult() does, internally. */
244 io_add_event(&device_read_io, device_handle_read, NULL, device_read_overlapped.hEvent);
248 static void disable_device(void) {
249 logger(DEBUG_ALWAYS, LOG_INFO, "Disabling %s", device_info);
251 io_del(&device_read_io);
255 DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL);
257 /* Note that we don't try to cancel ongoing I/O here - we just stop listening.
258 This is because some TAP-Win32 drivers don't seem to handle cancellation very well,
259 especially when combined with other events such as the computer going to sleep - cases
260 were observed where the GetOverlappedResult() would just block indefinitely and never
261 return in that case. */
264 static void close_device(void) {
265 CancelIo(device_handle);
267 /* According to MSDN, CancelIo() does not necessarily wait for the operation to complete.
268 To prevent race conditions, make sure the operation is complete
269 before we close the event it's referencing. */
273 if(!GetOverlappedResult(device_handle, &device_read_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED) {
274 logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s read to cancel: %s", device_info, device, winerror(GetLastError()));
277 if(device_write_packet.len > 0 && !GetOverlappedResult(device_handle, &device_write_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED) {
278 logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s write to cancel: %s", device_info, device, winerror(GetLastError()));
281 device_write_packet.len = 0;
283 CloseHandle(device_read_overlapped.hEvent);
284 CloseHandle(device_write_overlapped.hEvent);
286 CloseHandle(device_handle);
287 device_handle = INVALID_HANDLE_VALUE;
296 static bool read_packet(vpn_packet_t *packet) {
300 static bool write_packet(vpn_packet_t *packet) {
303 logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s",
304 packet->len, device_info);
306 if(device_write_packet.len > 0) {
307 /* Make sure the previous write operation is finished before we start the next one;
308 otherwise we end up with multiple write ops referencing the same OVERLAPPED structure,
309 which according to MSDN is a no-no. */
311 if(!GetOverlappedResult(device_handle, &device_write_overlapped, &outlen, FALSE)) {
312 if(GetLastError() != ERROR_IO_INCOMPLETE) {
313 logger(DEBUG_ALWAYS, LOG_ERR, "Error completing previously queued write to %s %s: %s", device_info, device, winerror(GetLastError()));
315 logger(DEBUG_TRAFFIC, LOG_ERR, "Previous overlapped write to %s %s still in progress", device_info, device);
322 /* Copy the packet, since the write operation might still be ongoing after we return. */
324 memcpy(&device_write_packet, packet, sizeof(*packet));
326 ResetEvent(device_write_overlapped.hEvent);
328 if(WriteFile(device_handle, DATA(&device_write_packet), device_write_packet.len, &outlen, &device_write_overlapped)) {
329 // Write was completed immediately.
330 device_write_packet.len = 0;
331 } else if(GetLastError() != ERROR_IO_PENDING) {
332 logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError()));
333 device_write_packet.len = 0;
340 const devops_t os_devops = {
341 .setup = setup_device,
342 .close = close_device,
344 .write = write_packet,
345 .enable = enable_device,
346 .disable = disable_device,