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