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