1 /* 2 * WPA Supplicant - Layer2 packet handling with Microsoft NDISUIO 3 * Copyright (c) 2003-2006, Jouni Malinen <j@w1.fi> 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License version 2 as 7 * published by the Free Software Foundation. 8 * 9 * Alternatively, this software may be distributed under the terms of BSD 10 * license. 11 * 12 * See README and COPYING for more details. 13 * 14 * This implementation requires Windows specific event loop implementation, 15 * i.e., eloop_win.c. In addition, the NDISUIO connection is shared with 16 * driver_ndis.c, so only that driver interface can be used and 17 * CONFIG_USE_NDISUIO must be defined. 18 * 19 * WinXP version of the code uses overlapped I/O and a single threaded design 20 * with callback functions from I/O code. WinCE version uses a separate RX 21 * thread that blocks on ReadFile() whenever the media status is connected. 22 */ 23 24 #include "includes.h" 25 #include <winsock2.h> 26 #include <ntddndis.h> 27 28 #ifdef _WIN32_WCE 29 #include <winioctl.h> 30 #include <nuiouser.h> 31 #endif /* _WIN32_WCE */ 32 33 #include "common.h" 34 #include "eloop.h" 35 #include "l2_packet.h" 36 37 #ifndef _WIN32_WCE 38 /* from nuiouser.h */ 39 #define FSCTL_NDISUIO_BASE FILE_DEVICE_NETWORK 40 #define _NDISUIO_CTL_CODE(_Function, _Method, _Access) \ 41 CTL_CODE(FSCTL_NDISUIO_BASE, _Function, _Method, _Access) 42 #define IOCTL_NDISUIO_SET_ETHER_TYPE \ 43 _NDISUIO_CTL_CODE(0x202, METHOD_BUFFERED, \ 44 FILE_READ_ACCESS | FILE_WRITE_ACCESS) 45 #endif /* _WIN32_WCE */ 46 47 /* From driver_ndis.c to shared the handle to NDISUIO */ 48 HANDLE driver_ndis_get_ndisuio_handle(void); 49 50 /* 51 * NDISUIO supports filtering of only one ethertype at the time, so we must 52 * fake support for two (EAPOL and RSN pre-auth) by switching to pre-auth 53 * whenever wpa_supplicant is trying to pre-authenticate and then switching 54 * back to EAPOL when pre-authentication has been completed. 55 */ 56 57 struct l2_packet_data; 58 59 struct l2_packet_ndisuio_global { 60 int refcount; 61 unsigned short first_proto; 62 struct l2_packet_data *l2[2]; 63 #ifdef _WIN32_WCE 64 HANDLE rx_thread; 65 HANDLE stop_request; 66 HANDLE ready_for_read; 67 HANDLE rx_processed; 68 #endif /* _WIN32_WCE */ 69 }; 70 71 static struct l2_packet_ndisuio_global *l2_ndisuio_global = NULL; 72 73 struct l2_packet_data { 74 char ifname[100]; 75 u8 own_addr[ETH_ALEN]; 76 void (*rx_callback)(void *ctx, const u8 *src_addr, 77 const u8 *buf, size_t len); 78 void *rx_callback_ctx; 79 int l2_hdr; /* whether to include layer 2 (Ethernet) header in calls to 80 * rx_callback and l2_packet_send() */ 81 HANDLE rx_avail; 82 #ifndef _WIN32_WCE 83 OVERLAPPED rx_overlapped; 84 #endif /* _WIN32_WCE */ 85 u8 rx_buf[1514]; 86 DWORD rx_written; 87 }; 88 89 90 int l2_packet_get_own_addr(struct l2_packet_data *l2, u8 *addr) 91 { 92 os_memcpy(addr, l2->own_addr, ETH_ALEN); 93 return 0; 94 } 95 96 97 int l2_packet_send(struct l2_packet_data *l2, const u8 *dst_addr, u16 proto, 98 const u8 *buf, size_t len) 99 { 100 BOOL res; 101 DWORD written; 102 struct l2_ethhdr *eth; 103 #ifndef _WIN32_WCE 104 OVERLAPPED overlapped; 105 #endif /* _WIN32_WCE */ 106 OVERLAPPED *o; 107 108 if (l2 == NULL) 109 return -1; 110 111 #ifdef _WIN32_WCE 112 o = NULL; 113 #else /* _WIN32_WCE */ 114 os_memset(&overlapped, 0, sizeof(overlapped)); 115 o = &overlapped; 116 #endif /* _WIN32_WCE */ 117 118 if (l2->l2_hdr) { 119 res = WriteFile(driver_ndis_get_ndisuio_handle(), buf, len, 120 &written, o); 121 } else { 122 size_t mlen = sizeof(*eth) + len; 123 eth = os_malloc(mlen); 124 if (eth == NULL) 125 return -1; 126 127 os_memcpy(eth->h_dest, dst_addr, ETH_ALEN); 128 os_memcpy(eth->h_source, l2->own_addr, ETH_ALEN); 129 eth->h_proto = htons(proto); 130 os_memcpy(eth + 1, buf, len); 131 res = WriteFile(driver_ndis_get_ndisuio_handle(), eth, mlen, 132 &written, o); 133 os_free(eth); 134 } 135 136 if (!res) { 137 DWORD err = GetLastError(); 138 #ifndef _WIN32_WCE 139 if (err == ERROR_IO_PENDING) { 140 /* For now, just assume that the packet will be sent in 141 * time before the next write happens. This could be 142 * cleaned up at some point to actually wait for 143 * completion before starting new writes. 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 } 517