xref: /freebsd/contrib/wpa/src/l2_packet/l2_packet_ndis.c (revision 884a2a699669ec61e2366e3e358342dbc94be24a)
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 */
48 HANDLE 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 
57 struct l2_packet_data;
58 
59 struct 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 
71 static struct l2_packet_ndisuio_global *l2_ndisuio_global = NULL;
72 
73 struct 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 
90 int 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 
97 int 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 
163 static void l2_packet_callback(struct l2_packet_data *l2);
164 
165 #ifdef _WIN32_WCE
166 static 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 
202 static 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 */
256 static 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 
285 static 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 
310 static 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 
336 static 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 
354 struct 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 
459 void 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 
514 int l2_packet_get_ip_addr(struct l2_packet_data *l2, char *buf, size_t len)
515 {
516 	return -1;
517 }
518 
519 
520 void l2_packet_notify_auth_start(struct l2_packet_data *l2)
521 {
522 }
523