| /* |
| * WPA Supplicant - Layer2 packet handling with Microsoft NDISUIO |
| * Copyright (c) 2003-2006, Jouni Malinen <j@w1.fi> |
| * |
| * This software may be distributed under the terms of the BSD license. |
| * See README for more details. |
| * |
| * This implementation requires Windows specific event loop implementation, |
| * i.e., eloop_win.c. In addition, the NDISUIO connection is shared with |
| * driver_ndis.c, so only that driver interface can be used and |
| * CONFIG_USE_NDISUIO must be defined. |
| * |
| * WinXP version of the code uses overlapped I/O and a single threaded design |
| * with callback functions from I/O code. WinCE version uses a separate RX |
| * thread that blocks on ReadFile() whenever the media status is connected. |
| */ |
| |
| #include "includes.h" |
| #include <winsock2.h> |
| #include <ntddndis.h> |
| |
| #ifdef _WIN32_WCE |
| #include <winioctl.h> |
| #include <nuiouser.h> |
| #endif /* _WIN32_WCE */ |
| |
| #include "common.h" |
| #include "eloop.h" |
| #include "l2_packet.h" |
| |
| #ifndef _WIN32_WCE |
| /* from nuiouser.h */ |
| #define FSCTL_NDISUIO_BASE FILE_DEVICE_NETWORK |
| #define _NDISUIO_CTL_CODE(_Function, _Method, _Access) \ |
| CTL_CODE(FSCTL_NDISUIO_BASE, _Function, _Method, _Access) |
| #define IOCTL_NDISUIO_SET_ETHER_TYPE \ |
| _NDISUIO_CTL_CODE(0x202, METHOD_BUFFERED, \ |
| FILE_READ_ACCESS | FILE_WRITE_ACCESS) |
| #endif /* _WIN32_WCE */ |
| |
| /* From driver_ndis.c to shared the handle to NDISUIO */ |
| HANDLE driver_ndis_get_ndisuio_handle(void); |
| |
| /* |
| * NDISUIO supports filtering of only one ethertype at the time, so we must |
| * fake support for two (EAPOL and RSN pre-auth) by switching to pre-auth |
| * whenever wpa_supplicant is trying to pre-authenticate and then switching |
| * back to EAPOL when pre-authentication has been completed. |
| */ |
| |
| struct l2_packet_data; |
| |
| struct l2_packet_ndisuio_global { |
| int refcount; |
| unsigned short first_proto; |
| struct l2_packet_data *l2[2]; |
| #ifdef _WIN32_WCE |
| HANDLE rx_thread; |
| HANDLE stop_request; |
| HANDLE ready_for_read; |
| HANDLE rx_processed; |
| #endif /* _WIN32_WCE */ |
| }; |
| |
| static struct l2_packet_ndisuio_global *l2_ndisuio_global = NULL; |
| |
| struct l2_packet_data { |
| char ifname[100]; |
| u8 own_addr[ETH_ALEN]; |
| void (*rx_callback)(void *ctx, const u8 *src_addr, |
| const u8 *buf, size_t len); |
| void *rx_callback_ctx; |
| int l2_hdr; /* whether to include layer 2 (Ethernet) header in calls to |
| * rx_callback and l2_packet_send() */ |
| HANDLE rx_avail; |
| #ifndef _WIN32_WCE |
| OVERLAPPED rx_overlapped; |
| #endif /* _WIN32_WCE */ |
| u8 rx_buf[1514]; |
| DWORD rx_written; |
| }; |
| |
| |
| int l2_packet_get_own_addr(struct l2_packet_data *l2, u8 *addr) |
| { |
| os_memcpy(addr, l2->own_addr, ETH_ALEN); |
| return 0; |
| } |
| |
| |
| int l2_packet_send(struct l2_packet_data *l2, const u8 *dst_addr, u16 proto, |
| const u8 *buf, size_t len) |
| { |
| BOOL res; |
| DWORD written; |
| struct l2_ethhdr *eth; |
| #ifndef _WIN32_WCE |
| OVERLAPPED overlapped; |
| #endif /* _WIN32_WCE */ |
| OVERLAPPED *o; |
| |
| if (l2 == NULL) |
| return -1; |
| |
| #ifdef _WIN32_WCE |
| o = NULL; |
| #else /* _WIN32_WCE */ |
| os_memset(&overlapped, 0, sizeof(overlapped)); |
| o = &overlapped; |
| #endif /* _WIN32_WCE */ |
| |
| if (l2->l2_hdr) { |
| res = WriteFile(driver_ndis_get_ndisuio_handle(), buf, len, |
| &written, o); |
| } else { |
| size_t mlen = sizeof(*eth) + len; |
| eth = os_malloc(mlen); |
| if (eth == NULL) |
| return -1; |
| |
| os_memcpy(eth->h_dest, dst_addr, ETH_ALEN); |
| os_memcpy(eth->h_source, l2->own_addr, ETH_ALEN); |
| eth->h_proto = htons(proto); |
| os_memcpy(eth + 1, buf, len); |
| res = WriteFile(driver_ndis_get_ndisuio_handle(), eth, mlen, |
| &written, o); |
| os_free(eth); |
| } |
| |
| if (!res) { |
| DWORD err = GetLastError(); |
| #ifndef _WIN32_WCE |
| if (err == ERROR_IO_PENDING) { |
| wpa_printf(MSG_DEBUG, "L2(NDISUIO): Wait for pending " |
| "write to complete"); |
| res = GetOverlappedResult( |
| driver_ndis_get_ndisuio_handle(), &overlapped, |
| &written, TRUE); |
| if (!res) { |
| wpa_printf(MSG_DEBUG, "L2(NDISUIO): " |
| "GetOverlappedResult failed: %d", |
| (int) GetLastError()); |
| return -1; |
| } |
| return 0; |
| } |
| #endif /* _WIN32_WCE */ |
| wpa_printf(MSG_DEBUG, "L2(NDISUIO): WriteFile failed: %d", |
| (int) GetLastError()); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| |
| static void l2_packet_callback(struct l2_packet_data *l2); |
| |
| #ifdef _WIN32_WCE |
| static void l2_packet_rx_thread_try_read(struct l2_packet_data *l2) |
| { |
| HANDLE handles[2]; |
| |
| wpa_printf(MSG_MSGDUMP, "l2_packet_rx_thread: -> ReadFile"); |
| if (!ReadFile(driver_ndis_get_ndisuio_handle(), l2->rx_buf, |
| sizeof(l2->rx_buf), &l2->rx_written, NULL)) { |
| DWORD err = GetLastError(); |
| wpa_printf(MSG_DEBUG, "l2_packet_rx_thread: ReadFile failed: " |
| "%d", (int) err); |
| /* |
| * ReadFile on NDISUIO/WinCE returns ERROR_DEVICE_NOT_CONNECTED |
| * error whenever the connection is not up. Yield the thread to |
| * avoid triggering a busy loop. Connection event should stop |
| * us from looping for long, but we need to allow enough CPU |
| * for the main thread to process the media disconnection. |
| */ |
| Sleep(100); |
| return; |
| } |
| |
| wpa_printf(MSG_DEBUG, "l2_packet_rx_thread: Read %d byte packet", |
| (int) l2->rx_written); |
| |
| /* |
| * Notify the main thread about the availability of a frame and wait |
| * for the frame to be processed. |
| */ |
| SetEvent(l2->rx_avail); |
| handles[0] = l2_ndisuio_global->stop_request; |
| handles[1] = l2_ndisuio_global->rx_processed; |
| WaitForMultipleObjects(2, handles, FALSE, INFINITE); |
| ResetEvent(l2_ndisuio_global->rx_processed); |
| } |
| |
| |
| static DWORD WINAPI l2_packet_rx_thread(LPVOID arg) |
| { |
| struct l2_packet_data *l2 = arg; |
| DWORD res; |
| HANDLE handles[2]; |
| int run = 1; |
| |
| wpa_printf(MSG_DEBUG, "L2(NDISUIO): RX thread started"); |
| handles[0] = l2_ndisuio_global->stop_request; |
| handles[1] = l2_ndisuio_global->ready_for_read; |
| |
| /* |
| * Unfortunately, NDISUIO on WinCE does not seem to support waiting |
| * on the handle. There do not seem to be anything else that we could |
| * wait for either. If one were to modify NDISUIO to set a named event |
| * whenever packets are available, this event could be used here to |
| * avoid having to poll for new packets or we could even move to use a |
| * single threaded design. |
| * |
| * In addition, NDISUIO on WinCE is returning |
| * ERROR_DEVICE_NOT_CONNECTED whenever ReadFile() is attempted while |
| * the adapter is not in connected state. For now, we are just using a |
| * local event to allow ReadFile calls only after having received NDIS |
| * media connect event. This event could be easily converted to handle |
| * another event if the protocol driver is replaced with somewhat more |
| * useful design. |
| */ |
| |
| while (l2_ndisuio_global && run) { |
| res = WaitForMultipleObjects(2, handles, FALSE, INFINITE); |
| switch (res) { |
| case WAIT_OBJECT_0: |
| wpa_printf(MSG_DEBUG, "l2_packet_rx_thread: Received " |
| "request to stop RX thread"); |
| run = 0; |
| break; |
| case WAIT_OBJECT_0 + 1: |
| l2_packet_rx_thread_try_read(l2); |
| break; |
| case WAIT_FAILED: |
| default: |
| wpa_printf(MSG_DEBUG, "l2_packet_rx_thread: " |
| "WaitForMultipleObjects failed: %d", |
| (int) GetLastError()); |
| run = 0; |
| break; |
| } |
| } |
| |
| wpa_printf(MSG_DEBUG, "L2(NDISUIO): RX thread stopped"); |
| |
| return 0; |
| } |
| #else /* _WIN32_WCE */ |
| static int l2_ndisuio_start_read(struct l2_packet_data *l2, int recursive) |
| { |
| os_memset(&l2->rx_overlapped, 0, sizeof(l2->rx_overlapped)); |
| l2->rx_overlapped.hEvent = l2->rx_avail; |
| if (!ReadFile(driver_ndis_get_ndisuio_handle(), l2->rx_buf, |
| sizeof(l2->rx_buf), &l2->rx_written, &l2->rx_overlapped)) |
| { |
| DWORD err = GetLastError(); |
| if (err != ERROR_IO_PENDING) { |
| wpa_printf(MSG_DEBUG, "L2(NDISUIO): ReadFile failed: " |
| "%d", (int) err); |
| return -1; |
| } |
| /* |
| * Once read is completed, l2_packet_rx_event() will be |
| * called. |
| */ |
| } else { |
| wpa_printf(MSG_DEBUG, "L2(NDISUIO): ReadFile returned data " |
| "without wait for completion"); |
| if (!recursive) |
| l2_packet_callback(l2); |
| } |
| |
| return 0; |
| } |
| #endif /* _WIN32_WCE */ |
| |
| |
| static void l2_packet_callback(struct l2_packet_data *l2) |
| { |
| const u8 *rx_buf, *rx_src; |
| size_t rx_len; |
| struct l2_ethhdr *ethhdr = (struct l2_ethhdr *) l2->rx_buf; |
| |
| wpa_printf(MSG_DEBUG, "L2(NDISUIO): Read %d bytes", |
| (int) l2->rx_written); |
| |
| if (l2->l2_hdr || l2->rx_written < sizeof(*ethhdr)) { |
| rx_buf = (u8 *) ethhdr; |
| rx_len = l2->rx_written; |
| } else { |
| rx_buf = (u8 *) (ethhdr + 1); |
| rx_len = l2->rx_written - sizeof(*ethhdr); |
| } |
| rx_src = ethhdr->h_source; |
| |
| l2->rx_callback(l2->rx_callback_ctx, rx_src, rx_buf, rx_len); |
| #ifndef _WIN32_WCE |
| l2_ndisuio_start_read(l2, 1); |
| #endif /* _WIN32_WCE */ |
| } |
| |
| |
| static void l2_packet_rx_event(void *eloop_data, void *user_data) |
| { |
| struct l2_packet_data *l2 = eloop_data; |
| |
| if (l2_ndisuio_global) |
| l2 = l2_ndisuio_global->l2[l2_ndisuio_global->refcount - 1]; |
| |
| ResetEvent(l2->rx_avail); |
| |
| #ifndef _WIN32_WCE |
| if (!GetOverlappedResult(driver_ndis_get_ndisuio_handle(), |
| &l2->rx_overlapped, &l2->rx_written, FALSE)) { |
| wpa_printf(MSG_DEBUG, "L2(NDISUIO): GetOverlappedResult " |
| "failed: %d", (int) GetLastError()); |
| return; |
| } |
| #endif /* _WIN32_WCE */ |
| |
| l2_packet_callback(l2); |
| |
| #ifdef _WIN32_WCE |
| SetEvent(l2_ndisuio_global->rx_processed); |
| #endif /* _WIN32_WCE */ |
| } |
| |
| |
| static int l2_ndisuio_set_ether_type(unsigned short protocol) |
| { |
| USHORT proto = htons(protocol); |
| DWORD written; |
| |
| if (!DeviceIoControl(driver_ndis_get_ndisuio_handle(), |
| IOCTL_NDISUIO_SET_ETHER_TYPE, &proto, |
| sizeof(proto), NULL, 0, &written, NULL)) { |
| wpa_printf(MSG_ERROR, "L2(NDISUIO): " |
| "IOCTL_NDISUIO_SET_ETHER_TYPE failed: %d", |
| (int) GetLastError()); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| |
| struct l2_packet_data * l2_packet_init( |
| const char *ifname, const u8 *own_addr, unsigned short protocol, |
| void (*rx_callback)(void *ctx, const u8 *src_addr, |
| const u8 *buf, size_t len), |
| void *rx_callback_ctx, int l2_hdr) |
| { |
| struct l2_packet_data *l2; |
| |
| if (l2_ndisuio_global == NULL) { |
| l2_ndisuio_global = os_zalloc(sizeof(*l2_ndisuio_global)); |
| if (l2_ndisuio_global == NULL) |
| return NULL; |
| l2_ndisuio_global->first_proto = protocol; |
| } |
| if (l2_ndisuio_global->refcount >= 2) { |
| wpa_printf(MSG_ERROR, "L2(NDISUIO): Not more than two " |
| "simultaneous connections allowed"); |
| return NULL; |
| } |
| l2_ndisuio_global->refcount++; |
| |
| l2 = os_zalloc(sizeof(struct l2_packet_data)); |
| if (l2 == NULL) |
| return NULL; |
| l2_ndisuio_global->l2[l2_ndisuio_global->refcount - 1] = l2; |
| |
| os_strlcpy(l2->ifname, ifname, sizeof(l2->ifname)); |
| l2->rx_callback = rx_callback; |
| l2->rx_callback_ctx = rx_callback_ctx; |
| l2->l2_hdr = l2_hdr; |
| |
| if (own_addr) |
| os_memcpy(l2->own_addr, own_addr, ETH_ALEN); |
| |
| if (l2_ndisuio_set_ether_type(protocol) < 0) { |
| os_free(l2); |
| return NULL; |
| } |
| |
| if (l2_ndisuio_global->refcount > 1) { |
| wpa_printf(MSG_DEBUG, "L2(NDISUIO): Temporarily setting " |
| "filtering ethertype to %04x", protocol); |
| if (l2_ndisuio_global->l2[0]) |
| l2->rx_avail = l2_ndisuio_global->l2[0]->rx_avail; |
| return l2; |
| } |
| |
| l2->rx_avail = CreateEvent(NULL, TRUE, FALSE, NULL); |
| if (l2->rx_avail == NULL) { |
| os_free(l2); |
| return NULL; |
| } |
| |
| eloop_register_event(l2->rx_avail, sizeof(l2->rx_avail), |
| l2_packet_rx_event, l2, NULL); |
| |
| #ifdef _WIN32_WCE |
| l2_ndisuio_global->stop_request = CreateEvent(NULL, TRUE, FALSE, NULL); |
| /* |
| * This event is being set based on media connect/disconnect |
| * notifications in driver_ndis.c. |
| */ |
| l2_ndisuio_global->ready_for_read = |
| CreateEvent(NULL, TRUE, FALSE, TEXT("WpaSupplicantConnected")); |
| l2_ndisuio_global->rx_processed = CreateEvent(NULL, TRUE, FALSE, NULL); |
| if (l2_ndisuio_global->stop_request == NULL || |
| l2_ndisuio_global->ready_for_read == NULL || |
| l2_ndisuio_global->rx_processed == NULL) { |
| if (l2_ndisuio_global->stop_request) { |
| CloseHandle(l2_ndisuio_global->stop_request); |
| l2_ndisuio_global->stop_request = NULL; |
| } |
| if (l2_ndisuio_global->ready_for_read) { |
| CloseHandle(l2_ndisuio_global->ready_for_read); |
| l2_ndisuio_global->ready_for_read = NULL; |
| } |
| if (l2_ndisuio_global->rx_processed) { |
| CloseHandle(l2_ndisuio_global->rx_processed); |
| l2_ndisuio_global->rx_processed = NULL; |
| } |
| eloop_unregister_event(l2->rx_avail, sizeof(l2->rx_avail)); |
| os_free(l2); |
| return NULL; |
| } |
| |
| l2_ndisuio_global->rx_thread = CreateThread(NULL, 0, |
| l2_packet_rx_thread, l2, 0, |
| NULL); |
| if (l2_ndisuio_global->rx_thread == NULL) { |
| wpa_printf(MSG_INFO, "L2(NDISUIO): Failed to create RX " |
| "thread: %d", (int) GetLastError()); |
| eloop_unregister_event(l2->rx_avail, sizeof(l2->rx_avail)); |
| CloseHandle(l2_ndisuio_global->stop_request); |
| l2_ndisuio_global->stop_request = NULL; |
| os_free(l2); |
| return NULL; |
| } |
| #else /* _WIN32_WCE */ |
| l2_ndisuio_start_read(l2, 0); |
| #endif /* _WIN32_WCE */ |
| |
| return l2; |
| } |
| |
| |
| void l2_packet_deinit(struct l2_packet_data *l2) |
| { |
| if (l2 == NULL) |
| return; |
| |
| if (l2_ndisuio_global) { |
| l2_ndisuio_global->refcount--; |
| l2_ndisuio_global->l2[l2_ndisuio_global->refcount] = NULL; |
| if (l2_ndisuio_global->refcount) { |
| wpa_printf(MSG_DEBUG, "L2(NDISUIO): restore filtering " |
| "ethertype to %04x", |
| l2_ndisuio_global->first_proto); |
| l2_ndisuio_set_ether_type( |
| l2_ndisuio_global->first_proto); |
| return; |
| } |
| |
| #ifdef _WIN32_WCE |
| wpa_printf(MSG_DEBUG, "L2(NDISUIO): Waiting for RX thread to " |
| "stop"); |
| SetEvent(l2_ndisuio_global->stop_request); |
| /* |
| * Cancel pending ReadFile() in the RX thread (if we were still |
| * connected at this point). |
| */ |
| if (!DeviceIoControl(driver_ndis_get_ndisuio_handle(), |
| IOCTL_CANCEL_READ, NULL, 0, NULL, 0, NULL, |
| NULL)) { |
| wpa_printf(MSG_DEBUG, "L2(NDISUIO): IOCTL_CANCEL_READ " |
| "failed: %d", (int) GetLastError()); |
| /* RX thread will exit blocking ReadFile once NDISUIO |
| * notices that the adapter is disconnected. */ |
| } |
| WaitForSingleObject(l2_ndisuio_global->rx_thread, INFINITE); |
| wpa_printf(MSG_DEBUG, "L2(NDISUIO): RX thread exited"); |
| CloseHandle(l2_ndisuio_global->rx_thread); |
| CloseHandle(l2_ndisuio_global->stop_request); |
| CloseHandle(l2_ndisuio_global->ready_for_read); |
| CloseHandle(l2_ndisuio_global->rx_processed); |
| #endif /* _WIN32_WCE */ |
| |
| os_free(l2_ndisuio_global); |
| l2_ndisuio_global = NULL; |
| } |
| |
| #ifndef _WIN32_WCE |
| CancelIo(driver_ndis_get_ndisuio_handle()); |
| #endif /* _WIN32_WCE */ |
| |
| eloop_unregister_event(l2->rx_avail, sizeof(l2->rx_avail)); |
| CloseHandle(l2->rx_avail); |
| os_free(l2); |
| } |
| |
| |
| int l2_packet_get_ip_addr(struct l2_packet_data *l2, char *buf, size_t len) |
| { |
| return -1; |
| } |
| |
| |
| void l2_packet_notify_auth_start(struct l2_packet_data *l2) |
| { |
| } |