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