Disconnect hostapd from building in base
[dragonfly.git] / contrib / hostapd / src / l2_packet / l2_packet_ndis.c
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
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
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
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
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 */
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
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         l2->rx_callback(l2->rx_callback_ctx, rx_src, rx_buf, rx_len);
298 #ifndef _WIN32_WCE
299         l2_ndisuio_start_read(l2, 1);
300 #endif /* _WIN32_WCE */
301 }
302
303
304 static void l2_packet_rx_event(void *eloop_data, void *user_data)
305 {
306         struct l2_packet_data *l2 = eloop_data;
307
308         if (l2_ndisuio_global)
309                 l2 = l2_ndisuio_global->l2[l2_ndisuio_global->refcount - 1];
310
311         ResetEvent(l2->rx_avail);
312
313 #ifndef _WIN32_WCE
314         if (!GetOverlappedResult(driver_ndis_get_ndisuio_handle(),
315                                  &l2->rx_overlapped, &l2->rx_written, FALSE)) {
316                 wpa_printf(MSG_DEBUG, "L2(NDISUIO): GetOverlappedResult "
317                            "failed: %d", (int) GetLastError());
318                 return;
319         }
320 #endif /* _WIN32_WCE */
321
322         l2_packet_callback(l2);
323
324 #ifdef _WIN32_WCE
325         SetEvent(l2_ndisuio_global->rx_processed);
326 #endif /* _WIN32_WCE */
327 }
328
329
330 static int l2_ndisuio_set_ether_type(unsigned short protocol)
331 {
332         USHORT proto = htons(protocol);
333         DWORD written;
334
335         if (!DeviceIoControl(driver_ndis_get_ndisuio_handle(),
336                              IOCTL_NDISUIO_SET_ETHER_TYPE, &proto,
337                              sizeof(proto), NULL, 0, &written, NULL)) {
338                 wpa_printf(MSG_ERROR, "L2(NDISUIO): "
339                            "IOCTL_NDISUIO_SET_ETHER_TYPE failed: %d",
340                            (int) GetLastError());
341                 return -1;
342         }
343
344         return 0;
345 }
346
347
348 struct l2_packet_data * l2_packet_init(
349         const char *ifname, const u8 *own_addr, unsigned short protocol,
350         void (*rx_callback)(void *ctx, const u8 *src_addr,
351                             const u8 *buf, size_t len),
352         void *rx_callback_ctx, int l2_hdr)
353 {
354         struct l2_packet_data *l2;
355
356         if (l2_ndisuio_global == NULL) {
357                 l2_ndisuio_global = os_zalloc(sizeof(*l2_ndisuio_global));
358                 if (l2_ndisuio_global == NULL)
359                         return NULL;
360                 l2_ndisuio_global->first_proto = protocol;
361         }
362         if (l2_ndisuio_global->refcount >= 2) {
363                 wpa_printf(MSG_ERROR, "L2(NDISUIO): Not more than two "
364                            "simultaneous connections allowed");
365                 return NULL;
366         }
367         l2_ndisuio_global->refcount++;
368
369         l2 = os_zalloc(sizeof(struct l2_packet_data));
370         if (l2 == NULL)
371                 return NULL;
372         l2_ndisuio_global->l2[l2_ndisuio_global->refcount - 1] = l2;
373
374         os_strlcpy(l2->ifname, ifname, sizeof(l2->ifname));
375         l2->rx_callback = rx_callback;
376         l2->rx_callback_ctx = rx_callback_ctx;
377         l2->l2_hdr = l2_hdr;
378
379         if (own_addr)
380                 os_memcpy(l2->own_addr, own_addr, ETH_ALEN);
381
382         if (l2_ndisuio_set_ether_type(protocol) < 0) {
383                 os_free(l2);
384                 return NULL;
385         }
386
387         if (l2_ndisuio_global->refcount > 1) {
388                 wpa_printf(MSG_DEBUG, "L2(NDISUIO): Temporarily setting "
389                            "filtering ethertype to %04x", protocol);
390                 if (l2_ndisuio_global->l2[0])
391                         l2->rx_avail = l2_ndisuio_global->l2[0]->rx_avail;
392                 return l2;
393         }
394
395         l2->rx_avail = CreateEvent(NULL, TRUE, FALSE, NULL);
396         if (l2->rx_avail == NULL) {
397                 os_free(l2);
398                 return NULL;
399         }
400
401         eloop_register_event(l2->rx_avail, sizeof(l2->rx_avail),
402                              l2_packet_rx_event, l2, NULL);
403
404 #ifdef _WIN32_WCE
405         l2_ndisuio_global->stop_request = CreateEvent(NULL, TRUE, FALSE, NULL);
406         /*
407          * This event is being set based on media connect/disconnect
408          * notifications in driver_ndis.c.
409          */
410         l2_ndisuio_global->ready_for_read =
411                 CreateEvent(NULL, TRUE, FALSE, TEXT("WpaSupplicantConnected"));
412         l2_ndisuio_global->rx_processed = CreateEvent(NULL, TRUE, FALSE, NULL);
413         if (l2_ndisuio_global->stop_request == NULL ||
414             l2_ndisuio_global->ready_for_read == NULL ||
415             l2_ndisuio_global->rx_processed == NULL) {
416                 if (l2_ndisuio_global->stop_request) {
417                         CloseHandle(l2_ndisuio_global->stop_request);
418                         l2_ndisuio_global->stop_request = NULL;
419                 }
420                 if (l2_ndisuio_global->ready_for_read) {
421                         CloseHandle(l2_ndisuio_global->ready_for_read);
422                         l2_ndisuio_global->ready_for_read = NULL;
423                 }
424                 if (l2_ndisuio_global->rx_processed) {
425                         CloseHandle(l2_ndisuio_global->rx_processed);
426                         l2_ndisuio_global->rx_processed = NULL;
427                 }
428                 eloop_unregister_event(l2->rx_avail, sizeof(l2->rx_avail));
429                 os_free(l2);
430                 return NULL;
431         }
432
433         l2_ndisuio_global->rx_thread = CreateThread(NULL, 0,
434                                                     l2_packet_rx_thread, l2, 0,
435                                                     NULL);
436         if (l2_ndisuio_global->rx_thread == NULL) {
437                 wpa_printf(MSG_INFO, "L2(NDISUIO): Failed to create RX "
438                            "thread: %d", (int) GetLastError());
439                 eloop_unregister_event(l2->rx_avail, sizeof(l2->rx_avail));
440                 CloseHandle(l2_ndisuio_global->stop_request);
441                 l2_ndisuio_global->stop_request = NULL;
442                 os_free(l2);
443                 return NULL;
444         }
445 #else /* _WIN32_WCE */
446         l2_ndisuio_start_read(l2, 0);
447 #endif /* _WIN32_WCE */
448
449         return l2;
450 }
451
452
453 void l2_packet_deinit(struct l2_packet_data *l2)
454 {
455         if (l2 == NULL)
456                 return;
457
458         if (l2_ndisuio_global) {
459                 l2_ndisuio_global->refcount--;
460                 l2_ndisuio_global->l2[l2_ndisuio_global->refcount] = NULL;
461                 if (l2_ndisuio_global->refcount) {
462                         wpa_printf(MSG_DEBUG, "L2(NDISUIO): restore filtering "
463                                    "ethertype to %04x",
464                                    l2_ndisuio_global->first_proto);
465                         l2_ndisuio_set_ether_type(
466                                 l2_ndisuio_global->first_proto);
467                         return;
468                 }
469
470 #ifdef _WIN32_WCE
471                 wpa_printf(MSG_DEBUG, "L2(NDISUIO): Waiting for RX thread to "
472                            "stop");
473                 SetEvent(l2_ndisuio_global->stop_request);
474                 /*
475                  * Cancel pending ReadFile() in the RX thread (if we were still
476                  * connected at this point).
477                  */
478                 if (!DeviceIoControl(driver_ndis_get_ndisuio_handle(),
479                                      IOCTL_CANCEL_READ, NULL, 0, NULL, 0, NULL,
480                                      NULL)) {
481                         wpa_printf(MSG_DEBUG, "L2(NDISUIO): IOCTL_CANCEL_READ "
482                                    "failed: %d", (int) GetLastError());
483                         /* RX thread will exit blocking ReadFile once NDISUIO
484                          * notices that the adapter is disconnected. */
485                 }
486                 WaitForSingleObject(l2_ndisuio_global->rx_thread, INFINITE);
487                 wpa_printf(MSG_DEBUG, "L2(NDISUIO): RX thread exited");
488                 CloseHandle(l2_ndisuio_global->rx_thread);
489                 CloseHandle(l2_ndisuio_global->stop_request);
490                 CloseHandle(l2_ndisuio_global->ready_for_read);
491                 CloseHandle(l2_ndisuio_global->rx_processed);
492 #endif /* _WIN32_WCE */
493
494                 os_free(l2_ndisuio_global);
495                 l2_ndisuio_global = NULL;
496         }
497
498 #ifndef _WIN32_WCE
499         CancelIo(driver_ndis_get_ndisuio_handle());
500 #endif /* _WIN32_WCE */
501
502         eloop_unregister_event(l2->rx_avail, sizeof(l2->rx_avail));
503         CloseHandle(l2->rx_avail);
504         os_free(l2);
505 }
506
507
508 int l2_packet_get_ip_addr(struct l2_packet_data *l2, char *buf, size_t len)
509 {
510         return -1;
511 }
512
513
514 void l2_packet_notify_auth_start(struct l2_packet_data *l2)
515 {
516 }