xref: /openbsd/usr.sbin/hostapd/hostapd.c (revision 8529ddd3)
1 /*	$OpenBSD: hostapd.c,v 1.35 2015/01/16 06:40:17 deraadt Exp $	*/
2 
3 /*
4  * Copyright (c) 2004, 2005 Reyk Floeter <reyk@openbsd.org>
5  * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <sys/ioctl.h>
21 #include <sys/types.h>
22 #include <sys/signal.h>
23 #include <sys/socket.h>
24 #include <sys/time.h>
25 #include <sys/queue.h>
26 #include <sys/stat.h>
27 
28 #include <net/if.h>
29 #include <net/if_dl.h>
30 #include <net/if_media.h>
31 #include <net/if_arp.h>
32 #include <net/if_llc.h>
33 #include <net/bpf.h>
34 
35 #include <netinet/in.h>
36 #include <netinet/if_ether.h>
37 #include <arpa/inet.h>
38 
39 #include <errno.h>
40 #include <event.h>
41 #include <fcntl.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <stdarg.h>
45 #include <string.h>
46 #include <unistd.h>
47 #include <limits.h>
48 #include <err.h>
49 
50 #include "hostapd.h"
51 #include "iapp.h"
52 
53 void	 hostapd_usage(void);
54 void	 hostapd_udp_init(struct hostapd_config *);
55 void	 hostapd_sig_handler(int, short, void *);
56 static __inline int
57 	 hostapd_entry_cmp(struct hostapd_entry *, struct hostapd_entry *);
58 
59 struct hostapd_config hostapd_cfg;
60 
61 extern char *__progname;
62 char printbuf[BUFSIZ];
63 
64 void
65 hostapd_usage(void)
66 {
67 	fprintf(stderr, "usage: %s [-dv] [-D macro=value] [-f file]\n",
68 	    __progname);
69 	exit(EXIT_FAILURE);
70 }
71 
72 void
73 hostapd_log(u_int level, const char *fmt, ...)
74 {
75 	char *nfmt = NULL;
76 	va_list ap;
77 
78 	if (level > hostapd_cfg.c_verbose)
79 		return;
80 
81 	va_start(ap, fmt);
82 	if (hostapd_cfg.c_debug) {
83 		if (asprintf(&nfmt, "%s\n", fmt) != -1)
84 			vfprintf(stderr, nfmt, ap);
85 		else {
86 			vfprintf(stderr, fmt, ap);
87 			fprintf(stderr, "\n");
88 		}
89 		fflush(stderr);
90 	} else
91 		vsyslog(LOG_INFO, fmt, ap);
92 	va_end(ap);
93 
94 	if (nfmt != NULL)
95 		free(nfmt);
96 }
97 
98 void
99 hostapd_printf(const char *fmt, ...)
100 {
101 	char newfmt[BUFSIZ];
102 	va_list ap;
103 	size_t n;
104 
105 	if (fmt == NULL)
106 		goto flush;
107 
108 	va_start(ap, fmt);
109 	bzero(newfmt, sizeof(newfmt));
110 	if ((n = strlcpy(newfmt, printbuf, sizeof(newfmt))) >= sizeof(newfmt))
111 		goto va_flush;
112 	if (strlcpy(newfmt + n, fmt, sizeof(newfmt) - n) >= sizeof(newfmt) - n)
113 		goto va_flush;
114 	if (vsnprintf(printbuf, sizeof(printbuf), newfmt, ap) == -1)
115 		goto va_flush;
116 	va_end(ap);
117 
118 	return;
119 
120  va_flush:
121 	va_end(ap);
122  flush:
123 	if (strlen(printbuf))
124 		hostapd_log(HOSTAPD_LOG, "%s", printbuf);
125 	bzero(printbuf, sizeof(printbuf));
126 }
127 
128 void
129 hostapd_fatal(const char *fmt, ...)
130 {
131 	va_list ap;
132 
133 	va_start(ap, fmt);
134 	if (hostapd_cfg.c_debug) {
135 		vfprintf(stderr, fmt, ap);
136 		fflush(stderr);
137 	} else
138 		vsyslog(LOG_ERR, fmt, ap);
139 	va_end(ap);
140 
141 	hostapd_cleanup(&hostapd_cfg);
142 	exit(EXIT_FAILURE);
143 }
144 
145 int
146 hostapd_check_file_secrecy(int fd, const char *fname)
147 {
148 	struct stat st;
149 
150 	if (fstat(fd, &st)) {
151 		hostapd_log(HOSTAPD_LOG,
152 		    "cannot stat %s", fname);
153 		return (-1);
154 	}
155 
156 	if (st.st_uid != 0 && st.st_uid != getuid()) {
157 		hostapd_log(HOSTAPD_LOG,
158 		    "%s: owner not root or current user", fname);
159 		return (-1);
160 	}
161 
162 	if (st.st_mode & (S_IRWXG | S_IRWXO)) {
163 		hostapd_log(HOSTAPD_LOG,
164 		    "%s: group/world readable/writeable", fname);
165 		return (-1);
166 	}
167 
168 	return (0);
169 }
170 
171 int
172 hostapd_bpf_open(u_int flags)
173 {
174 	u_int i;
175 	int fd = -1;
176 	char *dev;
177 	struct bpf_version bpv;
178 
179 	/*
180 	 * Try to open the next available BPF device
181 	 */
182 	for (i = 0; i < 255; i++) {
183 		if (asprintf(&dev, "/dev/bpf%u", i) == -1)
184 			hostapd_fatal("failed to allocate buffer\n");
185 
186 		if ((fd = open(dev, flags)) != -1) {
187 			free(dev);
188 			break;
189 		}
190 
191 		free(dev);
192 	}
193 
194 	if (fd == -1)
195 		hostapd_fatal("unable to open BPF device\n");
196 
197 	/*
198 	 * Get and validate the BPF version
199 	 */
200 
201 	if (ioctl(fd, BIOCVERSION, &bpv) == -1)
202 		hostapd_fatal("failed to get BPF version: %s\n",
203 		    strerror(errno));
204 
205 	if (bpv.bv_major != BPF_MAJOR_VERSION ||
206 	    bpv.bv_minor < BPF_MINOR_VERSION)
207 		hostapd_fatal("invalid BPF version\n");
208 
209 	return (fd);
210 }
211 
212 void
213 hostapd_udp_init(struct hostapd_config *cfg)
214 {
215 	struct hostapd_iapp *iapp = &cfg->c_iapp;
216 	struct ifreq ifr;
217 	struct sockaddr_in *addr, baddr;
218 	struct ip_mreq mreq;
219 	int brd = 1;
220 
221 	bzero(&ifr, sizeof(ifr));
222 
223 	/*
224 	 * Open a listening UDP socket
225 	 */
226 
227 	if ((iapp->i_udp = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
228 		hostapd_fatal("unable to open udp socket\n");
229 
230 	cfg->c_flags |= HOSTAPD_CFG_F_UDP;
231 
232 	(void)strlcpy(ifr.ifr_name, iapp->i_iface, sizeof(ifr.ifr_name));
233 
234 	if (ioctl(iapp->i_udp, SIOCGIFADDR, &ifr) == -1)
235 		hostapd_fatal("UDP ioctl %s on \"%s\" failed: %s\n",
236 		    "SIOCGIFADDR", ifr.ifr_name, strerror(errno));
237 
238 	addr = (struct sockaddr_in *)&ifr.ifr_addr;
239 	iapp->i_addr.sin_family = AF_INET;
240 	iapp->i_addr.sin_addr.s_addr = addr->sin_addr.s_addr;
241 	if (iapp->i_addr.sin_port == 0)
242 		iapp->i_addr.sin_port = htons(IAPP_PORT);
243 
244 	if (ioctl(iapp->i_udp, SIOCGIFBRDADDR, &ifr) == -1)
245 		hostapd_fatal("UDP ioctl %s on \"%s\" failed: %s\n",
246 		    "SIOCGIFBRDADDR", ifr.ifr_name, strerror(errno));
247 
248 	addr = (struct sockaddr_in *)&ifr.ifr_addr;
249 	iapp->i_broadcast.sin_family = AF_INET;
250 	iapp->i_broadcast.sin_addr.s_addr = addr->sin_addr.s_addr;
251 	iapp->i_broadcast.sin_port = iapp->i_addr.sin_port;
252 
253 	baddr.sin_family = AF_INET;
254 	baddr.sin_addr.s_addr = htonl(INADDR_ANY);
255 	baddr.sin_port = iapp->i_addr.sin_port;
256 
257 	if (bind(iapp->i_udp, (struct sockaddr *)&baddr,
258 	    sizeof(baddr)) == -1)
259 		hostapd_fatal("failed to bind UDP socket: %s\n",
260 		    strerror(errno));
261 
262 	/*
263 	 * The revised 802.11F standard requires IAPP messages to be
264 	 * sent via multicast to the default group 224.0.1.178.
265 	 * Nevertheless, some implementations still use broadcasts
266 	 * for IAPP messages.
267 	 */
268 	if (cfg->c_flags & HOSTAPD_CFG_F_BRDCAST) {
269 		/*
270 		 * Enable broadcast
271 		 */
272 		if (setsockopt(iapp->i_udp, SOL_SOCKET, SO_BROADCAST,
273 		    &brd, sizeof(brd)) == -1)
274 			hostapd_fatal("failed to enable broadcast on socket\n");
275 
276 		hostapd_log(HOSTAPD_LOG_DEBUG, "%s: using broadcast mode "
277 		    "(address %s)", iapp->i_iface, inet_ntoa(addr->sin_addr));
278 	} else {
279 		/*
280 		 * Enable multicast
281 		 */
282 		bzero(&mreq, sizeof(mreq));
283 
284 		iapp->i_multicast.sin_family = AF_INET;
285 		if (iapp->i_multicast.sin_addr.s_addr == INADDR_ANY)
286 			iapp->i_multicast.sin_addr.s_addr =
287 			    inet_addr(IAPP_MCASTADDR);
288 		iapp->i_multicast.sin_port = iapp->i_addr.sin_port;
289 
290 		mreq.imr_multiaddr.s_addr =
291 		    iapp->i_multicast.sin_addr.s_addr;
292 		mreq.imr_interface.s_addr =
293 		    iapp->i_addr.sin_addr.s_addr;
294 
295 		if (setsockopt(iapp->i_udp, IPPROTO_IP,
296 		    IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)
297 			hostapd_fatal("failed to add multicast membership to "
298 			    "%s: %s\n", IAPP_MCASTADDR, strerror(errno));
299 
300 		if (setsockopt(iapp->i_udp, IPPROTO_IP, IP_MULTICAST_TTL,
301 		    &iapp->i_ttl, sizeof(iapp->i_ttl)) < 0)
302 			hostapd_fatal("failed to set multicast ttl to "
303 			    "%u: %s\n", iapp->i_ttl, strerror(errno));
304 
305 		hostapd_log(HOSTAPD_LOG_DEBUG, "%s: using multicast mode "
306 		    "(ttl %u, group %s)", iapp->i_iface, iapp->i_ttl,
307 		    inet_ntoa(iapp->i_multicast.sin_addr));
308 	}
309 }
310 
311 /* ARGSUSED */
312 void
313 hostapd_sig_handler(int sig, short event, void *arg)
314 {
315 	switch (sig) {
316 	case SIGALRM:
317 	case SIGTERM:
318 	case SIGQUIT:
319 	case SIGINT:
320 		(void)event_loopexit(NULL);
321 	}
322 }
323 
324 void
325 hostapd_cleanup(struct hostapd_config *cfg)
326 {
327 	struct hostapd_iapp *iapp = &cfg->c_iapp;
328 	struct ip_mreq mreq;
329 	struct hostapd_apme *apme;
330 	struct hostapd_table *table;
331 	struct hostapd_entry *entry;
332 
333 	/* Release all Host APs */
334 	if (cfg->c_flags & HOSTAPD_CFG_F_APME) {
335 		while ((apme = TAILQ_FIRST(&cfg->c_apmes)) != NULL)
336 			hostapd_apme_term(apme);
337 	}
338 
339 	if (cfg->c_flags & HOSTAPD_CFG_F_PRIV &&
340 	    (cfg->c_flags & HOSTAPD_CFG_F_BRDCAST) == 0) {
341 		/*
342 		 * Disable multicast and let the kernel unsubscribe
343 		 * from the multicast group.
344 		 */
345 
346 		bzero(&mreq, sizeof(mreq));
347 
348 		mreq.imr_multiaddr.s_addr =
349 		    inet_addr(IAPP_MCASTADDR);
350 		mreq.imr_interface.s_addr =
351 		    iapp->i_addr.sin_addr.s_addr;
352 
353 		if (setsockopt(iapp->i_udp, IPPROTO_IP,
354 		    IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)
355 			hostapd_log(HOSTAPD_LOG, "failed to remove multicast"
356 			    " membership to %s: %s",
357 			    IAPP_MCASTADDR, strerror(errno));
358 	}
359 
360 	if ((cfg->c_flags & HOSTAPD_CFG_F_PRIV) == 0 &&
361 	    cfg->c_flags & HOSTAPD_CFG_F_APME) {
362 		/* Shutdown the Host AP protocol handler */
363 		hostapd_iapp_term(&hostapd_cfg);
364 	}
365 
366 	/* Cleanup tables */
367 	while ((table = TAILQ_FIRST(&cfg->c_tables)) != NULL) {
368 		while ((entry = RB_MIN(hostapd_tree, &table->t_tree)) != NULL) {
369 			RB_REMOVE(hostapd_tree, &table->t_tree, entry);
370 			free(entry);
371 		}
372 		while ((entry = TAILQ_FIRST(&table->t_mask_head)) != NULL) {
373 			TAILQ_REMOVE(&table->t_mask_head, entry, e_entries);
374 			free(entry);
375 		}
376 		TAILQ_REMOVE(&cfg->c_tables, table, t_entries);
377 		free(table);
378 	}
379 
380 	hostapd_log(HOSTAPD_LOG_VERBOSE, "bye!");
381 }
382 
383 int
384 main(int argc, char *argv[])
385 {
386 	struct event ev_sigalrm;
387 	struct event ev_sigterm;
388 	struct event ev_sigquit;
389 	struct event ev_sigint;
390 	struct hostapd_config *cfg = &hostapd_cfg;
391 	struct hostapd_iapp *iapp;
392 	struct hostapd_apme *apme;
393 	char *config = NULL;
394 	u_int debug = 0, ret;
395 	int ch;
396 
397 	/* Set startup logging */
398 	cfg->c_debug = 1;
399 
400 	/*
401 	 * Get and parse command line options
402 	 */
403 	while ((ch = getopt(argc, argv, "f:D:dv")) != -1) {
404 		switch (ch) {
405 		case 'f':
406 			config = optarg;
407 			break;
408 		case 'D':
409 			if (hostapd_parse_symset(optarg) < 0)
410 				hostapd_fatal("could not parse macro "
411 				    "definition %s\n", optarg);
412 			break;
413 		case 'd':
414 			debug++;
415 			break;
416 		case 'v':
417 			cfg->c_verbose++;
418 			break;
419 		default:
420 			hostapd_usage();
421 		}
422 	}
423 
424 	argc -= optind;
425 	argv += optind;
426 	if (argc > 0)
427 		hostapd_usage();
428 
429 	if (config == NULL)
430 		ret = strlcpy(cfg->c_config, HOSTAPD_CONFIG, sizeof(cfg->c_config));
431 	else
432 		ret = strlcpy(cfg->c_config, config, sizeof(cfg->c_config));
433 	if (ret >= sizeof(cfg->c_config))
434 		hostapd_fatal("invalid configuration file\n");
435 
436 	if (geteuid())
437 		hostapd_fatal("need root privileges\n");
438 
439 	/* Parse the configuration file */
440 	if (hostapd_parse_file(cfg) != 0)
441 		hostapd_fatal("invalid configuration in %s\n", cfg->c_config);
442 
443 	iapp = &cfg->c_iapp;
444 
445 	if ((cfg->c_flags & HOSTAPD_CFG_F_IAPP) == 0)
446 		hostapd_fatal("IAPP interface not specified\n");
447 
448 	if (cfg->c_apme_dlt == 0)
449 		cfg->c_apme_dlt = HOSTAPD_DLT;
450 
451 	/*
452 	 * Setup the hostapd handlers
453 	 */
454 	hostapd_udp_init(cfg);
455 	hostapd_llc_init(cfg);
456 
457 	/*
458 	 * Set runtime logging and detach as daemon
459 	 */
460 	if ((cfg->c_debug = debug) == 0) {
461 		openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);
462 		tzset();
463 		if (daemon(0, 0) == -1)
464 			hostapd_fatal("failed to daemonize\n");
465 	}
466 
467 	if (cfg->c_flags & HOSTAPD_CFG_F_APME) {
468 		TAILQ_FOREACH(apme, &cfg->c_apmes, a_entries)
469 			hostapd_apme_init(apme);
470 	} else
471 		hostapd_log(HOSTAPD_LOG, "%s: running without a Host AP",
472 		    iapp->i_iface);
473 
474 	/* Drop all privileges in an unprivileged child process */
475 	hostapd_priv_init(cfg);
476 
477 	if (cfg->c_flags & HOSTAPD_CFG_F_APME)
478 		setproctitle("IAPP: %s, Host AP", iapp->i_iface);
479 	else
480 		setproctitle("IAPP: %s", iapp->i_iface);
481 
482 	/*
483 	 * Unprivileged child process
484 	 */
485 
486 	(void)event_init();
487 
488 	/*
489 	 * Set signal handlers
490 	 */
491 	signal_set(&ev_sigalrm, SIGALRM, hostapd_sig_handler, NULL);
492 	signal_set(&ev_sigterm, SIGTERM, hostapd_sig_handler, NULL);
493 	signal_set(&ev_sigquit, SIGQUIT, hostapd_sig_handler, NULL);
494 	signal_set(&ev_sigint, SIGINT, hostapd_sig_handler, NULL);
495 	signal_add(&ev_sigalrm, NULL);
496 	signal_add(&ev_sigterm, NULL);
497 	signal_add(&ev_sigquit, NULL);
498 	signal_add(&ev_sigint, NULL);
499 	signal(SIGHUP, SIG_IGN);
500 	signal(SIGCHLD, SIG_IGN);
501 
502 	/* Initialize the IAPP protocol handler */
503 	hostapd_iapp_init(cfg);
504 
505 	/*
506 	 * Schedule the Host AP listener
507 	 */
508 	if (cfg->c_flags & HOSTAPD_CFG_F_APME) {
509 		TAILQ_FOREACH(apme, &cfg->c_apmes, a_entries) {
510 			event_set(&apme->a_ev, apme->a_raw,
511 			    EV_READ | EV_PERSIST, hostapd_apme_input, apme);
512 			if (event_add(&apme->a_ev, NULL) == -1)
513 				hostapd_fatal("failed to add APME event");
514 		}
515 	}
516 
517 	/*
518 	 * Schedule the IAPP listener
519 	 */
520 	event_set(&iapp->i_udp_ev, iapp->i_udp, EV_READ | EV_PERSIST,
521 	    hostapd_iapp_input, cfg);
522 	if (event_add(&iapp->i_udp_ev, NULL) == -1)
523 		hostapd_fatal("failed to add IAPP event");
524 
525 	hostapd_log(HOSTAPD_LOG, "starting hostapd with pid %u",
526 	    getpid());
527 
528 	/* Run event loop */
529 	if (event_dispatch() == -1)
530 		hostapd_fatal("failed to dispatch hostapd");
531 
532 	/* Executed after the event loop has been terminated */
533 	hostapd_cleanup(cfg);
534 	return (EXIT_SUCCESS);
535 }
536 
537 void
538 hostapd_randval(u_int8_t *buf, const u_int len)
539 {
540 	u_int32_t data = 0;
541 	u_int i;
542 
543 	for (i = 0; i < len; i++) {
544 		if ((i % sizeof(data)) == 0)
545 			data = arc4random();
546 		buf[i] = data & 0xff;
547 		data >>= 8;
548 	}
549 }
550 
551 struct hostapd_table *
552 hostapd_table_add(struct hostapd_config *cfg, const char *name)
553 {
554 	struct hostapd_table *table;
555 
556 	if (hostapd_table_lookup(cfg, name) != NULL)
557 		return (NULL);
558 	if ((table = (struct hostapd_table *)
559 	    calloc(1, sizeof(struct hostapd_table))) == NULL)
560 		return (NULL);
561 	if (strlcpy(table->t_name, name, sizeof(table->t_name)) >=
562 	    sizeof(table->t_name)) {
563 		free(table);
564 		return (NULL);
565 	}
566 	RB_INIT(&table->t_tree);
567 	TAILQ_INIT(&table->t_mask_head);
568 	TAILQ_INSERT_TAIL(&cfg->c_tables, table, t_entries);
569 
570 	return (table);
571 }
572 
573 struct hostapd_table *
574 hostapd_table_lookup(struct hostapd_config *cfg, const char *name)
575 {
576 	struct hostapd_table *table;
577 
578 	TAILQ_FOREACH(table, &cfg->c_tables, t_entries) {
579 		if (strcmp(name, table->t_name) == 0)
580 			return (table);
581 	}
582 
583 	return (NULL);
584 }
585 
586 struct hostapd_entry *
587 hostapd_entry_add(struct hostapd_table *table, u_int8_t *lladdr)
588 {
589 	struct hostapd_entry *entry;
590 
591 	if (hostapd_entry_lookup(table, lladdr) != NULL)
592 		return (NULL);
593 
594 	if ((entry = (struct hostapd_entry *)
595 	    calloc(1, sizeof(struct hostapd_entry))) == NULL)
596 		return (NULL);
597 
598 	bcopy(lladdr, entry->e_lladdr, IEEE80211_ADDR_LEN);
599 	RB_INSERT(hostapd_tree, &table->t_tree, entry);
600 
601 	return (entry);
602 }
603 
604 struct hostapd_entry *
605 hostapd_entry_lookup(struct hostapd_table *table, u_int8_t *lladdr)
606 {
607 	struct hostapd_entry *entry, key;
608 
609 	bcopy(lladdr, key.e_lladdr, IEEE80211_ADDR_LEN);
610 	if ((entry = RB_FIND(hostapd_tree, &table->t_tree, &key)) != NULL)
611 		return (entry);
612 
613 	/* Masked entries can't be handled by the red-black tree */
614 	TAILQ_FOREACH(entry, &table->t_mask_head, e_entries) {
615 		if (HOSTAPD_ENTRY_MASK_MATCH(entry, lladdr))
616 			return (entry);
617 	}
618 
619 	return (NULL);
620 }
621 
622 void
623 hostapd_entry_update(struct hostapd_table *table, struct hostapd_entry *entry)
624 {
625 	RB_REMOVE(hostapd_tree, &table->t_tree, entry);
626 
627 	/* Apply mask to entry */
628 	if (entry->e_flags & HOSTAPD_ENTRY_F_MASK) {
629 		HOSTAPD_ENTRY_MASK_ADD(entry->e_lladdr, entry->e_mask);
630 		TAILQ_INSERT_TAIL(&table->t_mask_head, entry, e_entries);
631 	} else {
632 		RB_INSERT(hostapd_tree, &table->t_tree, entry);
633 	}
634 }
635 
636 static __inline int
637 hostapd_entry_cmp(struct hostapd_entry *a, struct hostapd_entry *b)
638 {
639 	return (memcmp(a->e_lladdr, b->e_lladdr, IEEE80211_ADDR_LEN));
640 }
641 
642 RB_GENERATE(hostapd_tree, hostapd_entry, e_nodes, hostapd_entry_cmp);
643