d90c69b0244622138422390c110ba7b3e7ea4c0e
[tinc] / src / mingw / device.c
1 /*
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>
5
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.
10
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.
15
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.
19 */
20
21 #include "../system.h"
22
23 #include <windows.h>
24 #include <winioctl.h>
25
26 #include "../conf.h"
27 #include "../device.h"
28 #include "../logger.h"
29 #include "../names.h"
30 #include "../net.h"
31 #include "../route.h"
32 #include "../utils.h"
33 #include "../xalloc.h"
34
35 #include "common.h"
36
37 int device_fd = -1;
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;
44 char *device = NULL;
45 char *iface = NULL;
46 static const char *device_info = "Windows tap device";
47
48 extern char *myport;
49
50 static void device_issue_read() {
51         device_read_overlapped.Offset = 0;
52         device_read_overlapped.OffsetHigh = 0;
53
54         int status;
55
56         for(;;) {
57                 ResetEvent(device_read_overlapped.hEvent);
58
59                 DWORD len;
60                 status = ReadFile(device_handle, (void *)device_read_packet.data, MTU, &len, &device_read_overlapped);
61
62                 if(!status) {
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));
66
67                         break;
68                 }
69
70                 device_read_packet.len = len;
71                 device_read_packet.priority = 0;
72                 route(myself, &device_read_packet);
73         }
74 }
75
76 static void device_handle_read(void *data, int flags) {
77         DWORD len;
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));
81                 return;
82         }
83
84         device_read_packet.len = len;
85         device_read_packet.priority = 0;
86         route(myself, &device_read_packet);
87         device_issue_read();
88 }
89
90 static bool setup_device(void) {
91         HKEY key, key2;
92         int i;
93
94         char regpath[1024];
95         char adapterid[1024];
96         char adaptername[1024];
97         char tapname[1024];
98         DWORD len;
99
100         bool found = false;
101
102         int err;
103
104         get_config_string(lookup_config(config_tree, "Device"), &device);
105         get_config_string(lookup_config(config_tree, "Interface"), &iface);
106
107         if(device && iface) {
108                 logger(DEBUG_ALWAYS, LOG_WARNING, "Warning: both Device and Interface specified, results may not be as expected");
109         }
110
111         /* Open registry and look for network adapters */
112
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()));
115                 return false;
116         }
117
118         for(i = 0; ; i++) {
119                 len = sizeof(adapterid);
120
121                 if(RegEnumKeyEx(key, i, adapterid, &len, 0, 0, 0, NULL)) {
122                         break;
123                 }
124
125                 /* Find out more about this adapter */
126
127                 snprintf(regpath, sizeof(regpath), "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, adapterid);
128
129                 if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, regpath, 0, KEY_READ, &key2)) {
130                         continue;
131                 }
132
133                 len = sizeof(adaptername);
134                 err = RegQueryValueEx(key2, "Name", 0, 0, (LPBYTE)adaptername, &len);
135
136                 RegCloseKey(key2);
137
138                 if(err) {
139                         continue;
140                 }
141
142                 if(device) {
143                         if(!strcmp(device, adapterid)) {
144                                 found = true;
145                                 break;
146                         } else {
147                                 continue;
148                         }
149                 }
150
151                 if(iface) {
152                         if(!strcmp(iface, adaptername)) {
153                                 found = true;
154                                 break;
155                         } else {
156                                 continue;
157                         }
158                 }
159
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);
162
163                 if(device_handle != INVALID_HANDLE_VALUE) {
164                         found = true;
165                         break;
166                 }
167         }
168
169         RegCloseKey(key);
170
171         if(!found) {
172                 logger(DEBUG_ALWAYS, LOG_ERR, "No Windows tap device found!");
173                 return false;
174         }
175
176         if(!device) {
177                 device = xstrdup(adapterid);
178         }
179
180         if(!iface) {
181                 iface = xstrdup(adaptername);
182         }
183
184         /* Try to open the corresponding tap device */
185
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);
189         }
190
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()));
193                 return false;
194         }
195
196         /* Get version information from tap device */
197
198         {
199                 ULONG info[3] = {0};
200                 DWORD len;
201
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()));
204                 } else {
205                         logger(DEBUG_ALWAYS, LOG_INFO, "TAP-Windows driver version: %lu.%lu%s", info[0], info[1], info[2] ? " (DEBUG)" : "");
206
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.");
213                 }
214         }
215
216         /* Get MAC address from tap device */
217
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()));
220                 return false;
221         }
222
223         if(routing_mode == RMODE_ROUTER) {
224                 overwrite_mac = 1;
225         }
226
227         device_info = "Windows tap device";
228
229         logger(DEBUG_ALWAYS, LOG_INFO, "%s (%s) is a %s", device, iface, device_info);
230
231         device_read_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
232         device_write_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
233
234         return true;
235 }
236
237 static void enable_device(void) {
238         logger(DEBUG_ALWAYS, LOG_INFO, "Enabling %s", device_info);
239
240         ULONG status = 1;
241         DWORD len;
242         DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL);
243
244         /* We don't use the write event directly, but GetOverlappedResult() does, internally. */
245
246         io_add_event(&device_read_io, device_handle_read, NULL, device_read_overlapped.hEvent);
247         device_issue_read();
248 }
249
250 static void disable_device(void) {
251         logger(DEBUG_ALWAYS, LOG_INFO, "Disabling %s", device_info);
252
253         io_del(&device_read_io);
254
255         ULONG status = 0;
256         DWORD len;
257         DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL);
258
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. */
264 }
265
266 static void close_device(void) {
267         CancelIo(device_handle);
268
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. */
272
273         DWORD len;
274
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()));
277         }
278
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()));
281         }
282
283         device_write_packet.len = 0;
284
285         CloseHandle(device_read_overlapped.hEvent);
286         CloseHandle(device_write_overlapped.hEvent);
287
288         CloseHandle(device_handle);
289         device_handle = INVALID_HANDLE_VALUE;
290
291         free(device);
292         device = NULL;
293         free(iface);
294         iface = NULL;
295         device_info = NULL;
296 }
297
298 static bool read_packet(vpn_packet_t *packet) {
299         return false;
300 }
301
302 static bool write_packet(vpn_packet_t *packet) {
303         DWORD outlen;
304
305         logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s",
306                packet->len, device_info);
307
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. */
312
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()));
316                         return false;
317                 }
318         }
319
320         /* Copy the packet, since the write operation might still be ongoing after we return. */
321
322         memcpy(&device_write_packet, packet, sizeof(*packet));
323
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()));
328                 return false;
329         }
330
331         return true;
332 }
333
334 const devops_t os_devops = {
335         .setup = setup_device,
336         .close = close_device,
337         .read = read_packet,
338         .write = write_packet,
339         .enable = enable_device,
340         .disable = disable_device,
341 };