Fix warnings when compiling for Windows.
[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-2018 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         int status;
52
53         for(;;) {
54                 ResetEvent(device_read_overlapped.hEvent);
55
56                 DWORD len;
57                 status = ReadFile(device_handle, (void *)device_read_packet.data, MTU, &len, &device_read_overlapped);
58
59                 if(!status) {
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));
63
64                         break;
65                 }
66
67                 device_read_packet.len = len;
68                 device_read_packet.priority = 0;
69                 route(myself, &device_read_packet);
70         }
71 }
72
73 static void device_handle_read(void *data, int flags) {
74         (void)data;
75         (void)flags;
76
77         DWORD len;
78
79         if(!GetOverlappedResult(device_handle, &device_read_overlapped, &len, FALSE)) {
80                 logger(DEBUG_ALWAYS, LOG_ERR, "Error getting read result from %s %s: %s", device_info,
81                        device, strerror(errno));
82
83                 if(GetLastError() != ERROR_IO_INCOMPLETE) {
84                         /* Must reset event or it will keep firing. */
85                         ResetEvent(device_read_overlapped.hEvent);
86                 }
87
88                 return;
89         }
90
91         device_read_packet.len = len;
92         device_read_packet.priority = 0;
93         route(myself, &device_read_packet);
94         device_issue_read();
95 }
96
97 static bool setup_device(void) {
98         HKEY key, key2;
99         int i;
100
101         char regpath[1024];
102         char adapterid[1024];
103         char adaptername[1024];
104         char tapname[1024];
105         DWORD len;
106
107         bool found = false;
108
109         int err;
110
111         get_config_string(lookup_config(config_tree, "Device"), &device);
112         get_config_string(lookup_config(config_tree, "Interface"), &iface);
113
114         if(device && iface) {
115                 logger(DEBUG_ALWAYS, LOG_WARNING, "Warning: both Device and Interface specified, results may not be as expected");
116         }
117
118         /* Open registry and look for network adapters */
119
120         if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, NETWORK_CONNECTIONS_KEY, 0, KEY_READ, &key)) {
121                 logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read registry: %s", winerror(GetLastError()));
122                 return false;
123         }
124
125         for(i = 0; ; i++) {
126                 len = sizeof(adapterid);
127
128                 if(RegEnumKeyEx(key, i, adapterid, &len, 0, 0, 0, NULL)) {
129                         break;
130                 }
131
132                 /* Find out more about this adapter */
133
134                 snprintf(regpath, sizeof(regpath), "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, adapterid);
135
136                 if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, regpath, 0, KEY_READ, &key2)) {
137                         continue;
138                 }
139
140                 len = sizeof(adaptername);
141                 err = RegQueryValueEx(key2, "Name", 0, 0, (LPBYTE)adaptername, &len);
142
143                 RegCloseKey(key2);
144
145                 if(err) {
146                         continue;
147                 }
148
149                 if(device) {
150                         if(!strcmp(device, adapterid)) {
151                                 found = true;
152                                 break;
153                         } else {
154                                 continue;
155                         }
156                 }
157
158                 if(iface) {
159                         if(!strcmp(iface, adaptername)) {
160                                 found = true;
161                                 break;
162                         } else {
163                                 continue;
164                         }
165                 }
166
167                 snprintf(tapname, sizeof(tapname), USERMODEDEVICEDIR "%s" TAPSUFFIX, adapterid);
168                 device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
169
170                 if(device_handle != INVALID_HANDLE_VALUE) {
171                         found = true;
172                         break;
173                 }
174         }
175
176         RegCloseKey(key);
177
178         if(!found) {
179                 logger(DEBUG_ALWAYS, LOG_ERR, "No Windows tap device found!");
180                 return false;
181         }
182
183         if(!device) {
184                 device = xstrdup(adapterid);
185         }
186
187         if(!iface) {
188                 iface = xstrdup(adaptername);
189         }
190
191         /* Try to open the corresponding tap device */
192
193         if(device_handle == INVALID_HANDLE_VALUE) {
194                 snprintf(tapname, sizeof(tapname), USERMODEDEVICEDIR "%s" TAPSUFFIX, device);
195                 device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
196         }
197
198         if(device_handle == INVALID_HANDLE_VALUE) {
199                 logger(DEBUG_ALWAYS, LOG_ERR, "%s (%s) is not a usable Windows tap device: %s", device, iface, winerror(GetLastError()));
200                 return false;
201         }
202
203         /* Get version information from tap device */
204
205         {
206                 ULONG info[3] = {0};
207                 DWORD len;
208
209                 if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_VERSION, &info, sizeof(info), &info, sizeof(info), &len, NULL)) {
210                         logger(DEBUG_ALWAYS, LOG_WARNING, "Could not get version information from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError()));
211                 } else {
212                         logger(DEBUG_ALWAYS, LOG_INFO, "TAP-Windows driver version: %lu.%lu%s", info[0], info[1], info[2] ? " (DEBUG)" : "");
213
214                         /* Warn if using >=9.21. This is because starting from 9.21, TAP-Win32 seems to use a different, less efficient write path. */
215                         if(info[0] == 9 && info[1] >= 21)
216                                 logger(DEBUG_ALWAYS, LOG_WARNING,
217                                        "You are using the newer (>= 9.0.0.21, NDIS6) series of TAP-Win32 drivers. "
218                                        "Using these drivers with tinc is not recommended as it can result in poor performance. "
219                                        "You might want to revert back to 9.0.0.9 instead.");
220                 }
221         }
222
223         /* Get MAC address from tap device */
224
225         if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_MAC, mymac.x, sizeof(mymac.x), mymac.x, sizeof(mymac.x), &len, 0)) {
226                 logger(DEBUG_ALWAYS, LOG_ERR, "Could not get MAC address from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError()));
227                 return false;
228         }
229
230         if(routing_mode == RMODE_ROUTER) {
231                 overwrite_mac = 1;
232         }
233
234         device_info = "Windows tap device";
235
236         logger(DEBUG_ALWAYS, LOG_INFO, "%s (%s) is a %s", device, iface, device_info);
237
238         device_read_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
239         device_write_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
240
241         return true;
242 }
243
244 static void enable_device(void) {
245         logger(DEBUG_ALWAYS, LOG_INFO, "Enabling %s", device_info);
246
247         ULONG status = 1;
248         DWORD len;
249         DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL);
250
251         /* We don't use the write event directly, but GetOverlappedResult() does, internally. */
252
253         io_add_event(&device_read_io, device_handle_read, NULL, device_read_overlapped.hEvent);
254         device_issue_read();
255 }
256
257 static void disable_device(void) {
258         logger(DEBUG_ALWAYS, LOG_INFO, "Disabling %s", device_info);
259
260         io_del(&device_read_io);
261
262         ULONG status = 0;
263         DWORD len;
264         DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL);
265
266         /* Note that we don't try to cancel ongoing I/O here - we just stop listening.
267            This is because some TAP-Win32 drivers don't seem to handle cancellation very well,
268            especially when combined with other events such as the computer going to sleep - cases
269            were observed where the GetOverlappedResult() would just block indefinitely and never
270            return in that case. */
271 }
272
273 static void close_device(void) {
274         CancelIo(device_handle);
275
276         /* According to MSDN, CancelIo() does not necessarily wait for the operation to complete.
277            To prevent race conditions, make sure the operation is complete
278            before we close the event it's referencing. */
279
280         DWORD len;
281
282         if(!GetOverlappedResult(device_handle, &device_read_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED) {
283                 logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s read to cancel: %s", device_info, device, winerror(GetLastError()));
284         }
285
286         if(device_write_packet.len > 0 && !GetOverlappedResult(device_handle, &device_write_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED) {
287                 logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s write to cancel: %s", device_info, device, winerror(GetLastError()));
288         }
289
290         device_write_packet.len = 0;
291
292         CloseHandle(device_read_overlapped.hEvent);
293         CloseHandle(device_write_overlapped.hEvent);
294
295         CloseHandle(device_handle);
296         device_handle = INVALID_HANDLE_VALUE;
297
298         free(device);
299         device = NULL;
300         free(iface);
301         iface = NULL;
302         device_info = NULL;
303 }
304
305 static bool read_packet(vpn_packet_t *packet) {
306         (void)packet;
307         return false;
308 }
309
310 static bool write_packet(vpn_packet_t *packet) {
311         DWORD outlen;
312
313         logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s",
314                packet->len, device_info);
315
316         if(device_write_packet.len > 0) {
317                 /* Make sure the previous write operation is finished before we start the next one;
318                    otherwise we end up with multiple write ops referencing the same OVERLAPPED structure,
319                    which according to MSDN is a no-no. */
320
321                 if(!GetOverlappedResult(device_handle, &device_write_overlapped, &outlen, FALSE)) {
322                         if(GetLastError() != ERROR_IO_INCOMPLETE) {
323                                 logger(DEBUG_ALWAYS, LOG_ERR, "Error completing previously queued write to %s %s: %s", device_info, device, winerror(GetLastError()));
324                         } else {
325                                 logger(DEBUG_TRAFFIC, LOG_ERR, "Previous overlapped write to %s %s still in progress", device_info, device);
326                                 // drop this packet
327                                 return true;
328                         }
329                 }
330         }
331
332         /* Copy the packet, since the write operation might still be ongoing after we return. */
333
334         memcpy(&device_write_packet, packet, sizeof(*packet));
335
336         ResetEvent(device_write_overlapped.hEvent);
337
338         if(WriteFile(device_handle, DATA(&device_write_packet), device_write_packet.len, &outlen, &device_write_overlapped)) {
339                 // Write was completed immediately.
340                 device_write_packet.len = 0;
341         } else if(GetLastError() != ERROR_IO_PENDING) {
342                 logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError()));
343                 device_write_packet.len = 0;
344                 return false;
345         }
346
347         return true;
348 }
349
350 const devops_t os_devops = {
351         .setup = setup_device,
352         .close = close_device,
353         .read = read_packet,
354         .write = write_packet,
355         .enable = enable_device,
356         .disable = disable_device,
357 };