Home | History | Annotate | Download | only in l2_packet
      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 struct l2_packet_data * l2_packet_init_bridge(
    454 	const char *br_ifname, const char *ifname, const u8 *own_addr,
    455 	unsigned short protocol,
    456 	void (*rx_callback)(void *ctx, const u8 *src_addr,
    457 			    const u8 *buf, size_t len),
    458 	void *rx_callback_ctx, int l2_hdr)
    459 {
    460 	return l2_packet_init(br_ifname, own_addr, protocol, rx_callback,
    461 			      rx_callback_ctx, l2_hdr);
    462 }
    463 
    464 
    465 void l2_packet_deinit(struct l2_packet_data *l2)
    466 {
    467 	if (l2 == NULL)
    468 		return;
    469 
    470 	if (l2_ndisuio_global) {
    471 		l2_ndisuio_global->refcount--;
    472 		l2_ndisuio_global->l2[l2_ndisuio_global->refcount] = NULL;
    473 		if (l2_ndisuio_global->refcount) {
    474 			wpa_printf(MSG_DEBUG, "L2(NDISUIO): restore filtering "
    475 				   "ethertype to %04x",
    476 				   l2_ndisuio_global->first_proto);
    477 			l2_ndisuio_set_ether_type(
    478 				l2_ndisuio_global->first_proto);
    479 			return;
    480 		}
    481 
    482 #ifdef _WIN32_WCE
    483 		wpa_printf(MSG_DEBUG, "L2(NDISUIO): Waiting for RX thread to "
    484 			   "stop");
    485 		SetEvent(l2_ndisuio_global->stop_request);
    486 		/*
    487 		 * Cancel pending ReadFile() in the RX thread (if we were still
    488 		 * connected at this point).
    489 		 */
    490 		if (!DeviceIoControl(driver_ndis_get_ndisuio_handle(),
    491 				     IOCTL_CANCEL_READ, NULL, 0, NULL, 0, NULL,
    492 				     NULL)) {
    493 			wpa_printf(MSG_DEBUG, "L2(NDISUIO): IOCTL_CANCEL_READ "
    494 				   "failed: %d", (int) GetLastError());
    495 			/* RX thread will exit blocking ReadFile once NDISUIO
    496 			 * notices that the adapter is disconnected. */
    497 		}
    498 		WaitForSingleObject(l2_ndisuio_global->rx_thread, INFINITE);
    499 		wpa_printf(MSG_DEBUG, "L2(NDISUIO): RX thread exited");
    500 		CloseHandle(l2_ndisuio_global->rx_thread);
    501 		CloseHandle(l2_ndisuio_global->stop_request);
    502 		CloseHandle(l2_ndisuio_global->ready_for_read);
    503 		CloseHandle(l2_ndisuio_global->rx_processed);
    504 #endif /* _WIN32_WCE */
    505 
    506 		os_free(l2_ndisuio_global);
    507 		l2_ndisuio_global = NULL;
    508 	}
    509 
    510 #ifndef _WIN32_WCE
    511 	CancelIo(driver_ndis_get_ndisuio_handle());
    512 #endif /* _WIN32_WCE */
    513 
    514 	eloop_unregister_event(l2->rx_avail, sizeof(l2->rx_avail));
    515 	CloseHandle(l2->rx_avail);
    516 	os_free(l2);
    517 }
    518 
    519 
    520 int l2_packet_get_ip_addr(struct l2_packet_data *l2, char *buf, size_t len)
    521 {
    522 	return -1;
    523 }
    524 
    525 
    526 void l2_packet_notify_auth_start(struct l2_packet_data *l2)
    527 {
    528 }
    529 
    530 
    531 int l2_packet_set_packet_filter(struct l2_packet_data *l2,
    532 				enum l2_packet_filter_type type)
    533 {
    534 	return -1;
    535 }
    536