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