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