1 /*
2 * WPA Supplicant - Layer2 packet handling with Microsoft NDISUIO
3 * Copyright (c) 2003-2006, Jouni Malinen <j@w1.fi>
4 *
5 * This software may be distributed under the terms of the BSD license.
6 * See README for more details.
7 *
8 * This implementation requires Windows specific event loop implementation,
9 * i.e., eloop_win.c. In addition, the NDISUIO connection is shared with
10 * driver_ndis.c, so only that driver interface can be used and
11 * CONFIG_USE_NDISUIO must be defined.
12 *
13 * WinXP version of the code uses overlapped I/O and a single threaded design
14 * with callback functions from I/O code. WinCE version uses a separate RX
15 * thread that blocks on ReadFile() whenever the media status is connected.
16 */
17
18 #include "includes.h"
19 #include <winsock2.h>
20 #include <ntddndis.h>
21
22 #ifdef _WIN32_WCE
23 #include <winioctl.h>
24 #include <nuiouser.h>
25 #endif /* _WIN32_WCE */
26
27 #include "common.h"
28 #include "eloop.h"
29 #include "l2_packet.h"
30
31 #ifndef _WIN32_WCE
32 /* from nuiouser.h */
33 #define FSCTL_NDISUIO_BASE FILE_DEVICE_NETWORK
34 #define _NDISUIO_CTL_CODE(_Function, _Method, _Access) \
35 CTL_CODE(FSCTL_NDISUIO_BASE, _Function, _Method, _Access)
36 #define IOCTL_NDISUIO_SET_ETHER_TYPE \
37 _NDISUIO_CTL_CODE(0x202, METHOD_BUFFERED, \
38 FILE_READ_ACCESS | FILE_WRITE_ACCESS)
39 #endif /* _WIN32_WCE */
40
41 /* From driver_ndis.c to shared the handle to NDISUIO */
42 HANDLE driver_ndis_get_ndisuio_handle(void);
43
44 /*
45 * NDISUIO supports filtering of only one ethertype at the time, so we must
46 * fake support for two (EAPOL and RSN pre-auth) by switching to pre-auth
47 * whenever wpa_supplicant is trying to pre-authenticate and then switching
48 * back to EAPOL when pre-authentication has been completed.
49 */
50
51 struct l2_packet_data;
52
53 struct l2_packet_ndisuio_global {
54 int refcount;
55 unsigned short first_proto;
56 struct l2_packet_data *l2[2];
57 #ifdef _WIN32_WCE
58 HANDLE rx_thread;
59 HANDLE stop_request;
60 HANDLE ready_for_read;
61 HANDLE rx_processed;
62 #endif /* _WIN32_WCE */
63 };
64
65 static struct l2_packet_ndisuio_global *l2_ndisuio_global = NULL;
66
67 struct l2_packet_data {
68 char ifname[100];
69 u8 own_addr[ETH_ALEN];
70 void (*rx_callback)(void *ctx, const u8 *src_addr,
71 const u8 *buf, size_t len);
72 void *rx_callback_ctx;
73 int l2_hdr; /* whether to include layer 2 (Ethernet) header in calls to
74 * rx_callback and l2_packet_send() */
75 HANDLE rx_avail;
76 #ifndef _WIN32_WCE
77 OVERLAPPED rx_overlapped;
78 #endif /* _WIN32_WCE */
79 u8 rx_buf[1514];
80 DWORD rx_written;
81 };
82
83
l2_packet_get_own_addr(struct l2_packet_data * l2,u8 * addr)84 int l2_packet_get_own_addr(struct l2_packet_data *l2, u8 *addr)
85 {
86 os_memcpy(addr, l2->own_addr, ETH_ALEN);
87 return 0;
88 }
89
90
l2_packet_send(struct l2_packet_data * l2,const u8 * dst_addr,u16 proto,const u8 * buf,size_t len)91 int l2_packet_send(struct l2_packet_data *l2, const u8 *dst_addr, u16 proto,
92 const u8 *buf, size_t len)
93 {
94 BOOL res;
95 DWORD written;
96 struct l2_ethhdr *eth;
97 #ifndef _WIN32_WCE
98 OVERLAPPED overlapped;
99 #endif /* _WIN32_WCE */
100 OVERLAPPED *o;
101
102 if (l2 == NULL)
103 return -1;
104
105 #ifdef _WIN32_WCE
106 o = NULL;
107 #else /* _WIN32_WCE */
108 os_memset(&overlapped, 0, sizeof(overlapped));
109 o = &overlapped;
110 #endif /* _WIN32_WCE */
111
112 if (l2->l2_hdr) {
113 res = WriteFile(driver_ndis_get_ndisuio_handle(), buf, len,
114 &written, o);
115 } else {
116 size_t mlen = sizeof(*eth) + len;
117 eth = os_malloc(mlen);
118 if (eth == NULL)
119 return -1;
120
121 os_memcpy(eth->h_dest, dst_addr, ETH_ALEN);
122 os_memcpy(eth->h_source, l2->own_addr, ETH_ALEN);
123 eth->h_proto = htons(proto);
124 os_memcpy(eth + 1, buf, len);
125 res = WriteFile(driver_ndis_get_ndisuio_handle(), eth, mlen,
126 &written, o);
127 os_free(eth);
128 }
129
130 if (!res) {
131 DWORD err = GetLastError();
132 #ifndef _WIN32_WCE
133 if (err == ERROR_IO_PENDING) {
134 wpa_printf(MSG_DEBUG, "L2(NDISUIO): Wait for pending "
135 "write to complete");
136 res = GetOverlappedResult(
137 driver_ndis_get_ndisuio_handle(), &overlapped,
138 &written, TRUE);
139 if (!res) {
140 wpa_printf(MSG_DEBUG, "L2(NDISUIO): "
141 "GetOverlappedResult failed: %d",
142 (int) GetLastError());
143 return -1;
144 }
145 return 0;
146 }
147 #endif /* _WIN32_WCE */
148 wpa_printf(MSG_DEBUG, "L2(NDISUIO): WriteFile failed: %d",
149 (int) GetLastError());
150 return -1;
151 }
152
153 return 0;
154 }
155
156
157 static void l2_packet_callback(struct l2_packet_data *l2);
158
159 #ifdef _WIN32_WCE
l2_packet_rx_thread_try_read(struct l2_packet_data * l2)160 static void l2_packet_rx_thread_try_read(struct l2_packet_data *l2)
161 {
162 HANDLE handles[2];
163
164 wpa_printf(MSG_MSGDUMP, "l2_packet_rx_thread: -> ReadFile");
165 if (!ReadFile(driver_ndis_get_ndisuio_handle(), l2->rx_buf,
166 sizeof(l2->rx_buf), &l2->rx_written, NULL)) {
167 DWORD err = GetLastError();
168 wpa_printf(MSG_DEBUG, "l2_packet_rx_thread: ReadFile failed: "
169 "%d", (int) err);
170 /*
171 * ReadFile on NDISUIO/WinCE returns ERROR_DEVICE_NOT_CONNECTED
172 * error whenever the connection is not up. Yield the thread to
173 * avoid triggering a busy loop. Connection event should stop
174 * us from looping for long, but we need to allow enough CPU
175 * for the main thread to process the media disconnection.
176 */
177 Sleep(100);
178 return;
179 }
180
181 wpa_printf(MSG_DEBUG, "l2_packet_rx_thread: Read %d byte packet",
182 (int) l2->rx_written);
183
184 /*
185 * Notify the main thread about the availability of a frame and wait
186 * for the frame to be processed.
187 */
188 SetEvent(l2->rx_avail);
189 handles[0] = l2_ndisuio_global->stop_request;
190 handles[1] = l2_ndisuio_global->rx_processed;
191 WaitForMultipleObjects(2, handles, FALSE, INFINITE);
192 ResetEvent(l2_ndisuio_global->rx_processed);
193 }
194
195
l2_packet_rx_thread(LPVOID arg)196 static DWORD WINAPI l2_packet_rx_thread(LPVOID arg)
197 {
198 struct l2_packet_data *l2 = arg;
199 DWORD res;
200 HANDLE handles[2];
201 int run = 1;
202
203 wpa_printf(MSG_DEBUG, "L2(NDISUIO): RX thread started");
204 handles[0] = l2_ndisuio_global->stop_request;
205 handles[1] = l2_ndisuio_global->ready_for_read;
206
207 /*
208 * Unfortunately, NDISUIO on WinCE does not seem to support waiting
209 * on the handle. There do not seem to be anything else that we could
210 * wait for either. If one were to modify NDISUIO to set a named event
211 * whenever packets are available, this event could be used here to
212 * avoid having to poll for new packets or we could even move to use a
213 * single threaded design.
214 *
215 * In addition, NDISUIO on WinCE is returning
216 * ERROR_DEVICE_NOT_CONNECTED whenever ReadFile() is attempted while
217 * the adapter is not in connected state. For now, we are just using a
218 * local event to allow ReadFile calls only after having received NDIS
219 * media connect event. This event could be easily converted to handle
220 * another event if the protocol driver is replaced with somewhat more
221 * useful design.
222 */
223
224 while (l2_ndisuio_global && run) {
225 res = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
226 switch (res) {
227 case WAIT_OBJECT_0:
228 wpa_printf(MSG_DEBUG, "l2_packet_rx_thread: Received "
229 "request to stop RX thread");
230 run = 0;
231 break;
232 case WAIT_OBJECT_0 + 1:
233 l2_packet_rx_thread_try_read(l2);
234 break;
235 case WAIT_FAILED:
236 default:
237 wpa_printf(MSG_DEBUG, "l2_packet_rx_thread: "
238 "WaitForMultipleObjects failed: %d",
239 (int) GetLastError());
240 run = 0;
241 break;
242 }
243 }
244
245 wpa_printf(MSG_DEBUG, "L2(NDISUIO): RX thread stopped");
246
247 return 0;
248 }
249 #else /* _WIN32_WCE */
l2_ndisuio_start_read(struct l2_packet_data * l2,int recursive)250 static int l2_ndisuio_start_read(struct l2_packet_data *l2, int recursive)
251 {
252 os_memset(&l2->rx_overlapped, 0, sizeof(l2->rx_overlapped));
253 l2->rx_overlapped.hEvent = l2->rx_avail;
254 if (!ReadFile(driver_ndis_get_ndisuio_handle(), l2->rx_buf,
255 sizeof(l2->rx_buf), &l2->rx_written, &l2->rx_overlapped))
256 {
257 DWORD err = GetLastError();
258 if (err != ERROR_IO_PENDING) {
259 wpa_printf(MSG_DEBUG, "L2(NDISUIO): ReadFile failed: "
260 "%d", (int) err);
261 return -1;
262 }
263 /*
264 * Once read is completed, l2_packet_rx_event() will be
265 * called.
266 */
267 } else {
268 wpa_printf(MSG_DEBUG, "L2(NDISUIO): ReadFile returned data "
269 "without wait for completion");
270 if (!recursive)
271 l2_packet_callback(l2);
272 }
273
274 return 0;
275 }
276 #endif /* _WIN32_WCE */
277
278
l2_packet_callback(struct l2_packet_data * l2)279 static void l2_packet_callback(struct l2_packet_data *l2)
280 {
281 const u8 *rx_buf, *rx_src;
282 size_t rx_len;
283 struct l2_ethhdr *ethhdr = (struct l2_ethhdr *) l2->rx_buf;
284
285 wpa_printf(MSG_DEBUG, "L2(NDISUIO): Read %d bytes",
286 (int) l2->rx_written);
287
288 if (l2->l2_hdr || l2->rx_written < sizeof(*ethhdr)) {
289 rx_buf = (u8 *) ethhdr;
290 rx_len = l2->rx_written;
291 } else {
292 rx_buf = (u8 *) (ethhdr + 1);
293 rx_len = l2->rx_written - sizeof(*ethhdr);
294 }
295 rx_src = ethhdr->h_source;
296
297 if (l2->rx_callback)
298 l2->rx_callback(l2->rx_callback_ctx, rx_src, rx_buf, rx_len);
299 #ifndef _WIN32_WCE
300 l2_ndisuio_start_read(l2, 1);
301 #endif /* _WIN32_WCE */
302 }
303
304
l2_packet_rx_event(void * eloop_data,void * user_data)305 static void l2_packet_rx_event(void *eloop_data, void *user_data)
306 {
307 struct l2_packet_data *l2 = eloop_data;
308
309 if (l2_ndisuio_global)
310 l2 = l2_ndisuio_global->l2[l2_ndisuio_global->refcount - 1];
311
312 ResetEvent(l2->rx_avail);
313
314 #ifndef _WIN32_WCE
315 if (!GetOverlappedResult(driver_ndis_get_ndisuio_handle(),
316 &l2->rx_overlapped, &l2->rx_written, FALSE)) {
317 wpa_printf(MSG_DEBUG, "L2(NDISUIO): GetOverlappedResult "
318 "failed: %d", (int) GetLastError());
319 return;
320 }
321 #endif /* _WIN32_WCE */
322
323 l2_packet_callback(l2);
324
325 #ifdef _WIN32_WCE
326 SetEvent(l2_ndisuio_global->rx_processed);
327 #endif /* _WIN32_WCE */
328 }
329
330
l2_ndisuio_set_ether_type(unsigned short protocol)331 static int l2_ndisuio_set_ether_type(unsigned short protocol)
332 {
333 USHORT proto = htons(protocol);
334 DWORD written;
335
336 if (!DeviceIoControl(driver_ndis_get_ndisuio_handle(),
337 IOCTL_NDISUIO_SET_ETHER_TYPE, &proto,
338 sizeof(proto), NULL, 0, &written, NULL)) {
339 wpa_printf(MSG_ERROR, "L2(NDISUIO): "
340 "IOCTL_NDISUIO_SET_ETHER_TYPE failed: %d",
341 (int) GetLastError());
342 return -1;
343 }
344
345 return 0;
346 }
347
348
l2_packet_init(const char * ifname,const u8 * own_addr,unsigned short protocol,void (* rx_callback)(void * ctx,const u8 * src_addr,const u8 * buf,size_t len),void * rx_callback_ctx,int l2_hdr)349 struct l2_packet_data * l2_packet_init(
350 const char *ifname, const u8 *own_addr, unsigned short protocol,
351 void (*rx_callback)(void *ctx, const u8 *src_addr,
352 const u8 *buf, size_t len),
353 void *rx_callback_ctx, int l2_hdr)
354 {
355 struct l2_packet_data *l2;
356
357 if (l2_ndisuio_global == NULL) {
358 l2_ndisuio_global = os_zalloc(sizeof(*l2_ndisuio_global));
359 if (l2_ndisuio_global == NULL)
360 return NULL;
361 l2_ndisuio_global->first_proto = protocol;
362 }
363 if (l2_ndisuio_global->refcount >= 2) {
364 wpa_printf(MSG_ERROR, "L2(NDISUIO): Not more than two "
365 "simultaneous connections allowed");
366 return NULL;
367 }
368 l2_ndisuio_global->refcount++;
369
370 l2 = os_zalloc(sizeof(struct l2_packet_data));
371 if (l2 == NULL)
372 return NULL;
373 l2_ndisuio_global->l2[l2_ndisuio_global->refcount - 1] = l2;
374
375 os_strlcpy(l2->ifname, ifname, sizeof(l2->ifname));
376 l2->rx_callback = rx_callback;
377 l2->rx_callback_ctx = rx_callback_ctx;
378 l2->l2_hdr = l2_hdr;
379
380 if (own_addr)
381 os_memcpy(l2->own_addr, own_addr, ETH_ALEN);
382
383 if (l2_ndisuio_set_ether_type(protocol) < 0) {
384 os_free(l2);
385 return NULL;
386 }
387
388 if (l2_ndisuio_global->refcount > 1) {
389 wpa_printf(MSG_DEBUG, "L2(NDISUIO): Temporarily setting "
390 "filtering ethertype to %04x", protocol);
391 if (l2_ndisuio_global->l2[0])
392 l2->rx_avail = l2_ndisuio_global->l2[0]->rx_avail;
393 return l2;
394 }
395
396 l2->rx_avail = CreateEvent(NULL, TRUE, FALSE, NULL);
397 if (l2->rx_avail == NULL) {
398 os_free(l2);
399 return NULL;
400 }
401
402 eloop_register_event(l2->rx_avail, sizeof(l2->rx_avail),
403 l2_packet_rx_event, l2, NULL);
404
405 #ifdef _WIN32_WCE
406 l2_ndisuio_global->stop_request = CreateEvent(NULL, TRUE, FALSE, NULL);
407 /*
408 * This event is being set based on media connect/disconnect
409 * notifications in driver_ndis.c.
410 */
411 l2_ndisuio_global->ready_for_read =
412 CreateEvent(NULL, TRUE, FALSE, TEXT("WpaSupplicantConnected"));
413 l2_ndisuio_global->rx_processed = CreateEvent(NULL, TRUE, FALSE, NULL);
414 if (l2_ndisuio_global->stop_request == NULL ||
415 l2_ndisuio_global->ready_for_read == NULL ||
416 l2_ndisuio_global->rx_processed == NULL) {
417 if (l2_ndisuio_global->stop_request) {
418 CloseHandle(l2_ndisuio_global->stop_request);
419 l2_ndisuio_global->stop_request = NULL;
420 }
421 if (l2_ndisuio_global->ready_for_read) {
422 CloseHandle(l2_ndisuio_global->ready_for_read);
423 l2_ndisuio_global->ready_for_read = NULL;
424 }
425 if (l2_ndisuio_global->rx_processed) {
426 CloseHandle(l2_ndisuio_global->rx_processed);
427 l2_ndisuio_global->rx_processed = NULL;
428 }
429 eloop_unregister_event(l2->rx_avail, sizeof(l2->rx_avail));
430 os_free(l2);
431 return NULL;
432 }
433
434 l2_ndisuio_global->rx_thread = CreateThread(NULL, 0,
435 l2_packet_rx_thread, l2, 0,
436 NULL);
437 if (l2_ndisuio_global->rx_thread == NULL) {
438 wpa_printf(MSG_INFO, "L2(NDISUIO): Failed to create RX "
439 "thread: %d", (int) GetLastError());
440 eloop_unregister_event(l2->rx_avail, sizeof(l2->rx_avail));
441 CloseHandle(l2_ndisuio_global->stop_request);
442 l2_ndisuio_global->stop_request = NULL;
443 os_free(l2);
444 return NULL;
445 }
446 #else /* _WIN32_WCE */
447 l2_ndisuio_start_read(l2, 0);
448 #endif /* _WIN32_WCE */
449
450 return l2;
451 }
452
453
l2_packet_init_bridge(const char * br_ifname,const char * ifname,const u8 * own_addr,unsigned short protocol,void (* rx_callback)(void * ctx,const u8 * src_addr,const u8 * buf,size_t len),void * rx_callback_ctx,int l2_hdr)454 struct l2_packet_data * l2_packet_init_bridge(
455 const char *br_ifname, const char *ifname, const u8 *own_addr,
456 unsigned short protocol,
457 void (*rx_callback)(void *ctx, const u8 *src_addr,
458 const u8 *buf, size_t len),
459 void *rx_callback_ctx, int l2_hdr)
460 {
461 return l2_packet_init(br_ifname, own_addr, protocol, rx_callback,
462 rx_callback_ctx, l2_hdr);
463 }
464
465
l2_packet_deinit(struct l2_packet_data * l2)466 void l2_packet_deinit(struct l2_packet_data *l2)
467 {
468 if (l2 == NULL)
469 return;
470
471 if (l2_ndisuio_global) {
472 l2_ndisuio_global->refcount--;
473 l2_ndisuio_global->l2[l2_ndisuio_global->refcount] = NULL;
474 if (l2_ndisuio_global->refcount) {
475 wpa_printf(MSG_DEBUG, "L2(NDISUIO): restore filtering "
476 "ethertype to %04x",
477 l2_ndisuio_global->first_proto);
478 l2_ndisuio_set_ether_type(
479 l2_ndisuio_global->first_proto);
480 return;
481 }
482
483 #ifdef _WIN32_WCE
484 wpa_printf(MSG_DEBUG, "L2(NDISUIO): Waiting for RX thread to "
485 "stop");
486 SetEvent(l2_ndisuio_global->stop_request);
487 /*
488 * Cancel pending ReadFile() in the RX thread (if we were still
489 * connected at this point).
490 */
491 if (!DeviceIoControl(driver_ndis_get_ndisuio_handle(),
492 IOCTL_CANCEL_READ, NULL, 0, NULL, 0, NULL,
493 NULL)) {
494 wpa_printf(MSG_DEBUG, "L2(NDISUIO): IOCTL_CANCEL_READ "
495 "failed: %d", (int) GetLastError());
496 /* RX thread will exit blocking ReadFile once NDISUIO
497 * notices that the adapter is disconnected. */
498 }
499 WaitForSingleObject(l2_ndisuio_global->rx_thread, INFINITE);
500 wpa_printf(MSG_DEBUG, "L2(NDISUIO): RX thread exited");
501 CloseHandle(l2_ndisuio_global->rx_thread);
502 CloseHandle(l2_ndisuio_global->stop_request);
503 CloseHandle(l2_ndisuio_global->ready_for_read);
504 CloseHandle(l2_ndisuio_global->rx_processed);
505 #endif /* _WIN32_WCE */
506
507 os_free(l2_ndisuio_global);
508 l2_ndisuio_global = NULL;
509 }
510
511 #ifndef _WIN32_WCE
512 CancelIo(driver_ndis_get_ndisuio_handle());
513 #endif /* _WIN32_WCE */
514
515 eloop_unregister_event(l2->rx_avail, sizeof(l2->rx_avail));
516 CloseHandle(l2->rx_avail);
517 os_free(l2);
518 }
519
520
l2_packet_get_ip_addr(struct l2_packet_data * l2,char * buf,size_t len)521 int l2_packet_get_ip_addr(struct l2_packet_data *l2, char *buf, size_t len)
522 {
523 return -1;
524 }
525
526
l2_packet_notify_auth_start(struct l2_packet_data * l2)527 void l2_packet_notify_auth_start(struct l2_packet_data *l2)
528 {
529 }
530
531
l2_packet_set_packet_filter(struct l2_packet_data * l2,enum l2_packet_filter_type type)532 int l2_packet_set_packet_filter(struct l2_packet_data *l2,
533 enum l2_packet_filter_type type)
534 {
535 return -1;
536 }
537