1 /* 2 * WPA Supplicant - Layer2 packet handling with Microsoft NDISUIO 3 * Copyright (c) 2003-2006, Jouni Malinen <j (at) 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 } 517