Merge remote-tracking branch 'origin/debian' into debian
[mech_eap.git] / libeap / src / l2_packet / l2_packet_ndis.c
diff --git a/libeap/src/l2_packet/l2_packet_ndis.c b/libeap/src/l2_packet/l2_packet_ndis.c
new file mode 100644 (file)
index 0000000..7167781
--- /dev/null
@@ -0,0 +1,535 @@
+/*
+ * 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;
+}