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 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 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 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 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 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 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 521 int l2_packet_get_ip_addr(struct l2_packet_data *l2, char *buf, size_t len) 522 { 523 return -1; 524 } 525 526 527 void l2_packet_notify_auth_start(struct l2_packet_data *l2) 528 { 529 } 530 531 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