Fix Windows device asynchronous write behavior.
[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 MAC address from tap device */
181
182         if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_MAC, mymac.x, sizeof mymac.x, mymac.x, sizeof mymac.x, &len, 0)) {
183                 logger(DEBUG_ALWAYS, LOG_ERR, "Could not get MAC address from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError()));
184                 return false;
185         }
186
187         if(routing_mode == RMODE_ROUTER) {
188                 overwrite_mac = 1;
189         }
190
191         device_info = "Windows tap device";
192
193         logger(DEBUG_ALWAYS, LOG_INFO, "%s (%s) is a %s", device, iface, device_info);
194
195         return true;
196 }
197
198 static void enable_device(void) {
199         logger(DEBUG_ALWAYS, LOG_INFO, "Enabling %s", device_info);
200
201         ULONG status = 1;
202         DWORD len;
203         DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof status, &status, sizeof status, &len, NULL);
204
205         /* We don't use the write event directly, but GetOverlappedResult() does, internally. */
206
207         device_read_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
208         device_write_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
209
210         io_add_event(&device_read_io, device_handle_read, NULL, device_read_overlapped.hEvent);
211         device_issue_read();
212 }
213
214 static void disable_device(void) {
215         logger(DEBUG_ALWAYS, LOG_INFO, "Disabling %s", device_info);
216
217         io_del(&device_read_io);
218         CancelIo(device_handle);
219
220         /* According to MSDN, CancelIo() does not necessarily wait for the operation to complete.
221            To prevent race conditions, make sure the operation is complete
222            before we close the event it's referencing. */
223
224         DWORD len;
225         if(!GetOverlappedResult(device_handle, &device_read_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED)
226                 logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s read to cancel: %s", device_info, device, winerror(GetLastError()));
227         if(device_write_packet.len > 0 && !GetOverlappedResult(device_handle, &device_write_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED)
228                 logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s write to cancel: %s", device_info, device, winerror(GetLastError()));
229         device_write_packet.len = 0;
230
231         CloseHandle(device_read_overlapped.hEvent);
232         CloseHandle(device_write_overlapped.hEvent);
233
234         ULONG status = 0;
235         DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof status, &status, sizeof status, &len, NULL);
236 }
237
238 static void close_device(void) {
239         CloseHandle(device_handle); device_handle = INVALID_HANDLE_VALUE;
240
241         free(device); device = NULL;
242         free(iface); iface = NULL;
243         device_info = NULL;
244 }
245
246 static bool read_packet(vpn_packet_t *packet) {
247         return false;
248 }
249
250 static bool write_packet(vpn_packet_t *packet) {
251         DWORD outlen;
252
253         logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s",
254                            packet->len, device_info);
255
256         if(device_write_packet.len > 0) {
257                 /* Make sure the previous write operation is finished before we start the next one;
258                    otherwise we end up with multiple write ops referencing the same OVERLAPPED structure,
259                    which according to MSDN is a no-no. */
260
261                 if(!GetOverlappedResult(device_handle, &device_write_overlapped, &outlen, FALSE)) {
262                         int log_level = (GetLastError() == ERROR_IO_INCOMPLETE) ? DEBUG_TRAFFIC : DEBUG_ALWAYS;
263                         logger(log_level, LOG_ERR, "Error while checking previous write to %s %s: %s", device_info, device, winerror(GetLastError()));
264                         return false;
265                 }
266         }
267
268         /* Copy the packet, since the write operation might still be ongoing after we return. */
269
270         memcpy(&device_write_packet, packet, sizeof *packet);
271
272         if(WriteFile(device_handle, DATA(&device_write_packet), device_write_packet.len, &outlen, &device_write_overlapped))
273                 device_write_packet.len = 0;
274         else if (GetLastError() != ERROR_IO_PENDING) {
275                 logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError()));
276                 return false;
277         }
278
279         return true;
280 }
281
282 const devops_t os_devops = {
283         .setup = setup_device,
284         .close = close_device,
285         .read = read_packet,
286         .write = write_packet,
287         .enable = enable_device,
288         .disable = disable_device,
289 };