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