/* * 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; } struct l2_packet_data * l2_packet_init_bridge( const char *br_ifname, 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) { return l2_packet_init(br_ifname, own_addr, protocol, rx_callback, rx_callback_ctx, l2_hdr); } 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) { } int l2_packet_set_packet_filter(struct l2_packet_data *l2, enum l2_packet_filter_type type) { return -1; }