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