l2_packet_ndis.c revision 209158
1178476Sjb/*
2178476Sjb * WPA Supplicant - Layer2 packet handling with Microsoft NDISUIO
3178476Sjb * Copyright (c) 2003-2006, Jouni Malinen <j@w1.fi>
4178476Sjb *
5178476Sjb * This program is free software; you can redistribute it and/or modify
6178476Sjb * it under the terms of the GNU General Public License version 2 as
7178476Sjb * published by the Free Software Foundation.
8178476Sjb *
9178476Sjb * Alternatively, this software may be distributed under the terms of BSD
10178476Sjb * license.
11178476Sjb *
12178476Sjb * See README and COPYING for more details.
13178476Sjb *
14178476Sjb * This implementation requires Windows specific event loop implementation,
15178476Sjb * i.e., eloop_win.c. In addition, the NDISUIO connection is shared with
16178476Sjb * driver_ndis.c, so only that driver interface can be used and
17178476Sjb * CONFIG_USE_NDISUIO must be defined.
18178476Sjb *
19178476Sjb * WinXP version of the code uses overlapped I/O and a single threaded design
20178476Sjb * with callback functions from I/O code. WinCE version uses a separate RX
21178476Sjb * thread that blocks on ReadFile() whenever the media status is connected.
22178476Sjb */
23178476Sjb
24178476Sjb#include "includes.h"
25178476Sjb#include <winsock2.h>
26178476Sjb#include <ntddndis.h>
27178476Sjb
28178476Sjb#ifdef _WIN32_WCE
29178476Sjb#include <winioctl.h>
30178476Sjb#include <nuiouser.h>
31178476Sjb#endif /* _WIN32_WCE */
32178476Sjb
33178476Sjb#include "common.h"
34178476Sjb#include "eloop.h"
35178476Sjb#include "l2_packet.h"
36178476Sjb
37178476Sjb#ifndef _WIN32_WCE
38178476Sjb/* from nuiouser.h */
39178476Sjb#define FSCTL_NDISUIO_BASE      FILE_DEVICE_NETWORK
40178476Sjb#define _NDISUIO_CTL_CODE(_Function, _Method, _Access) \
41178476Sjb	CTL_CODE(FSCTL_NDISUIO_BASE, _Function, _Method, _Access)
42178476Sjb#define IOCTL_NDISUIO_SET_ETHER_TYPE \
43178476Sjb	_NDISUIO_CTL_CODE(0x202, METHOD_BUFFERED, \
44178476Sjb			  FILE_READ_ACCESS | FILE_WRITE_ACCESS)
45178476Sjb#endif /* _WIN32_WCE */
46178476Sjb
47178476Sjb/* From driver_ndis.c to shared the handle to NDISUIO */
48178476SjbHANDLE driver_ndis_get_ndisuio_handle(void);
49178476Sjb
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			/* For now, just assume that the packet will be sent in
141			 * time before the next write happens. This could be
142			 * cleaned up at some point to actually wait for
143			 * completion before starting new writes.
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
157static void l2_packet_callback(struct l2_packet_data *l2);
158
159#ifdef _WIN32_WCE
160static 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
196static 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 */
250static 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
279static 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
304static 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
330static 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
348struct 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
453void l2_packet_deinit(struct l2_packet_data *l2)
454{
455	if (l2 == NULL)
456		return;
457
458	if (l2_ndisuio_global) {
459		l2_ndisuio_global->refcount--;
460		l2_ndisuio_global->l2[l2_ndisuio_global->refcount] = NULL;
461		if (l2_ndisuio_global->refcount) {
462			wpa_printf(MSG_DEBUG, "L2(NDISUIO): restore filtering "
463				   "ethertype to %04x",
464				   l2_ndisuio_global->first_proto);
465			l2_ndisuio_set_ether_type(
466				l2_ndisuio_global->first_proto);
467			return;
468		}
469
470#ifdef _WIN32_WCE
471		wpa_printf(MSG_DEBUG, "L2(NDISUIO): Waiting for RX thread to "
472			   "stop");
473		SetEvent(l2_ndisuio_global->stop_request);
474		/*
475		 * Cancel pending ReadFile() in the RX thread (if we were still
476		 * connected at this point).
477		 */
478		if (!DeviceIoControl(driver_ndis_get_ndisuio_handle(),
479				     IOCTL_CANCEL_READ, NULL, 0, NULL, 0, NULL,
480				     NULL)) {
481			wpa_printf(MSG_DEBUG, "L2(NDISUIO): IOCTL_CANCEL_READ "
482				   "failed: %d", (int) GetLastError());
483			/* RX thread will exit blocking ReadFile once NDISUIO
484			 * notices that the adapter is disconnected. */
485		}
486		WaitForSingleObject(l2_ndisuio_global->rx_thread, INFINITE);
487		wpa_printf(MSG_DEBUG, "L2(NDISUIO): RX thread exited");
488		CloseHandle(l2_ndisuio_global->rx_thread);
489		CloseHandle(l2_ndisuio_global->stop_request);
490		CloseHandle(l2_ndisuio_global->ready_for_read);
491		CloseHandle(l2_ndisuio_global->rx_processed);
492#endif /* _WIN32_WCE */
493
494		os_free(l2_ndisuio_global);
495		l2_ndisuio_global = NULL;
496	}
497
498#ifndef _WIN32_WCE
499	CancelIo(driver_ndis_get_ndisuio_handle());
500#endif /* _WIN32_WCE */
501
502	eloop_unregister_event(l2->rx_avail, sizeof(l2->rx_avail));
503	CloseHandle(l2->rx_avail);
504	os_free(l2);
505}
506
507
508int l2_packet_get_ip_addr(struct l2_packet_data *l2, char *buf, size_t len)
509{
510	return -1;
511}
512
513
514void l2_packet_notify_auth_start(struct l2_packet_data *l2)
515{
516}
517