xref: /freebsd/contrib/wpa/src/l2_packet/l2_packet_ndis.c (revision c1d255d3ffdbe447de3ab875bf4e7d7accc5bfc5)
1 /*
2  * WPA Supplicant - Layer2 packet handling with Microsoft NDISUIO
3  * Copyright (c) 2003-2006, Jouni Malinen <j@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 
l2_packet_get_own_addr(struct l2_packet_data * l2,u8 * addr)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 
l2_packet_send(struct l2_packet_data * l2,const u8 * dst_addr,u16 proto,const u8 * buf,size_t len)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
l2_packet_rx_thread_try_read(struct l2_packet_data * l2)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 
l2_packet_rx_thread(LPVOID arg)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 */
l2_ndisuio_start_read(struct l2_packet_data * l2,int recursive)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 
l2_packet_callback(struct l2_packet_data * l2)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 	if (l2->rx_callback)
298 		l2->rx_callback(l2->rx_callback_ctx, rx_src, rx_buf, rx_len);
299 #ifndef _WIN32_WCE
300 	l2_ndisuio_start_read(l2, 1);
301 #endif /* _WIN32_WCE */
302 }
303 
304 
l2_packet_rx_event(void * eloop_data,void * user_data)305 static void l2_packet_rx_event(void *eloop_data, void *user_data)
306 {
307 	struct l2_packet_data *l2 = eloop_data;
308 
309 	if (l2_ndisuio_global)
310 		l2 = l2_ndisuio_global->l2[l2_ndisuio_global->refcount - 1];
311 
312 	ResetEvent(l2->rx_avail);
313 
314 #ifndef _WIN32_WCE
315 	if (!GetOverlappedResult(driver_ndis_get_ndisuio_handle(),
316 				 &l2->rx_overlapped, &l2->rx_written, FALSE)) {
317 		wpa_printf(MSG_DEBUG, "L2(NDISUIO): GetOverlappedResult "
318 			   "failed: %d", (int) GetLastError());
319 		return;
320 	}
321 #endif /* _WIN32_WCE */
322 
323 	l2_packet_callback(l2);
324 
325 #ifdef _WIN32_WCE
326 	SetEvent(l2_ndisuio_global->rx_processed);
327 #endif /* _WIN32_WCE */
328 }
329 
330 
l2_ndisuio_set_ether_type(unsigned short protocol)331 static int l2_ndisuio_set_ether_type(unsigned short protocol)
332 {
333 	USHORT proto = htons(protocol);
334 	DWORD written;
335 
336 	if (!DeviceIoControl(driver_ndis_get_ndisuio_handle(),
337 			     IOCTL_NDISUIO_SET_ETHER_TYPE, &proto,
338 			     sizeof(proto), NULL, 0, &written, NULL)) {
339 		wpa_printf(MSG_ERROR, "L2(NDISUIO): "
340 			   "IOCTL_NDISUIO_SET_ETHER_TYPE failed: %d",
341 			   (int) GetLastError());
342 		return -1;
343 	}
344 
345 	return 0;
346 }
347 
348 
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)349 struct l2_packet_data * l2_packet_init(
350 	const char *ifname, const u8 *own_addr, unsigned short protocol,
351 	void (*rx_callback)(void *ctx, const u8 *src_addr,
352 			    const u8 *buf, size_t len),
353 	void *rx_callback_ctx, int l2_hdr)
354 {
355 	struct l2_packet_data *l2;
356 
357 	if (l2_ndisuio_global == NULL) {
358 		l2_ndisuio_global = os_zalloc(sizeof(*l2_ndisuio_global));
359 		if (l2_ndisuio_global == NULL)
360 			return NULL;
361 		l2_ndisuio_global->first_proto = protocol;
362 	}
363 	if (l2_ndisuio_global->refcount >= 2) {
364 		wpa_printf(MSG_ERROR, "L2(NDISUIO): Not more than two "
365 			   "simultaneous connections allowed");
366 		return NULL;
367 	}
368 	l2_ndisuio_global->refcount++;
369 
370 	l2 = os_zalloc(sizeof(struct l2_packet_data));
371 	if (l2 == NULL)
372 		return NULL;
373 	l2_ndisuio_global->l2[l2_ndisuio_global->refcount - 1] = l2;
374 
375 	os_strlcpy(l2->ifname, ifname, sizeof(l2->ifname));
376 	l2->rx_callback = rx_callback;
377 	l2->rx_callback_ctx = rx_callback_ctx;
378 	l2->l2_hdr = l2_hdr;
379 
380 	if (own_addr)
381 		os_memcpy(l2->own_addr, own_addr, ETH_ALEN);
382 
383 	if (l2_ndisuio_set_ether_type(protocol) < 0) {
384 		os_free(l2);
385 		return NULL;
386 	}
387 
388 	if (l2_ndisuio_global->refcount > 1) {
389 		wpa_printf(MSG_DEBUG, "L2(NDISUIO): Temporarily setting "
390 			   "filtering ethertype to %04x", protocol);
391 		if (l2_ndisuio_global->l2[0])
392 			l2->rx_avail = l2_ndisuio_global->l2[0]->rx_avail;
393 		return l2;
394 	}
395 
396 	l2->rx_avail = CreateEvent(NULL, TRUE, FALSE, NULL);
397 	if (l2->rx_avail == NULL) {
398 		os_free(l2);
399 		return NULL;
400 	}
401 
402 	eloop_register_event(l2->rx_avail, sizeof(l2->rx_avail),
403 			     l2_packet_rx_event, l2, NULL);
404 
405 #ifdef _WIN32_WCE
406 	l2_ndisuio_global->stop_request = CreateEvent(NULL, TRUE, FALSE, NULL);
407 	/*
408 	 * This event is being set based on media connect/disconnect
409 	 * notifications in driver_ndis.c.
410 	 */
411 	l2_ndisuio_global->ready_for_read =
412 		CreateEvent(NULL, TRUE, FALSE, TEXT("WpaSupplicantConnected"));
413 	l2_ndisuio_global->rx_processed = CreateEvent(NULL, TRUE, FALSE, NULL);
414 	if (l2_ndisuio_global->stop_request == NULL ||
415 	    l2_ndisuio_global->ready_for_read == NULL ||
416 	    l2_ndisuio_global->rx_processed == NULL) {
417 		if (l2_ndisuio_global->stop_request) {
418 			CloseHandle(l2_ndisuio_global->stop_request);
419 			l2_ndisuio_global->stop_request = NULL;
420 		}
421 		if (l2_ndisuio_global->ready_for_read) {
422 			CloseHandle(l2_ndisuio_global->ready_for_read);
423 			l2_ndisuio_global->ready_for_read = NULL;
424 		}
425 		if (l2_ndisuio_global->rx_processed) {
426 			CloseHandle(l2_ndisuio_global->rx_processed);
427 			l2_ndisuio_global->rx_processed = NULL;
428 		}
429 		eloop_unregister_event(l2->rx_avail, sizeof(l2->rx_avail));
430 		os_free(l2);
431 		return NULL;
432 	}
433 
434 	l2_ndisuio_global->rx_thread = CreateThread(NULL, 0,
435 						    l2_packet_rx_thread, l2, 0,
436 						    NULL);
437 	if (l2_ndisuio_global->rx_thread == NULL) {
438 		wpa_printf(MSG_INFO, "L2(NDISUIO): Failed to create RX "
439 			   "thread: %d", (int) GetLastError());
440 		eloop_unregister_event(l2->rx_avail, sizeof(l2->rx_avail));
441 		CloseHandle(l2_ndisuio_global->stop_request);
442 		l2_ndisuio_global->stop_request = NULL;
443 		os_free(l2);
444 		return NULL;
445 	}
446 #else /* _WIN32_WCE */
447 	l2_ndisuio_start_read(l2, 0);
448 #endif /* _WIN32_WCE */
449 
450 	return l2;
451 }
452 
453 
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)454 struct l2_packet_data * l2_packet_init_bridge(
455 	const char *br_ifname, const char *ifname, const u8 *own_addr,
456 	unsigned short protocol,
457 	void (*rx_callback)(void *ctx, const u8 *src_addr,
458 			    const u8 *buf, size_t len),
459 	void *rx_callback_ctx, int l2_hdr)
460 {
461 	return l2_packet_init(br_ifname, own_addr, protocol, rx_callback,
462 			      rx_callback_ctx, l2_hdr);
463 }
464 
465 
l2_packet_deinit(struct l2_packet_data * l2)466 void l2_packet_deinit(struct l2_packet_data *l2)
467 {
468 	if (l2 == NULL)
469 		return;
470 
471 	if (l2_ndisuio_global) {
472 		l2_ndisuio_global->refcount--;
473 		l2_ndisuio_global->l2[l2_ndisuio_global->refcount] = NULL;
474 		if (l2_ndisuio_global->refcount) {
475 			wpa_printf(MSG_DEBUG, "L2(NDISUIO): restore filtering "
476 				   "ethertype to %04x",
477 				   l2_ndisuio_global->first_proto);
478 			l2_ndisuio_set_ether_type(
479 				l2_ndisuio_global->first_proto);
480 			return;
481 		}
482 
483 #ifdef _WIN32_WCE
484 		wpa_printf(MSG_DEBUG, "L2(NDISUIO): Waiting for RX thread to "
485 			   "stop");
486 		SetEvent(l2_ndisuio_global->stop_request);
487 		/*
488 		 * Cancel pending ReadFile() in the RX thread (if we were still
489 		 * connected at this point).
490 		 */
491 		if (!DeviceIoControl(driver_ndis_get_ndisuio_handle(),
492 				     IOCTL_CANCEL_READ, NULL, 0, NULL, 0, NULL,
493 				     NULL)) {
494 			wpa_printf(MSG_DEBUG, "L2(NDISUIO): IOCTL_CANCEL_READ "
495 				   "failed: %d", (int) GetLastError());
496 			/* RX thread will exit blocking ReadFile once NDISUIO
497 			 * notices that the adapter is disconnected. */
498 		}
499 		WaitForSingleObject(l2_ndisuio_global->rx_thread, INFINITE);
500 		wpa_printf(MSG_DEBUG, "L2(NDISUIO): RX thread exited");
501 		CloseHandle(l2_ndisuio_global->rx_thread);
502 		CloseHandle(l2_ndisuio_global->stop_request);
503 		CloseHandle(l2_ndisuio_global->ready_for_read);
504 		CloseHandle(l2_ndisuio_global->rx_processed);
505 #endif /* _WIN32_WCE */
506 
507 		os_free(l2_ndisuio_global);
508 		l2_ndisuio_global = NULL;
509 	}
510 
511 #ifndef _WIN32_WCE
512 	CancelIo(driver_ndis_get_ndisuio_handle());
513 #endif /* _WIN32_WCE */
514 
515 	eloop_unregister_event(l2->rx_avail, sizeof(l2->rx_avail));
516 	CloseHandle(l2->rx_avail);
517 	os_free(l2);
518 }
519 
520 
l2_packet_get_ip_addr(struct l2_packet_data * l2,char * buf,size_t len)521 int l2_packet_get_ip_addr(struct l2_packet_data *l2, char *buf, size_t len)
522 {
523 	return -1;
524 }
525 
526 
l2_packet_notify_auth_start(struct l2_packet_data * l2)527 void l2_packet_notify_auth_start(struct l2_packet_data *l2)
528 {
529 }
530 
531 
l2_packet_set_packet_filter(struct l2_packet_data * l2,enum l2_packet_filter_type type)532 int l2_packet_set_packet_filter(struct l2_packet_data *l2,
533 				enum l2_packet_filter_type type)
534 {
535 	return -1;
536 }
537