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() {
51 device_read_overlapped.Offset = 0;
52 device_read_overlapped.OffsetHigh = 0;
57 ResetEvent(device_read_overlapped.hEvent);
60 status = ReadFile(device_handle, (void *)device_read_packet.data, MTU, &len, &device_read_overlapped);
63 if(GetLastError() != ERROR_IO_PENDING)
64 logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info,
65 device, strerror(errno));
70 device_read_packet.len = len;
71 device_read_packet.priority = 0;
72 route(myself, &device_read_packet);
76 static void device_handle_read(void *data, int flags) {
78 if(!GetOverlappedResult(device_handle, &device_read_overlapped, &len, FALSE)) {
79 logger(DEBUG_ALWAYS, LOG_ERR, "Error getting read result from %s %s: %s", device_info,
80 device, strerror(errno));
84 device_read_packet.len = len;
85 device_read_packet.priority = 0;
86 route(myself, &device_read_packet);
90 static bool setup_device(void) {
96 char adaptername[1024];
104 get_config_string(lookup_config(config_tree, "Device"), &device);
105 get_config_string(lookup_config(config_tree, "Interface"), &iface);
107 if(device && iface) {
108 logger(DEBUG_ALWAYS, LOG_WARNING, "Warning: both Device and Interface specified, results may not be as expected");
111 /* Open registry and look for network adapters */
113 if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, NETWORK_CONNECTIONS_KEY, 0, KEY_READ, &key)) {
114 logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read registry: %s", winerror(GetLastError()));
119 len = sizeof(adapterid);
121 if(RegEnumKeyEx(key, i, adapterid, &len, 0, 0, 0, NULL)) {
125 /* Find out more about this adapter */
127 snprintf(regpath, sizeof(regpath), "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, adapterid);
129 if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, regpath, 0, KEY_READ, &key2)) {
133 len = sizeof(adaptername);
134 err = RegQueryValueEx(key2, "Name", 0, 0, (LPBYTE)adaptername, &len);
143 if(!strcmp(device, adapterid)) {
152 if(!strcmp(iface, adaptername)) {
160 snprintf(tapname, sizeof(tapname), USERMODEDEVICEDIR "%s" TAPSUFFIX, adapterid);
161 device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
163 if(device_handle != INVALID_HANDLE_VALUE) {
172 logger(DEBUG_ALWAYS, LOG_ERR, "No Windows tap device found!");
177 device = xstrdup(adapterid);
181 iface = xstrdup(adaptername);
184 /* Try to open the corresponding tap device */
186 if(device_handle == INVALID_HANDLE_VALUE) {
187 snprintf(tapname, sizeof(tapname), USERMODEDEVICEDIR "%s" TAPSUFFIX, device);
188 device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
191 if(device_handle == INVALID_HANDLE_VALUE) {
192 logger(DEBUG_ALWAYS, LOG_ERR, "%s (%s) is not a usable Windows tap device: %s", device, iface, winerror(GetLastError()));
196 /* Get version information from tap device */
202 if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_VERSION, &info, sizeof(info), &info, sizeof(info), &len, NULL)) {
203 logger(DEBUG_ALWAYS, LOG_WARNING, "Could not get version information from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError()));
205 logger(DEBUG_ALWAYS, LOG_INFO, "TAP-Windows driver version: %lu.%lu%s", info[0], info[1], info[2] ? " (DEBUG)" : "");
207 /* Warn if using >=9.21. This is because starting from 9.21, TAP-Win32 seems to use a different, less efficient write path. */
208 if(info[0] == 9 && info[1] >= 21)
209 logger(DEBUG_ALWAYS, LOG_WARNING,
210 "You are using the newer (>= 9.0.0.21, NDIS6) series of TAP-Win32 drivers. "
211 "Using these drivers with tinc is not recommanded as it can result in poor performance. "
212 "You might want to revert back to 9.0.0.9 instead.");
216 /* Get MAC address from tap device */
218 if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_MAC, mymac.x, sizeof(mymac.x), mymac.x, sizeof(mymac.x), &len, 0)) {
219 logger(DEBUG_ALWAYS, LOG_ERR, "Could not get MAC address from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError()));
223 if(routing_mode == RMODE_ROUTER) {
227 device_info = "Windows tap device";
229 logger(DEBUG_ALWAYS, LOG_INFO, "%s (%s) is a %s", device, iface, device_info);
231 device_read_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
232 device_write_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
237 static void enable_device(void) {
238 logger(DEBUG_ALWAYS, LOG_INFO, "Enabling %s", device_info);
242 DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL);
244 /* We don't use the write event directly, but GetOverlappedResult() does, internally. */
246 io_add_event(&device_read_io, device_handle_read, NULL, device_read_overlapped.hEvent);
250 static void disable_device(void) {
251 logger(DEBUG_ALWAYS, LOG_INFO, "Disabling %s", device_info);
253 io_del(&device_read_io);
257 DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL);
259 /* Note that we don't try to cancel ongoing I/O here - we just stop listening.
260 This is because some TAP-Win32 drivers don't seem to handle cancellation very well,
261 especially when combined with other events such as the computer going to sleep - cases
262 were observed where the GetOverlappedResult() would just block indefinitely and never
263 return in that case. */
266 static void close_device(void) {
267 CancelIo(device_handle);
269 /* According to MSDN, CancelIo() does not necessarily wait for the operation to complete.
270 To prevent race conditions, make sure the operation is complete
271 before we close the event it's referencing. */
275 if(!GetOverlappedResult(device_handle, &device_read_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED) {
276 logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s read to cancel: %s", device_info, device, winerror(GetLastError()));
279 if(device_write_packet.len > 0 && !GetOverlappedResult(device_handle, &device_write_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED) {
280 logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s write to cancel: %s", device_info, device, winerror(GetLastError()));
283 device_write_packet.len = 0;
285 CloseHandle(device_read_overlapped.hEvent);
286 CloseHandle(device_write_overlapped.hEvent);
288 CloseHandle(device_handle);
289 device_handle = INVALID_HANDLE_VALUE;
298 static bool read_packet(vpn_packet_t *packet) {
302 static bool write_packet(vpn_packet_t *packet) {
305 logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s",
306 packet->len, device_info);
308 if(device_write_packet.len > 0) {
309 /* Make sure the previous write operation is finished before we start the next one;
310 otherwise we end up with multiple write ops referencing the same OVERLAPPED structure,
311 which according to MSDN is a no-no. */
313 if(!GetOverlappedResult(device_handle, &device_write_overlapped, &outlen, FALSE)) {
314 int log_level = (GetLastError() == ERROR_IO_INCOMPLETE) ? DEBUG_TRAFFIC : DEBUG_ALWAYS;
315 logger(log_level, LOG_ERR, "Error while checking previous write to %s %s: %s", device_info, device, winerror(GetLastError()));
320 /* Copy the packet, since the write operation might still be ongoing after we return. */
322 memcpy(&device_write_packet, packet, sizeof(*packet));
324 if(WriteFile(device_handle, DATA(&device_write_packet), device_write_packet.len, &outlen, &device_write_overlapped)) {
325 device_write_packet.len = 0;
326 } else if(GetLastError() != ERROR_IO_PENDING) {
327 logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError()));
334 const devops_t os_devops = {
335 .setup = setup_device,
336 .close = close_device,
338 .write = write_packet,
339 .enable = enable_device,
340 .disable = disable_device,