When disabling the Windows device, wait for pending reads to complete.
[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 vpn_packet_t device_read_packet;
42 char *device = NULL;
43 char *iface = NULL;
44 static char *device_info = NULL;
45
46 extern char *myport;
47
48 static void device_issue_read() {
49         device_read_overlapped.Offset = 0;
50         device_read_overlapped.OffsetHigh = 0;
51
52         int status;
53         for (;;) {
54                 DWORD len;
55                 status = ReadFile(device_handle, (void *)device_read_packet.data, MTU, &len, &device_read_overlapped);
56                 if (!status) {
57                         if (GetLastError() != ERROR_IO_PENDING)
58                                 logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info,
59                                            device, strerror(errno));
60                         break;
61                 }
62
63                 device_read_packet.len = len;
64                 device_read_packet.priority = 0;
65                 route(myself, &device_read_packet);
66         }
67 }
68
69 static void device_handle_read(void *data, int flags) {
70         ResetEvent(device_read_overlapped.hEvent);
71
72         DWORD len;
73         if (!GetOverlappedResult(device_handle, &device_read_overlapped, &len, FALSE)) {
74                 logger(DEBUG_ALWAYS, LOG_ERR, "Error getting read result from %s %s: %s", device_info,
75                            device, strerror(errno));
76                 return;
77         }
78
79         device_read_packet.len = len;
80         device_read_packet.priority = 0;
81         route(myself, &device_read_packet);
82         device_issue_read();
83 }
84
85 static bool setup_device(void) {
86         HKEY key, key2;
87         int i;
88
89         char regpath[1024];
90         char adapterid[1024];
91         char adaptername[1024];
92         char tapname[1024];
93         DWORD len;
94
95         bool found = false;
96
97         int err;
98
99         get_config_string(lookup_config(config_tree, "Device"), &device);
100         get_config_string(lookup_config(config_tree, "Interface"), &iface);
101
102         /* Open registry and look for network adapters */
103
104         if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, NETWORK_CONNECTIONS_KEY, 0, KEY_READ, &key)) {
105                 logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read registry: %s", winerror(GetLastError()));
106                 return false;
107         }
108
109         for (i = 0; ; i++) {
110                 len = sizeof adapterid;
111                 if(RegEnumKeyEx(key, i, adapterid, &len, 0, 0, 0, NULL))
112                         break;
113
114                 /* Find out more about this adapter */
115
116                 snprintf(regpath, sizeof regpath, "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, adapterid);
117
118                 if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, regpath, 0, KEY_READ, &key2))
119                         continue;
120
121                 len = sizeof adaptername;
122                 err = RegQueryValueEx(key2, "Name", 0, 0, (LPBYTE)adaptername, &len);
123
124                 RegCloseKey(key2);
125
126                 if(err)
127                         continue;
128
129                 if(device) {
130                         if(!strcmp(device, adapterid)) {
131                                 found = true;
132                                 break;
133                         } else
134                                 continue;
135                 }
136
137                 if(iface) {
138                         if(!strcmp(iface, adaptername)) {
139                                 found = true;
140                                 break;
141                         } else
142                                 continue;
143                 }
144
145                 snprintf(tapname, sizeof tapname, USERMODEDEVICEDIR "%s" TAPSUFFIX, adapterid);
146                 device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
147                 if(device_handle != INVALID_HANDLE_VALUE) {
148                         found = true;
149                         break;
150                 }
151         }
152
153         RegCloseKey(key);
154
155         if(!found) {
156                 logger(DEBUG_ALWAYS, LOG_ERR, "No Windows tap device found!");
157                 return false;
158         }
159
160         if(!device)
161                 device = xstrdup(adapterid);
162
163         if(!iface)
164                 iface = xstrdup(adaptername);
165
166         /* Try to open the corresponding tap device */
167
168         if(device_handle == INVALID_HANDLE_VALUE) {
169                 snprintf(tapname, sizeof tapname, USERMODEDEVICEDIR "%s" TAPSUFFIX, device);
170                 device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
171         }
172
173         if(device_handle == INVALID_HANDLE_VALUE) {
174                 logger(DEBUG_ALWAYS, LOG_ERR, "%s (%s) is not a usable Windows tap device: %s", device, iface, winerror(GetLastError()));
175                 return false;
176         }
177
178         /* Get MAC address from tap device */
179
180         if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_MAC, mymac.x, sizeof mymac.x, mymac.x, sizeof mymac.x, &len, 0)) {
181                 logger(DEBUG_ALWAYS, LOG_ERR, "Could not get MAC address from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError()));
182                 return false;
183         }
184
185         if(routing_mode == RMODE_ROUTER) {
186                 overwrite_mac = 1;
187         }
188
189         device_info = "Windows tap device";
190
191         logger(DEBUG_ALWAYS, LOG_INFO, "%s (%s) is a %s", device, iface, device_info);
192
193         return true;
194 }
195
196 static void enable_device(void) {
197         logger(DEBUG_ALWAYS, LOG_INFO, "Enabling %s", device_info);
198
199         ULONG status = 1;
200         DWORD len;
201         DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof status, &status, sizeof status, &len, NULL);
202
203         io_add_event(&device_read_io, device_handle_read, NULL, CreateEvent(NULL, TRUE, FALSE, NULL));
204         device_read_overlapped.hEvent = device_read_io.event;
205         device_issue_read();
206 }
207
208 static void disable_device(void) {
209         logger(DEBUG_ALWAYS, LOG_INFO, "Disabling %s", device_info);
210
211         io_del(&device_read_io);
212         CancelIo(device_handle);
213
214         /* According to MSDN, CancelIo() does not necessarily wait for the operation to complete.
215            To prevent race conditions, make sure the operation is complete
216            before we close the event it's referencing. */
217
218         DWORD len;
219         if(!GetOverlappedResult(device_handle, &device_read_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED)
220                 logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s read to cancel: %s", device_info, device, winerror(GetLastError()));
221
222         CloseHandle(device_read_overlapped.hEvent);
223
224         ULONG status = 0;
225         DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof status, &status, sizeof status, &len, NULL);
226 }
227
228 static void close_device(void) {
229         CloseHandle(device_handle); device_handle = INVALID_HANDLE_VALUE;
230
231         free(device); device = NULL;
232         free(iface); iface = NULL;
233         device_info = NULL;
234 }
235
236 static bool read_packet(vpn_packet_t *packet) {
237         return false;
238 }
239
240 static bool write_packet(vpn_packet_t *packet) {
241         DWORD outlen;
242         OVERLAPPED overlapped = {0};
243
244         logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s",
245                            packet->len, device_info);
246
247         if(!WriteFile(device_handle, DATA(packet), packet->len, &outlen, &overlapped)) {
248                 logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError()));
249                 return false;
250         }
251
252         return true;
253 }
254
255 const devops_t os_devops = {
256         .setup = setup_device,
257         .close = close_device,
258         .read = read_packet,
259         .write = write_packet,
260         .enable = enable_device,
261         .disable = disable_device,
262 };