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) {
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);
104 if(device && iface) {
105 logger(DEBUG_ALWAYS, LOG_WARNING, "Warning: both Device and Interface specified, results may not be as expected");
108 /* Open registry and look for network adapters */
110 if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, NETWORK_CONNECTIONS_KEY, 0, KEY_READ, &key)) {
111 logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read registry: %s", winerror(GetLastError()));
116 len = sizeof(adapterid);
118 if(RegEnumKeyEx(key, i, adapterid, &len, 0, 0, 0, NULL)) {
122 /* Find out more about this adapter */
124 snprintf(regpath, sizeof(regpath), "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, adapterid);
126 if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, regpath, 0, KEY_READ, &key2)) {
130 len = sizeof(adaptername);
131 err = RegQueryValueEx(key2, "Name", 0, 0, (LPBYTE)adaptername, &len);
140 if(!strcmp(device, adapterid)) {
149 if(!strcmp(iface, adaptername)) {
157 snprintf(tapname, sizeof(tapname), USERMODEDEVICEDIR "%s" TAPSUFFIX, adapterid);
158 device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
160 if(device_handle != INVALID_HANDLE_VALUE) {
169 logger(DEBUG_ALWAYS, LOG_ERR, "No Windows tap device found!");
174 device = xstrdup(adapterid);
178 iface = xstrdup(adaptername);
181 /* Try to open the corresponding tap device */
183 if(device_handle == INVALID_HANDLE_VALUE) {
184 snprintf(tapname, sizeof(tapname), USERMODEDEVICEDIR "%s" TAPSUFFIX, device);
185 device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
188 if(device_handle == INVALID_HANDLE_VALUE) {
189 logger(DEBUG_ALWAYS, LOG_ERR, "%s (%s) is not a usable Windows tap device: %s", device, iface, winerror(GetLastError()));
193 /* Get version information from tap device */
199 if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_VERSION, &info, sizeof(info), &info, sizeof(info), &len, NULL)) {
200 logger(DEBUG_ALWAYS, LOG_WARNING, "Could not get version information from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError()));
202 logger(DEBUG_ALWAYS, LOG_INFO, "TAP-Windows driver version: %lu.%lu%s", info[0], info[1], info[2] ? " (DEBUG)" : "");
204 /* Warn if using >=9.21. This is because starting from 9.21, TAP-Win32 seems to use a different, less efficient write path. */
205 if(info[0] == 9 && info[1] >= 21)
206 logger(DEBUG_ALWAYS, LOG_WARNING,
207 "You are using the newer (>= 9.0.0.21, NDIS6) series of TAP-Win32 drivers. "
208 "Using these drivers with tinc is not recommanded as it can result in poor performance. "
209 "You might want to revert back to 9.0.0.9 instead.");
213 /* Get MAC address from tap device */
215 if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_MAC, mymac.x, sizeof(mymac.x), mymac.x, sizeof(mymac.x), &len, 0)) {
216 logger(DEBUG_ALWAYS, LOG_ERR, "Could not get MAC address from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError()));
220 if(routing_mode == RMODE_ROUTER) {
224 device_info = "Windows tap device";
226 logger(DEBUG_ALWAYS, LOG_INFO, "%s (%s) is a %s", device, iface, device_info);
228 device_read_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
229 device_write_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
234 static void enable_device(void) {
235 logger(DEBUG_ALWAYS, LOG_INFO, "Enabling %s", device_info);
239 DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL);
241 /* We don't use the write event directly, but GetOverlappedResult() does, internally. */
243 io_add_event(&device_read_io, device_handle_read, NULL, device_read_overlapped.hEvent);
247 static void disable_device(void) {
248 logger(DEBUG_ALWAYS, LOG_INFO, "Disabling %s", device_info);
250 io_del(&device_read_io);
254 DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL);
256 /* Note that we don't try to cancel ongoing I/O here - we just stop listening.
257 This is because some TAP-Win32 drivers don't seem to handle cancellation very well,
258 especially when combined with other events such as the computer going to sleep - cases
259 were observed where the GetOverlappedResult() would just block indefinitely and never
260 return in that case. */
263 static void close_device(void) {
264 CancelIo(device_handle);
266 /* According to MSDN, CancelIo() does not necessarily wait for the operation to complete.
267 To prevent race conditions, make sure the operation is complete
268 before we close the event it's referencing. */
272 if(!GetOverlappedResult(device_handle, &device_read_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED) {
273 logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s read to cancel: %s", device_info, device, winerror(GetLastError()));
276 if(device_write_packet.len > 0 && !GetOverlappedResult(device_handle, &device_write_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED) {
277 logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s write to cancel: %s", device_info, device, winerror(GetLastError()));
280 device_write_packet.len = 0;
282 CloseHandle(device_read_overlapped.hEvent);
283 CloseHandle(device_write_overlapped.hEvent);
285 CloseHandle(device_handle);
286 device_handle = INVALID_HANDLE_VALUE;
295 static bool read_packet(vpn_packet_t *packet) {
299 static bool write_packet(vpn_packet_t *packet) {
302 logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s",
303 packet->len, device_info);
305 if(device_write_packet.len > 0) {
306 /* Make sure the previous write operation is finished before we start the next one;
307 otherwise we end up with multiple write ops referencing the same OVERLAPPED structure,
308 which according to MSDN is a no-no. */
310 if(!GetOverlappedResult(device_handle, &device_write_overlapped, &outlen, FALSE)) {
311 if(GetLastError() != ERROR_IO_INCOMPLETE) {
312 logger(DEBUG_ALWAYS, LOG_ERR, "Error completing previously queued write to %s %s: %s", device_info, device, winerror(GetLastError()));
314 logger(DEBUG_TRAFFIC, LOG_ERR, "Previous overlapped write to %s %s still in progress", device_info, device);
321 /* Copy the packet, since the write operation might still be ongoing after we return. */
323 memcpy(&device_write_packet, packet, sizeof(*packet));
325 ResetEvent(device_write_overlapped.hEvent);
327 if(WriteFile(device_handle, DATA(&device_write_packet), device_write_packet.len, &outlen, &device_write_overlapped)) {
328 // Write was completed immediately.
329 device_write_packet.len = 0;
330 } else if(GetLastError() != ERROR_IO_PENDING) {
331 logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError()));
332 device_write_packet.len = 0;
339 const devops_t os_devops = {
340 .setup = setup_device,
341 .close = close_device,
343 .write = write_packet,
344 .enable = enable_device,
345 .disable = disable_device,