l2_packet_ndis.c revision 8d520ff1dc2da35cdca849e982051b86468016d8
1/*
2 * WPA Supplicant - Layer2 packet handling with Microsoft NDISUIO
3 * Copyright (c) 2003-2006, Jouni Malinen <j@w1.fi>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 as
7 * published by the Free Software Foundation.
8 *
9 * Alternatively, this software may be distributed under the terms of BSD
10 * license.
11 *
12 * See README and COPYING for more details.
13 *
14 * This implementation requires Windows specific event loop implementation,
15 * i.e., eloop_win.c. In addition, the NDISUIO connection is shared with
16 * driver_ndis.c, so only that driver interface can be used and
17 * CONFIG_USE_NDISUIO must be defined.
18 *
19 * WinXP version of the code uses overlapped I/O and a single threaded design
20 * with callback functions from I/O code. WinCE version uses a separate RX
21 * thread that blocks on ReadFile() whenever the media status is connected.
22 */
23
24#include "includes.h"
25#include <winsock2.h>
26#include <ntddndis.h>
27
28#ifdef _WIN32_WCE
29#include <winioctl.h>
30#include <nuiouser.h>
31#endif /* _WIN32_WCE */
32
33#include "common.h"
34#include "eloop.h"
35#include "l2_packet.h"
36
37#ifndef _WIN32_WCE
38/* from nuiouser.h */
39#define FSCTL_NDISUIO_BASE      FILE_DEVICE_NETWORK
40#define _NDISUIO_CTL_CODE(_Function, _Method, _Access) \
41	CTL_CODE(FSCTL_NDISUIO_BASE, _Function, _Method, _Access)
42#define IOCTL_NDISUIO_SET_ETHER_TYPE \
43	_NDISUIO_CTL_CODE(0x202, METHOD_BUFFERED, \
44			  FILE_READ_ACCESS | FILE_WRITE_ACCESS)
45#endif /* _WIN32_WCE */
46
47/* From driver_ndis.c to shared the handle to NDISUIO */
48HANDLE driver_ndis_get_ndisuio_handle(void);
49
50/*
51 * NDISUIO supports filtering of only one ethertype at the time, so we must
52 * fake support for two (EAPOL and RSN pre-auth) by switching to pre-auth
53 * whenever wpa_supplicant is trying to pre-authenticate and then switching
54 * back to EAPOL when pre-authentication has been completed.
55 */
56
57struct l2_packet_data;
58
59struct l2_packet_ndisuio_global {
60	int refcount;
61	unsigned short first_proto;
62	struct l2_packet_data *l2[2];
63#ifdef _WIN32_WCE
64	HANDLE rx_thread;
65	HANDLE stop_request;
66	HANDLE ready_for_read;
67	HANDLE rx_processed;
68#endif /* _WIN32_WCE */
69};
70
71static struct l2_packet_ndisuio_global *l2_ndisuio_global = NULL;
72
73struct l2_packet_data {
74	char ifname[100];
75	u8 own_addr[ETH_ALEN];
76	void (*rx_callback)(void *ctx, const u8 *src_addr,
77			    const u8 *buf, size_t len);
78	void *rx_callback_ctx;
79	int l2_hdr; /* whether to include layer 2 (Ethernet) header in calls to
80		     * rx_callback and l2_packet_send() */
81	HANDLE rx_avail;
82#ifndef _WIN32_WCE
83	OVERLAPPED rx_overlapped;
84#endif /* _WIN32_WCE */
85	u8 rx_buf[1514];
86	DWORD rx_written;
87};
88
89
90int l2_packet_get_own_addr(struct l2_packet_data *l2, u8 *addr)
91{
92	os_memcpy(addr, l2->own_addr, ETH_ALEN);
93	return 0;
94}
95
96
97int l2_packet_send(struct l2_packet_data *l2, const u8 *dst_addr, u16 proto,
98		   const u8 *buf, size_t len)
99{
100	BOOL res;
101	DWORD written;
102	struct l2_ethhdr *eth;
103#ifndef _WIN32_WCE
104	OVERLAPPED overlapped;
105#endif /* _WIN32_WCE */
106	OVERLAPPED *o;
107
108	if (l2 == NULL)
109		return -1;
110
111#ifdef _WIN32_WCE
112	o = NULL;
113#else /* _WIN32_WCE */
114	os_memset(&overlapped, 0, sizeof(overlapped));
115	o = &overlapped;
116#endif /* _WIN32_WCE */
117
118	if (l2->l2_hdr) {
119		res = WriteFile(driver_ndis_get_ndisuio_handle(), buf, len,
120				&written, o);
121	} else {
122		size_t mlen = sizeof(*eth) + len;
123		eth = os_malloc(mlen);
124		if (eth == NULL)
125			return -1;
126
127		os_memcpy(eth->h_dest, dst_addr, ETH_ALEN);
128		os_memcpy(eth->h_source, l2->own_addr, ETH_ALEN);
129		eth->h_proto = htons(proto);
130		os_memcpy(eth + 1, buf, len);
131		res = WriteFile(driver_ndis_get_ndisuio_handle(), eth, mlen,
132				&written, o);
133		os_free(eth);
134	}
135
136	if (!res) {
137		DWORD err = GetLastError();
138#ifndef _WIN32_WCE
139		if (err == ERROR_IO_PENDING) {
140			wpa_printf(MSG_DEBUG, "L2(NDISUIO): Wait for pending "
141				   "write to complete");
142			res = GetOverlappedResult(
143				driver_ndis_get_ndisuio_handle(), &overlapped,
144				&written, TRUE);
145			if (!res) {
146				wpa_printf(MSG_DEBUG, "L2(NDISUIO): "
147					   "GetOverlappedResult failed: %d",
148					   (int) GetLastError());
149				return -1;
150			}
151			return 0;
152		}
153#endif /* _WIN32_WCE */
154		wpa_printf(MSG_DEBUG, "L2(NDISUIO): WriteFile failed: %d",
155			   (int) GetLastError());
156		return -1;
157	}
158
159	return 0;
160}
161
162
163static void l2_packet_callback(struct l2_packet_data *l2);
164
165#ifdef _WIN32_WCE
166static void l2_packet_rx_thread_try_read(struct l2_packet_data *l2)
167{
168	HANDLE handles[2];
169
170	wpa_printf(MSG_MSGDUMP, "l2_packet_rx_thread: -> ReadFile");
171	if (!ReadFile(driver_ndis_get_ndisuio_handle(), l2->rx_buf,
172		      sizeof(l2->rx_buf), &l2->rx_written, NULL)) {
173		DWORD err = GetLastError();
174		wpa_printf(MSG_DEBUG, "l2_packet_rx_thread: ReadFile failed: "
175			   "%d", (int) err);
176		/*
177		 * ReadFile on NDISUIO/WinCE returns ERROR_DEVICE_NOT_CONNECTED
178		 * error whenever the connection is not up. Yield the thread to
179		 * avoid triggering a busy loop. Connection event should stop
180		 * us from looping for long, but we need to allow enough CPU
181		 * for the main thread to process the media disconnection.
182		 */
183		Sleep(100);
184		return;
185	}
186
187	wpa_printf(MSG_DEBUG, "l2_packet_rx_thread: Read %d byte packet",
188		   (int) l2->rx_written);
189
190	/*
191	 * Notify the main thread about the availability of a frame and wait
192	 * for the frame to be processed.
193	 */
194	SetEvent(l2->rx_avail);
195	handles[0] = l2_ndisuio_global->stop_request;
196	handles[1] = l2_ndisuio_global->rx_processed;
197	WaitForMultipleObjects(2, handles, FALSE, INFINITE);
198	ResetEvent(l2_ndisuio_global->rx_processed);
199}
200
201
202static DWORD WINAPI l2_packet_rx_thread(LPVOID arg)
203{
204	struct l2_packet_data *l2 = arg;
205	DWORD res;
206	HANDLE handles[2];
207	int run = 1;
208
209	wpa_printf(MSG_DEBUG, "L2(NDISUIO): RX thread started");
210	handles[0] = l2_ndisuio_global->stop_request;
211	handles[1] = l2_ndisuio_global->ready_for_read;
212
213	/*
214	 * Unfortunately, NDISUIO on WinCE does not seem to support waiting
215	 * on the handle. There do not seem to be anything else that we could
216	 * wait for either. If one were to modify NDISUIO to set a named event
217	 * whenever packets are available, this event could be used here to
218	 * avoid having to poll for new packets or we could even move to use a
219	 * single threaded design.
220	 *
221	 * In addition, NDISUIO on WinCE is returning
222	 * ERROR_DEVICE_NOT_CONNECTED whenever ReadFile() is attempted while
223	 * the adapter is not in connected state. For now, we are just using a
224	 * local event to allow ReadFile calls only after having received NDIS
225	 * media connect event. This event could be easily converted to handle
226	 * another event if the protocol driver is replaced with somewhat more
227	 * useful design.
228	 */
229
230	while (l2_ndisuio_global && run) {
231		res = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
232		switch (res) {
233		case WAIT_OBJECT_0:
234			wpa_printf(MSG_DEBUG, "l2_packet_rx_thread: Received "
235				   "request to stop RX thread");
236			run = 0;
237			break;
238		case WAIT_OBJECT_0 + 1:
239			l2_packet_rx_thread_try_read(l2);
240			break;
241		case WAIT_FAILED:
242		default:
243			wpa_printf(MSG_DEBUG, "l2_packet_rx_thread: "
244				   "WaitForMultipleObjects failed: %d",
245				   (int) GetLastError());
246			run = 0;
247			break;
248		}
249	}
250
251	wpa_printf(MSG_DEBUG, "L2(NDISUIO): RX thread stopped");
252
253	return 0;
254}
255#else /* _WIN32_WCE */
256static int l2_ndisuio_start_read(struct l2_packet_data *l2, int recursive)
257{
258	os_memset(&l2->rx_overlapped, 0, sizeof(l2->rx_overlapped));
259	l2->rx_overlapped.hEvent = l2->rx_avail;
260	if (!ReadFile(driver_ndis_get_ndisuio_handle(), l2->rx_buf,
261		      sizeof(l2->rx_buf), &l2->rx_written, &l2->rx_overlapped))
262	{
263		DWORD err = GetLastError();
264		if (err != ERROR_IO_PENDING) {
265			wpa_printf(MSG_DEBUG, "L2(NDISUIO): ReadFile failed: "
266				   "%d", (int) err);
267			return -1;
268		}
269		/*
270		 * Once read is completed, l2_packet_rx_event() will be
271		 * called.
272		 */
273	} else {
274		wpa_printf(MSG_DEBUG, "L2(NDISUIO): ReadFile returned data "
275			   "without wait for completion");
276		if (!recursive)
277			l2_packet_callback(l2);
278	}
279
280	return 0;
281}
282#endif /* _WIN32_WCE */
283
284
285static void l2_packet_callback(struct l2_packet_data *l2)
286{
287	const u8 *rx_buf, *rx_src;
288	size_t rx_len;
289	struct l2_ethhdr *ethhdr = (struct l2_ethhdr *) l2->rx_buf;
290
291	wpa_printf(MSG_DEBUG, "L2(NDISUIO): Read %d bytes",
292		   (int) l2->rx_written);
293
294	if (l2->l2_hdr || l2->rx_written < sizeof(*ethhdr)) {
295		rx_buf = (u8 *) ethhdr;
296		rx_len = l2->rx_written;
297	} else {
298		rx_buf = (u8 *) (ethhdr + 1);
299		rx_len = l2->rx_written - sizeof(*ethhdr);
300	}
301	rx_src = ethhdr->h_source;
302
303	l2->rx_callback(l2->rx_callback_ctx, rx_src, rx_buf, rx_len);
304#ifndef _WIN32_WCE
305	l2_ndisuio_start_read(l2, 1);
306#endif /* _WIN32_WCE */
307}
308
309
310static void l2_packet_rx_event(void *eloop_data, void *user_data)
311{
312	struct l2_packet_data *l2 = eloop_data;
313
314	if (l2_ndisuio_global)
315		l2 = l2_ndisuio_global->l2[l2_ndisuio_global->refcount - 1];
316
317	ResetEvent(l2->rx_avail);
318
319#ifndef _WIN32_WCE
320	if (!GetOverlappedResult(driver_ndis_get_ndisuio_handle(),
321				 &l2->rx_overlapped, &l2->rx_written, FALSE)) {
322		wpa_printf(MSG_DEBUG, "L2(NDISUIO): GetOverlappedResult "
323			   "failed: %d", (int) GetLastError());
324		return;
325	}
326#endif /* _WIN32_WCE */
327
328	l2_packet_callback(l2);
329
330#ifdef _WIN32_WCE
331	SetEvent(l2_ndisuio_global->rx_processed);
332#endif /* _WIN32_WCE */
333}
334
335
336static int l2_ndisuio_set_ether_type(unsigned short protocol)
337{
338	USHORT proto = htons(protocol);
339	DWORD written;
340
341	if (!DeviceIoControl(driver_ndis_get_ndisuio_handle(),
342			     IOCTL_NDISUIO_SET_ETHER_TYPE, &proto,
343			     sizeof(proto), NULL, 0, &written, NULL)) {
344		wpa_printf(MSG_ERROR, "L2(NDISUIO): "
345			   "IOCTL_NDISUIO_SET_ETHER_TYPE failed: %d",
346			   (int) GetLastError());
347		return -1;
348	}
349
350	return 0;
351}
352
353
354struct l2_packet_data * l2_packet_init(
355	const char *ifname, const u8 *own_addr, unsigned short protocol,
356	void (*rx_callback)(void *ctx, const u8 *src_addr,
357			    const u8 *buf, size_t len),
358	void *rx_callback_ctx, int l2_hdr)
359{
360	struct l2_packet_data *l2;
361
362	if (l2_ndisuio_global == NULL) {
363		l2_ndisuio_global = os_zalloc(sizeof(*l2_ndisuio_global));
364		if (l2_ndisuio_global == NULL)
365			return NULL;
366		l2_ndisuio_global->first_proto = protocol;
367	}
368	if (l2_ndisuio_global->refcount >= 2) {
369		wpa_printf(MSG_ERROR, "L2(NDISUIO): Not more than two "
370			   "simultaneous connections allowed");
371		return NULL;
372	}
373	l2_ndisuio_global->refcount++;
374
375	l2 = os_zalloc(sizeof(struct l2_packet_data));
376	if (l2 == NULL)
377		return NULL;
378	l2_ndisuio_global->l2[l2_ndisuio_global->refcount - 1] = l2;
379
380	os_strlcpy(l2->ifname, ifname, sizeof(l2->ifname));
381	l2->rx_callback = rx_callback;
382	l2->rx_callback_ctx = rx_callback_ctx;
383	l2->l2_hdr = l2_hdr;
384
385	if (own_addr)
386		os_memcpy(l2->own_addr, own_addr, ETH_ALEN);
387
388	if (l2_ndisuio_set_ether_type(protocol) < 0) {
389		os_free(l2);
390		return NULL;
391	}
392
393	if (l2_ndisuio_global->refcount > 1) {
394		wpa_printf(MSG_DEBUG, "L2(NDISUIO): Temporarily setting "
395			   "filtering ethertype to %04x", protocol);
396		if (l2_ndisuio_global->l2[0])
397			l2->rx_avail = l2_ndisuio_global->l2[0]->rx_avail;
398		return l2;
399	}
400
401	l2->rx_avail = CreateEvent(NULL, TRUE, FALSE, NULL);
402	if (l2->rx_avail == NULL) {
403		os_free(l2);
404		return NULL;
405	}
406
407	eloop_register_event(l2->rx_avail, sizeof(l2->rx_avail),
408			     l2_packet_rx_event, l2, NULL);
409
410#ifdef _WIN32_WCE
411	l2_ndisuio_global->stop_request = CreateEvent(NULL, TRUE, FALSE, NULL);
412	/*
413	 * This event is being set based on media connect/disconnect
414	 * notifications in driver_ndis.c.
415	 */
416	l2_ndisuio_global->ready_for_read =
417		CreateEvent(NULL, TRUE, FALSE, TEXT("WpaSupplicantConnected"));
418	l2_ndisuio_global->rx_processed = CreateEvent(NULL, TRUE, FALSE, NULL);
419	if (l2_ndisuio_global->stop_request == NULL ||
420	    l2_ndisuio_global->ready_for_read == NULL ||
421	    l2_ndisuio_global->rx_processed == NULL) {
422		if (l2_ndisuio_global->stop_request) {
423			CloseHandle(l2_ndisuio_global->stop_request);
424			l2_ndisuio_global->stop_request = NULL;
425		}
426		if (l2_ndisuio_global->ready_for_read) {
427			CloseHandle(l2_ndisuio_global->ready_for_read);
428			l2_ndisuio_global->ready_for_read = NULL;
429		}
430		if (l2_ndisuio_global->rx_processed) {
431			CloseHandle(l2_ndisuio_global->rx_processed);
432			l2_ndisuio_global->rx_processed = NULL;
433		}
434		eloop_unregister_event(l2->rx_avail, sizeof(l2->rx_avail));
435		os_free(l2);
436		return NULL;
437	}
438
439	l2_ndisuio_global->rx_thread = CreateThread(NULL, 0,
440						    l2_packet_rx_thread, l2, 0,
441						    NULL);
442	if (l2_ndisuio_global->rx_thread == NULL) {
443		wpa_printf(MSG_INFO, "L2(NDISUIO): Failed to create RX "
444			   "thread: %d", (int) GetLastError());
445		eloop_unregister_event(l2->rx_avail, sizeof(l2->rx_avail));
446		CloseHandle(l2_ndisuio_global->stop_request);
447		l2_ndisuio_global->stop_request = NULL;
448		os_free(l2);
449		return NULL;
450	}
451#else /* _WIN32_WCE */
452	l2_ndisuio_start_read(l2, 0);
453#endif /* _WIN32_WCE */
454
455	return l2;
456}
457
458
459void l2_packet_deinit(struct l2_packet_data *l2)
460{
461	if (l2 == NULL)
462		return;
463
464	if (l2_ndisuio_global) {
465		l2_ndisuio_global->refcount--;
466		l2_ndisuio_global->l2[l2_ndisuio_global->refcount] = NULL;
467		if (l2_ndisuio_global->refcount) {
468			wpa_printf(MSG_DEBUG, "L2(NDISUIO): restore filtering "
469				   "ethertype to %04x",
470				   l2_ndisuio_global->first_proto);
471			l2_ndisuio_set_ether_type(
472				l2_ndisuio_global->first_proto);
473			return;
474		}
475
476#ifdef _WIN32_WCE
477		wpa_printf(MSG_DEBUG, "L2(NDISUIO): Waiting for RX thread to "
478			   "stop");
479		SetEvent(l2_ndisuio_global->stop_request);
480		/*
481		 * Cancel pending ReadFile() in the RX thread (if we were still
482		 * connected at this point).
483		 */
484		if (!DeviceIoControl(driver_ndis_get_ndisuio_handle(),
485				     IOCTL_CANCEL_READ, NULL, 0, NULL, 0, NULL,
486				     NULL)) {
487			wpa_printf(MSG_DEBUG, "L2(NDISUIO): IOCTL_CANCEL_READ "
488				   "failed: %d", (int) GetLastError());
489			/* RX thread will exit blocking ReadFile once NDISUIO
490			 * notices that the adapter is disconnected. */
491		}
492		WaitForSingleObject(l2_ndisuio_global->rx_thread, INFINITE);
493		wpa_printf(MSG_DEBUG, "L2(NDISUIO): RX thread exited");
494		CloseHandle(l2_ndisuio_global->rx_thread);
495		CloseHandle(l2_ndisuio_global->stop_request);
496		CloseHandle(l2_ndisuio_global->ready_for_read);
497		CloseHandle(l2_ndisuio_global->rx_processed);
498#endif /* _WIN32_WCE */
499
500		os_free(l2_ndisuio_global);
501		l2_ndisuio_global = NULL;
502	}
503
504#ifndef _WIN32_WCE
505	CancelIo(driver_ndis_get_ndisuio_handle());
506#endif /* _WIN32_WCE */
507
508	eloop_unregister_event(l2->rx_avail, sizeof(l2->rx_avail));
509	CloseHandle(l2->rx_avail);
510	os_free(l2);
511}
512
513
514int l2_packet_get_ip_addr(struct l2_packet_data *l2, char *buf, size_t len)
515{
516	return -1;
517}
518
519
520void l2_packet_notify_auth_start(struct l2_packet_data *l2)
521{
522}
523