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