f4e5fd948a
When using overlapped write, we must have the provided memory areas available during the operation and cannot just use stack unless we wait for the completion within the function. In the case of TX here, we can easily wait for the completion since it is likely to happen immediately. In addition, this provides more reliable success/failure return value for l2_packet_send(). [Bug 328]
522 lines
14 KiB
C
522 lines
14 KiB
C
/*
|
|
* WPA Supplicant - Layer2 packet handling with Microsoft NDISUIO
|
|
* Copyright (c) 2003-2006, Jouni Malinen <j@w1.fi>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* Alternatively, this software may be distributed under the terms of BSD
|
|
* license.
|
|
*
|
|
* See README and COPYING 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)
|
|
{
|
|
}
|