Fix compiling under MinGW.
[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         return true;
218 }
219
220 static void enable_device(void) {
221         logger(DEBUG_ALWAYS, LOG_INFO, "Enabling %s", device_info);
222
223         ULONG status = 1;
224         DWORD len;
225         DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof status, &status, sizeof status, &len, NULL);
226
227         /* We don't use the write event directly, but GetOverlappedResult() does, internally. */
228
229         device_read_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
230         device_write_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
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         CancelIo(device_handle);
241
242         /* According to MSDN, CancelIo() does not necessarily wait for the operation to complete.
243            To prevent race conditions, make sure the operation is complete
244            before we close the event it's referencing. */
245
246         DWORD len;
247         if(!GetOverlappedResult(device_handle, &device_read_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED)
248                 logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s read to cancel: %s", device_info, device, winerror(GetLastError()));
249         if(device_write_packet.len > 0 && !GetOverlappedResult(device_handle, &device_write_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED)
250                 logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s write to cancel: %s", device_info, device, winerror(GetLastError()));
251         device_write_packet.len = 0;
252
253         CloseHandle(device_read_overlapped.hEvent);
254         CloseHandle(device_write_overlapped.hEvent);
255
256         ULONG status = 0;
257         DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof status, &status, sizeof status, &len, NULL);
258 }
259
260 static void close_device(void) {
261         CloseHandle(device_handle); device_handle = INVALID_HANDLE_VALUE;
262
263         free(device); device = NULL;
264         free(iface); iface = NULL;
265         device_info = NULL;
266 }
267
268 static bool read_packet(vpn_packet_t *packet) {
269         return false;
270 }
271
272 static bool write_packet(vpn_packet_t *packet) {
273         DWORD outlen;
274
275         logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s",
276                            packet->len, device_info);
277
278         if(device_write_packet.len > 0) {
279                 /* Make sure the previous write operation is finished before we start the next one;
280                    otherwise we end up with multiple write ops referencing the same OVERLAPPED structure,
281                    which according to MSDN is a no-no. */
282
283                 if(!GetOverlappedResult(device_handle, &device_write_overlapped, &outlen, FALSE)) {
284                         int log_level = (GetLastError() == ERROR_IO_INCOMPLETE) ? DEBUG_TRAFFIC : DEBUG_ALWAYS;
285                         logger(log_level, LOG_ERR, "Error while checking previous write to %s %s: %s", device_info, device, winerror(GetLastError()));
286                         return false;
287                 }
288         }
289
290         /* Copy the packet, since the write operation might still be ongoing after we return. */
291
292         memcpy(&device_write_packet, packet, sizeof *packet);
293
294         if(WriteFile(device_handle, DATA(&device_write_packet), device_write_packet.len, &outlen, &device_write_overlapped))
295                 device_write_packet.len = 0;
296         else if (GetLastError() != ERROR_IO_PENDING) {
297                 logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError()));
298                 return false;
299         }
300
301         return true;
302 }
303
304 const devops_t os_devops = {
305         .setup = setup_device,
306         .close = close_device,
307         .read = read_packet,
308         .write = write_packet,
309         .enable = enable_device,
310         .disable = disable_device,
311 };