2 * WPA Supplicant - Layer2 packet handling with Microsoft NDISUIO
3 * Copyright (c) 2003-2006, Jouni Malinen <j@w1.fi>
5 * This software may be distributed under the terms of the BSD license.
6 * See README for more details.
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.
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.
25 #endif /* _WIN32_WCE */
29 #include "l2_packet.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 */
41 /* From driver_ndis.c to shared the handle to NDISUIO */
42 HANDLE
driver_ndis_get_ndisuio_handle(void);
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.
51 struct l2_packet_data
;
53 struct l2_packet_ndisuio_global
{
55 unsigned short first_proto
;
56 struct l2_packet_data
*l2
[2];
60 HANDLE ready_for_read
;
62 #endif /* _WIN32_WCE */
65 static struct l2_packet_ndisuio_global
*l2_ndisuio_global
= NULL
;
67 struct l2_packet_data
{
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() */
77 OVERLAPPED rx_overlapped
;
78 #endif /* _WIN32_WCE */
84 int l2_packet_get_own_addr(struct l2_packet_data
*l2
, u8
*addr
)
86 os_memcpy(addr
, l2
->own_addr
, ETH_ALEN
);
91 int l2_packet_send(struct l2_packet_data
*l2
, const u8
*dst_addr
, u16 proto
,
92 const u8
*buf
, size_t len
)
96 struct l2_ethhdr
*eth
;
98 OVERLAPPED overlapped
;
99 #endif /* _WIN32_WCE */
107 #else /* _WIN32_WCE */
108 os_memset(&overlapped
, 0, sizeof(overlapped
));
110 #endif /* _WIN32_WCE */
113 res
= WriteFile(driver_ndis_get_ndisuio_handle(), buf
, len
,
116 size_t mlen
= sizeof(*eth
) + len
;
117 eth
= os_malloc(mlen
);
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
,
131 DWORD err
= GetLastError();
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
,
140 wpa_printf(MSG_DEBUG
, "L2(NDISUIO): "
141 "GetOverlappedResult failed: %d",
142 (int) GetLastError());
147 #endif /* _WIN32_WCE */
148 wpa_printf(MSG_DEBUG
, "L2(NDISUIO): WriteFile failed: %d",
149 (int) GetLastError());
157 static void l2_packet_callback(struct l2_packet_data
*l2
);
160 static void l2_packet_rx_thread_try_read(struct l2_packet_data
*l2
)
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: "
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.
181 wpa_printf(MSG_DEBUG
, "l2_packet_rx_thread: Read %d byte packet",
182 (int) l2
->rx_written
);
185 * Notify the main thread about the availability of a frame and wait
186 * for the frame to be processed.
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
);
196 static DWORD WINAPI
l2_packet_rx_thread(LPVOID arg
)
198 struct l2_packet_data
*l2
= arg
;
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
;
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.
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
224 while (l2_ndisuio_global
&& run
) {
225 res
= WaitForMultipleObjects(2, handles
, FALSE
, INFINITE
);
228 wpa_printf(MSG_DEBUG
, "l2_packet_rx_thread: Received "
229 "request to stop RX thread");
232 case WAIT_OBJECT_0
+ 1:
233 l2_packet_rx_thread_try_read(l2
);
237 wpa_printf(MSG_DEBUG
, "l2_packet_rx_thread: "
238 "WaitForMultipleObjects failed: %d",
239 (int) GetLastError());
245 wpa_printf(MSG_DEBUG
, "L2(NDISUIO): RX thread stopped");
249 #else /* _WIN32_WCE */
250 static int l2_ndisuio_start_read(struct l2_packet_data
*l2
, int recursive
)
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
))
257 DWORD err
= GetLastError();
258 if (err
!= ERROR_IO_PENDING
) {
259 wpa_printf(MSG_DEBUG
, "L2(NDISUIO): ReadFile failed: "
264 * Once read is completed, l2_packet_rx_event() will be
268 wpa_printf(MSG_DEBUG
, "L2(NDISUIO): ReadFile returned data "
269 "without wait for completion");
271 l2_packet_callback(l2
);
276 #endif /* _WIN32_WCE */
279 static void l2_packet_callback(struct l2_packet_data
*l2
)
281 const u8
*rx_buf
, *rx_src
;
283 struct l2_ethhdr
*ethhdr
= (struct l2_ethhdr
*) l2
->rx_buf
;
285 wpa_printf(MSG_DEBUG
, "L2(NDISUIO): Read %d bytes",
286 (int) l2
->rx_written
);
288 if (l2
->l2_hdr
|| l2
->rx_written
< sizeof(*ethhdr
)) {
289 rx_buf
= (u8
*) ethhdr
;
290 rx_len
= l2
->rx_written
;
292 rx_buf
= (u8
*) (ethhdr
+ 1);
293 rx_len
= l2
->rx_written
- sizeof(*ethhdr
);
295 rx_src
= ethhdr
->h_source
;
297 l2
->rx_callback(l2
->rx_callback_ctx
, rx_src
, rx_buf
, rx_len
);
299 l2_ndisuio_start_read(l2
, 1);
300 #endif /* _WIN32_WCE */
304 static void l2_packet_rx_event(void *eloop_data
, void *user_data
)
306 struct l2_packet_data
*l2
= eloop_data
;
308 if (l2_ndisuio_global
)
309 l2
= l2_ndisuio_global
->l2
[l2_ndisuio_global
->refcount
- 1];
311 ResetEvent(l2
->rx_avail
);
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());
320 #endif /* _WIN32_WCE */
322 l2_packet_callback(l2
);
325 SetEvent(l2_ndisuio_global
->rx_processed
);
326 #endif /* _WIN32_WCE */
330 static int l2_ndisuio_set_ether_type(unsigned short protocol
)
332 USHORT proto
= htons(protocol
);
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());
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
)
354 struct l2_packet_data
*l2
;
356 if (l2_ndisuio_global
== NULL
) {
357 l2_ndisuio_global
= os_zalloc(sizeof(*l2_ndisuio_global
));
358 if (l2_ndisuio_global
== NULL
)
360 l2_ndisuio_global
->first_proto
= protocol
;
362 if (l2_ndisuio_global
->refcount
>= 2) {
363 wpa_printf(MSG_ERROR
, "L2(NDISUIO): Not more than two "
364 "simultaneous connections allowed");
367 l2_ndisuio_global
->refcount
++;
369 l2
= os_zalloc(sizeof(struct l2_packet_data
));
372 l2_ndisuio_global
->l2
[l2_ndisuio_global
->refcount
- 1] = l2
;
374 os_strlcpy(l2
->ifname
, ifname
, sizeof(l2
->ifname
));
375 l2
->rx_callback
= rx_callback
;
376 l2
->rx_callback_ctx
= rx_callback_ctx
;
380 os_memcpy(l2
->own_addr
, own_addr
, ETH_ALEN
);
382 if (l2_ndisuio_set_ether_type(protocol
) < 0) {
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
;
395 l2
->rx_avail
= CreateEvent(NULL
, TRUE
, FALSE
, NULL
);
396 if (l2
->rx_avail
== NULL
) {
401 eloop_register_event(l2
->rx_avail
, sizeof(l2
->rx_avail
),
402 l2_packet_rx_event
, l2
, NULL
);
405 l2_ndisuio_global
->stop_request
= CreateEvent(NULL
, TRUE
, FALSE
, NULL
);
407 * This event is being set based on media connect/disconnect
408 * notifications in driver_ndis.c.
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
;
420 if (l2_ndisuio_global
->ready_for_read
) {
421 CloseHandle(l2_ndisuio_global
->ready_for_read
);
422 l2_ndisuio_global
->ready_for_read
= NULL
;
424 if (l2_ndisuio_global
->rx_processed
) {
425 CloseHandle(l2_ndisuio_global
->rx_processed
);
426 l2_ndisuio_global
->rx_processed
= NULL
;
428 eloop_unregister_event(l2
->rx_avail
, sizeof(l2
->rx_avail
));
433 l2_ndisuio_global
->rx_thread
= CreateThread(NULL
, 0,
434 l2_packet_rx_thread
, l2
, 0,
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
;
445 #else /* _WIN32_WCE */
446 l2_ndisuio_start_read(l2
, 0);
447 #endif /* _WIN32_WCE */
453 struct l2_packet_data
* l2_packet_init_bridge(
454 const char *br_ifname
, const char *ifname
, const u8
*own_addr
,
455 unsigned short protocol
,
456 void (*rx_callback
)(void *ctx
, const u8
*src_addr
,
457 const u8
*buf
, size_t len
),
458 void *rx_callback_ctx
, int l2_hdr
)
460 return l2_packet_init(br_ifname
, own_addr
, protocol
, rx_callback
,
461 rx_callback_ctx
, l2_hdr
);
465 void l2_packet_deinit(struct l2_packet_data
*l2
)
470 if (l2_ndisuio_global
) {
471 l2_ndisuio_global
->refcount
--;
472 l2_ndisuio_global
->l2
[l2_ndisuio_global
->refcount
] = NULL
;
473 if (l2_ndisuio_global
->refcount
) {
474 wpa_printf(MSG_DEBUG
, "L2(NDISUIO): restore filtering "
476 l2_ndisuio_global
->first_proto
);
477 l2_ndisuio_set_ether_type(
478 l2_ndisuio_global
->first_proto
);
483 wpa_printf(MSG_DEBUG
, "L2(NDISUIO): Waiting for RX thread to "
485 SetEvent(l2_ndisuio_global
->stop_request
);
487 * Cancel pending ReadFile() in the RX thread (if we were still
488 * connected at this point).
490 if (!DeviceIoControl(driver_ndis_get_ndisuio_handle(),
491 IOCTL_CANCEL_READ
, NULL
, 0, NULL
, 0, NULL
,
493 wpa_printf(MSG_DEBUG
, "L2(NDISUIO): IOCTL_CANCEL_READ "
494 "failed: %d", (int) GetLastError());
495 /* RX thread will exit blocking ReadFile once NDISUIO
496 * notices that the adapter is disconnected. */
498 WaitForSingleObject(l2_ndisuio_global
->rx_thread
, INFINITE
);
499 wpa_printf(MSG_DEBUG
, "L2(NDISUIO): RX thread exited");
500 CloseHandle(l2_ndisuio_global
->rx_thread
);
501 CloseHandle(l2_ndisuio_global
->stop_request
);
502 CloseHandle(l2_ndisuio_global
->ready_for_read
);
503 CloseHandle(l2_ndisuio_global
->rx_processed
);
504 #endif /* _WIN32_WCE */
506 os_free(l2_ndisuio_global
);
507 l2_ndisuio_global
= NULL
;
511 CancelIo(driver_ndis_get_ndisuio_handle());
512 #endif /* _WIN32_WCE */
514 eloop_unregister_event(l2
->rx_avail
, sizeof(l2
->rx_avail
));
515 CloseHandle(l2
->rx_avail
);
520 int l2_packet_get_ip_addr(struct l2_packet_data
*l2
, char *buf
, size_t len
)
526 void l2_packet_notify_auth_start(struct l2_packet_data
*l2
)
531 int l2_packet_set_packet_filter(struct l2_packet_data
*l2
,
532 enum l2_packet_filter_type type
)