xref: /freebsd/tools/tools/net80211/wlanwds/wlanwds.c (revision db80116da404c90e6f0d69350554291f866e8ebb)
1 /*-
2  * Copyright (c) 2006-2009 Sam Leffler, Errno Consulting
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer,
10  *    without modification.
11  * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12  *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
13  *    redistribution must be conditioned upon including a substantially
14  *    similar Disclaimer requirement for further binary redistribution.
15  *
16  * NO WARRANTY
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19  * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
20  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21  * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
22  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27  * THE POSSIBILITY OF SUCH DAMAGES.
28  */
29 
30 /*
31  * Test app to demonstrate how to handle dynamic WDS links:
32  * o monitor 802.11 events for wds discovery events
33  * o create wds vap's in response to wds discovery events
34  *   and launch a script to handle adding the vap to the
35  *   bridge, etc.
36  * o destroy wds vap's when station leaves
37  */
38 #include <sys/param.h>
39 #include <sys/file.h>
40 #include <sys/socket.h>
41 #include <sys/ioctl.h>
42 #include <sys/sysctl.h>
43 #include <sys/types.h>
44 
45 #include <net/if.h>
46 #include "net/if_media.h"
47 #include <net/route.h>
48 #include <net/if_dl.h>
49 #include <netinet/in.h>
50 #include <netinet/if_ether.h>
51 #include "net80211/ieee80211_ioctl.h"
52 #include "net80211/ieee80211_freebsd.h"
53 #include <arpa/inet.h>
54 #include <netdb.h>
55 
56 #include <net/if.h>
57 #include <net/if_types.h>
58 
59 #include <ctype.h>
60 #include <err.h>
61 #include <errno.h>
62 #include <paths.h>
63 #include <stdarg.h>
64 #include <stdio.h>
65 #include <stdlib.h>
66 #include <string.h>
67 #include <sysexits.h>
68 #include <syslog.h>
69 #include <unistd.h>
70 #include <ifaddrs.h>
71 #include <libutil.h>
72 
73 #define	IEEE80211_ADDR_EQ(a1,a2)	(memcmp(a1,a2,IEEE80211_ADDR_LEN) == 0)
74 #define	IEEE80211_ADDR_COPY(dst,src)	memcpy(dst,src,IEEE80211_ADDR_LEN)
75 
76 struct wds {
77 	struct wds *next;
78 	uint8_t	bssid[IEEE80211_ADDR_LEN];	/* bssid of associated sta */
79 	char	ifname[IFNAMSIZ];		/* vap interface name */
80 };
81 static struct wds *wds;
82 
83 static	const char *script = NULL;
84 static	char **ifnets;
85 static	int nifnets = 0;
86 static	int discover_on_join = 0;
87 
88 static	void scanforvaps(int s);
89 static	void handle_rtmsg(struct rt_msghdr *rtm, ssize_t msglen);
90 static	void wds_discovery(const char *ifname,
91 		const uint8_t bssid[IEEE80211_ADDR_LEN]);
92 static	void wds_destroy(const char *ifname);
93 static	void wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN]);
94 static	int wds_vap_create(const char *ifname, uint8_t macaddr[ETHER_ADDR_LEN],
95 	    struct wds *);
96 static	int wds_vap_destroy(const char *ifname);
97 
98 static void
usage(const char * progname)99 usage(const char *progname)
100 {
101 	fprintf(stderr, "usage: %s [-efjtv] [-P pidfile] [-s <set_scriptname>] [ifnet0 ... | any]\n",
102 		progname);
103 	exit(-1);
104 }
105 
106 int
main(int argc,char * argv[])107 main(int argc, char *argv[])
108 {
109 	const char *progname = argv[0];
110 	const char *pidfile = NULL;
111 	struct pidfh *pfh = NULL;
112 	int s, c, logmask, bg = 1;
113 	char msg[2048];
114 	int log_stderr = 0;
115 	pid_t otherpid;
116 
117 	logmask = LOG_UPTO(LOG_INFO);
118 	while ((c = getopt(argc, argv, "efjP:s:tv")) != -1)
119 		switch (c) {
120 		case 'e':
121 			log_stderr = LOG_PERROR;
122 			break;
123 		case 'f':
124 			bg = 0;
125 			break;
126 		case 'j':
127 			discover_on_join = 1;
128 			break;
129 		case 'P':
130 			pidfile = optarg;
131 			break;
132 		case 's':
133 			script = optarg;
134 			break;
135 		case 't':
136 			logmask = LOG_UPTO(LOG_ERR);
137 			break;
138 		case 'v':
139 			logmask = LOG_UPTO(LOG_DEBUG);
140 			break;
141 		case '?':
142 			usage(progname);
143 			/*NOTREACHED*/
144 		}
145 	argc -= optind, argv += optind;
146 	if (argc == 0) {
147 		fprintf(stderr, "%s: no ifnet's specified to monitor\n",
148 		    progname);
149 		usage(progname);
150 	}
151 	ifnets = argv;
152 	nifnets = argc;
153 
154 	if (pidfile != NULL) {
155 		pfh = pidfile_open(pidfile, 0600, &otherpid);
156 		if (pfh == NULL) {
157 			if (errno == EEXIST)
158 				errx(EXIT_FAILURE, "Daemon already running; pid: %jd.",
159 				    (intmax_t)otherpid);
160 
161 			warn("Cannot open or create pidfile");
162 		}
163 	}
164 
165 	s = socket(PF_ROUTE, SOCK_RAW, 0);
166 	if (s < 0)
167 		err(EX_OSERR, "socket");
168 	/*
169 	 * Scan for inherited state.
170 	 */
171 	scanforvaps(s);
172 
173 	/* XXX what directory to work in? */
174 	if (bg && daemon(0, 0) < 0) {
175 		pidfile_remove(pfh);
176 		err(EX_OSERR, "daemon");
177 	}
178 
179 	pidfile_write(pfh);
180 
181 	openlog("wlanwds", log_stderr | LOG_PID | LOG_CONS, LOG_DAEMON);
182 	setlogmask(logmask);
183 
184 	for (;;) {
185 		ssize_t n = read(s, msg, sizeof(msg));
186 		handle_rtmsg((struct rt_msghdr *)msg, n);
187 	}
188 
189 	pidfile_remove(pfh);
190 	return 0;
191 }
192 
193 static const char *
ether_sprintf(const uint8_t mac[IEEE80211_ADDR_LEN])194 ether_sprintf(const uint8_t mac[IEEE80211_ADDR_LEN])
195 {
196 	static char buf[32];
197 
198 	snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
199 		mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
200 	return buf;
201 }
202 
203 /*
204  * Fetch a vap's parent ifnet name.
205  */
206 static int
getparent(const char * ifname,char parent[IFNAMSIZ+1])207 getparent(const char *ifname, char parent[IFNAMSIZ+1])
208 {
209 	char oid[256];
210 	size_t parentlen;
211 
212 	/* fetch parent interface name */
213 	snprintf(oid, sizeof(oid), "net.wlan.%s.%%parent", ifname+4);
214 	parentlen = IFNAMSIZ;
215 	if (sysctlbyname(oid, parent, &parentlen, NULL, 0) < 0)
216 		return -1;
217 	parent[parentlen] = '\0';
218 	return 0;
219 }
220 
221 /*
222  * Check if the specified ifnet is one we're supposed to monitor.
223  * The ifnet is assumed to be a vap; we find it's parent and check
224  * it against the set of ifnet's specified on the command line.
225  *
226  * TODO: extend this to also optionally allow the specific DWDS
227  * VAP to be monitored, instead of assuming all VAPs on a parent
228  * physical interface are being monitored by this instance of
229  * wlanwds.
230  */
231 static int
checkifnet(const char * ifname,int complain)232 checkifnet(const char *ifname, int complain)
233 {
234 	char parent[256];
235 	int i;
236 
237 	if (getparent(ifname, parent) < 0) {
238 		if (complain)
239 			syslog(LOG_ERR,
240 			   "%s: no pointer to parent interface: %m", ifname);
241 		return 0;
242 	}
243 
244 	for (i = 0; i < nifnets; i++)
245 		if (strcasecmp(ifnets[i], "any") == 0 ||
246 		    strcmp(ifnets[i], parent) == 0)
247 			return 1;
248 	syslog(LOG_DEBUG, "%s: parent %s not being monitored", ifname, parent);
249 	return 0;
250 }
251 
252 /*
253  * Return 1 if the specified ifnet is a WDS vap.
254  */
255 static int
iswdsvap(int s,const char * ifname)256 iswdsvap(int s, const char *ifname)
257 {
258 	struct ifmediareq ifmr;
259 
260 	memset(&ifmr, 0, sizeof(ifmr));
261 	strncpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name));
262 	if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0)
263 		err(-1, "%s: cannot get media", ifname);
264 	return (ifmr.ifm_current & IFM_IEEE80211_WDS) != 0;
265 }
266 
267 /*
268  * Fetch the bssid for an ifnet.  The caller is assumed
269  * to have already verified this is possible.
270  */
271 static void
getbssid(int s,const char * ifname,uint8_t bssid[IEEE80211_ADDR_LEN])272 getbssid(int s, const char *ifname, uint8_t bssid[IEEE80211_ADDR_LEN])
273 {
274 	struct ieee80211req ireq;
275 
276 	memset(&ireq, 0, sizeof(ireq));
277 	strncpy(ireq.i_name, ifname, sizeof(ireq.i_name));
278 	ireq.i_type = IEEE80211_IOC_BSSID;
279 	ireq.i_data = bssid;
280 	ireq.i_len = IEEE80211_ADDR_LEN;
281 	if (ioctl(s, SIOCG80211, &ireq) < 0)
282 		err(-1, "%s: cannot fetch bssid", ifname);
283 }
284 
285 /*
286  * Fetch the mac address configured for a given ifnet.
287  * (Note - the current link level address, NOT hwaddr.)
288  *
289  * This is currently, sigh, O(n) because there's no current kernel
290  * API that will do it for a single interface.
291  *
292  * Return 0 if successful, -1 if failure.
293  */
294 static int
getlladdr(const char * ifname,uint8_t macaddr[ETHER_ADDR_LEN])295 getlladdr(const char *ifname, uint8_t macaddr[ETHER_ADDR_LEN])
296 {
297 	struct ifaddrs *ifap, *ifa;
298 	struct sockaddr_dl *sdl;
299 
300 	if (getifaddrs(&ifap) < 0) {
301 		warn("%s: getifaddrs", __func__);
302 		return (-1);
303 	}
304 
305 	/* Look for a matching interface */
306 	for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
307 		if (strcmp(ifname, ifa->ifa_name) != 0)
308 			continue;
309 
310 		/* Found it - check if there's an ifa_addr */
311 		if (ifa->ifa_addr == NULL) {
312 			syslog(LOG_CRIT, "%s: ifname %s; ifa_addr is NULL\n",
313 			    __func__, ifname);
314 			goto err;
315 		}
316 
317 		/* Check address family */
318 		sdl = (struct sockaddr_dl *)(void *)ifa->ifa_addr;
319 		if (sdl->sdl_type != IFT_ETHER) {
320 			syslog(LOG_CRIT, "%s: %s: unknown aftype (%d)\n",
321 			    __func__,
322 			    ifname,
323 			    sdl->sdl_type);
324 			goto err;
325 		}
326 		if (sdl->sdl_alen != ETHER_ADDR_LEN) {
327 			syslog(LOG_CRIT, "%s: %s: aflen too short (%d)\n",
328 			    __func__,
329 			    ifname,
330 			    sdl->sdl_alen);
331 			goto err;
332 		}
333 
334 		/* Ok, found it */
335 		memcpy(macaddr, (void *) LLADDR(sdl), ETHER_ADDR_LEN);
336 		goto ok;
337 	}
338 	syslog(LOG_CRIT, "%s: couldn't find ifname %s\n", __func__, ifname);
339 	/* FALLTHROUGH */
340 err:
341 	freeifaddrs(ifap);
342 	return (-1);
343 
344 ok:
345 	freeifaddrs(ifap);
346 	return (0);
347 }
348 
349 /*
350  * Scan the system for WDS vaps associated with the ifnet's we're
351  * supposed to monitor.  Any vaps are added to our internal table
352  * so we can find them (and destroy them) on station leave.
353  */
354 static void
scanforvaps(int s)355 scanforvaps(int s)
356 {
357 	char ifname[IFNAMSIZ+1];
358 	uint8_t bssid[IEEE80211_ADDR_LEN];
359 	int i;
360 
361 	/* XXX brutal; should just walk sysctl tree */
362 	for (i = 0; i < 128; i++) {
363 		snprintf(ifname, sizeof(ifname), "wlan%d", i);
364 		if (checkifnet(ifname, 0) && iswdsvap(s, ifname)) {
365 			struct wds *p = malloc(sizeof(struct wds));
366 			if (p == NULL)
367 				err(-1, "%s: malloc failed", __func__);
368 			strlcpy(p->ifname, ifname, IFNAMSIZ);
369 			getbssid(s, ifname, p->bssid);
370 			p->next = wds;
371 			wds = p;
372 
373 			syslog(LOG_INFO, "[%s] discover wds vap %s",
374 			    ether_sprintf(bssid), ifname);
375 		}
376 	}
377 }
378 
379 /*
380  * Process a routing socket message.  We handle messages related
381  * to dynamic WDS:
382  * o on WDS discovery (rx of a 4-address frame with DWDS enabled)
383  *   we create a WDS vap for the specified mac address
384  * o on station leave we destroy any associated WDS vap
385  * o on ifnet destroy we update state if this is manual destroy of
386  *   a WDS vap in our table
387  * o if the -j option is supplied on the command line we create
388  *   WDS vaps on station join/rejoin, this is useful for some setups
389  *   where a WDS vap is required for 4-address traffic to flow
390  */
391 static void
handle_rtmsg(struct rt_msghdr * rtm,ssize_t msglen)392 handle_rtmsg(struct rt_msghdr *rtm, ssize_t msglen)
393 {
394 	struct if_announcemsghdr *ifan;
395 
396 	(void) msglen; /* UNUSED */
397 
398 	if (rtm->rtm_version != RTM_VERSION) {
399 		syslog(LOG_ERR, "routing message version %d not understood",
400 		    rtm->rtm_version);
401 		return;
402 	}
403 	switch (rtm->rtm_type) {
404 	case RTM_IFANNOUNCE:
405 		ifan = (struct if_announcemsghdr *)rtm;
406 		switch (ifan->ifan_what) {
407 		case IFAN_ARRIVAL:
408 			syslog(LOG_DEBUG,
409 			    "RTM_IFANNOUNCE: if# %d, what: arrival",
410 			    ifan->ifan_index);
411 			break;
412 		case IFAN_DEPARTURE:
413 			syslog(LOG_DEBUG,
414 			    "RTM_IFANNOUNCE: if# %d, what: departure",
415 			    ifan->ifan_index);
416 			/* NB: ok to call w/ unmonitored ifnets */
417 			wds_destroy(ifan->ifan_name);
418 			break;
419 		}
420 		break;
421 	case RTM_IEEE80211:
422 #define	V(type)	((struct type *)(&ifan[1]))
423 		ifan = (struct if_announcemsghdr *)rtm;
424 		switch (ifan->ifan_what) {
425 		case RTM_IEEE80211_DISASSOC:
426 			if (!discover_on_join)
427 				break;
428 			/* fall thru... */
429 		case RTM_IEEE80211_LEAVE:
430 			if (!checkifnet(ifan->ifan_name, 1))
431 				break;
432 			syslog(LOG_INFO, "[%s] station leave",
433 			    ether_sprintf(V(ieee80211_leave_event)->iev_addr));
434 			wds_leave(V(ieee80211_leave_event)->iev_addr);
435 			break;
436 		case RTM_IEEE80211_JOIN:
437 		case RTM_IEEE80211_REJOIN:
438 		case RTM_IEEE80211_ASSOC:
439 		case RTM_IEEE80211_REASSOC:
440 			if (!discover_on_join)
441 				break;
442 			/* fall thru... */
443 		case RTM_IEEE80211_WDS:
444 			syslog(LOG_INFO, "[%s] wds discovery",
445 			    ether_sprintf(V(ieee80211_wds_event)->iev_addr));
446 			if (!checkifnet(ifan->ifan_name, 1))
447 				break;
448 			wds_discovery(ifan->ifan_name,
449 			    V(ieee80211_wds_event)->iev_addr);
450 			break;
451 		}
452 		break;
453 #undef V
454 	}
455 }
456 
457 /*
458  * Handle WDS discovery; create a WDS vap for the specified bssid.
459  * If a vap already exists then do nothing (can happen when a flood
460  * of 4-address frames causes multiple events to be queued before
461  * we create a vap).
462  */
463 static void
wds_discovery(const char * ifname,const uint8_t bssid[IEEE80211_ADDR_LEN])464 wds_discovery(const char *ifname, const uint8_t bssid[IEEE80211_ADDR_LEN])
465 {
466 	struct wds *p;
467 	char parent[256];
468 	char cmd[1024];
469 	uint8_t macaddr[ETHER_ADDR_LEN];
470 	int status;
471 
472 	for (p = wds; p != NULL; p = p->next)
473 		if (IEEE80211_ADDR_EQ(p->bssid, bssid)) {
474 			syslog(LOG_INFO, "[%s] wds vap already created (%s)",
475 			    ether_sprintf(bssid), ifname);
476 			return;
477 		}
478 	if (getparent(ifname, parent) < 0) {
479 		syslog(LOG_ERR, "%s: no pointer to parent interface: %m",
480 		    ifname);
481 		return;
482 	}
483 
484 	if (getlladdr(ifname, macaddr) < 0) {
485 		syslog(LOG_ERR, "%s: couldn't get lladdr for parent interface: %m",
486 		    ifname);
487 		return;
488 	}
489 
490 	p = malloc(sizeof(struct wds));
491 	if (p == NULL) {
492 		syslog(LOG_ERR, "%s: malloc failed: %m", __func__);
493 		return;
494 	}
495 	IEEE80211_ADDR_COPY(p->bssid, bssid);
496 	if (wds_vap_create(parent, macaddr, p) < 0) {
497 		free(p);
498 		return;
499 	}
500 	/*
501 	 * Add to table and launch setup script.
502 	 */
503 	p->next = wds;
504 	wds = p;
505 	syslog(LOG_INFO, "[%s] create wds vap %s, parent %s (%s)",
506 	    ether_sprintf(bssid),
507 	    p->ifname,
508 	    ifname,
509 	    parent);
510 	if (script != NULL) {
511 		snprintf(cmd, sizeof(cmd), "%s %s", script, p->ifname);
512 		status = system(cmd);
513 		if (status)
514 			syslog(LOG_ERR, "vap setup script %s exited with "
515 			    "status %d", script, status);
516 	}
517 }
518 
519 /*
520  * Destroy a WDS vap (if known).
521  */
522 static void
wds_destroy(const char * ifname)523 wds_destroy(const char *ifname)
524 {
525 	struct wds *p, **pp;
526 
527 	for (pp = &wds; (p = *pp) != NULL; pp = &p->next)
528 		if (strncmp(p->ifname, ifname, IFNAMSIZ) == 0)
529 			break;
530 	if (p != NULL) {
531 		*pp = p->next;
532 		/* NB: vap already destroyed */
533 		free(p);
534 		return;
535 	}
536 }
537 
538 /*
539  * Handle a station leave event; destroy any associated WDS vap.
540  */
541 static void
wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN])542 wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN])
543 {
544 	struct wds *p, **pp;
545 
546 	for (pp = &wds; (p = *pp) != NULL; pp = &p->next)
547 		if (IEEE80211_ADDR_EQ(p->bssid, bssid))
548 			break;
549 	if (p != NULL) {
550 		*pp = p->next;
551 		if (wds_vap_destroy(p->ifname) >= 0)
552 			syslog(LOG_INFO, "[%s] wds vap %s destroyed",
553 			    ether_sprintf(bssid), p->ifname);
554 		free(p);
555 	}
556 }
557 
558 static int
wds_vap_create(const char * parent,uint8_t macaddr[ETHER_ADDR_LEN],struct wds * p)559 wds_vap_create(const char *parent, uint8_t macaddr[ETHER_ADDR_LEN],
560     struct wds *p)
561 {
562 	struct ieee80211_clone_params cp;
563 	struct ifreq ifr;
564 	int s, status;
565 	char bssid_str[32], macaddr_str[32];
566 
567 	memset(&cp, 0, sizeof(cp));
568 
569 	/* Parent interface */
570 	strncpy(cp.icp_parent, parent, IFNAMSIZ);
571 
572 	/* WDS interface */
573 	cp.icp_opmode = IEEE80211_M_WDS;
574 
575 	/* BSSID for the current node */
576 	IEEE80211_ADDR_COPY(cp.icp_bssid, p->bssid);
577 
578 	/*
579 	 * Set the MAC address to match the actual interface
580 	 * that we received the discovery event from.
581 	 * That way we can run WDS on any VAP rather than
582 	 * only the first VAP and then correctly set the
583 	 * MAC address.
584 	 */
585 	cp.icp_flags |= IEEE80211_CLONE_MACADDR;
586 	IEEE80211_ADDR_COPY(cp.icp_macaddr, macaddr);
587 
588 	memset(&ifr, 0, sizeof(ifr));
589 	strncpy(ifr.ifr_name, "wlan", IFNAMSIZ);
590 	ifr.ifr_data = (void *) &cp;
591 
592 	status = -1;
593 	s = socket(AF_INET, SOCK_DGRAM, 0);
594 	if (s >= 0) {
595 		if (ioctl(s, SIOCIFCREATE2, &ifr) >= 0) {
596 			strlcpy(p->ifname, ifr.ifr_name, IFNAMSIZ);
597 			status = 0;
598 		} else {
599 			syslog(LOG_ERR, "SIOCIFCREATE2("
600 			    "mode %u flags 0x%x parent %s bssid %s macaddr %s): %m",
601 			    cp.icp_opmode, cp.icp_flags, parent,
602 			    ether_ntoa_r((void *) cp.icp_bssid, bssid_str),
603 			    ether_ntoa_r((void *) cp.icp_macaddr, macaddr_str));
604 		}
605 		close(s);
606 	} else
607 		syslog(LOG_ERR, "socket(SOCK_DRAGM): %m");
608 	return status;
609 }
610 
611 static int
wds_vap_destroy(const char * ifname)612 wds_vap_destroy(const char *ifname)
613 {
614 	struct ieee80211req ifr;
615 	int s, status;
616 
617 	s = socket(AF_INET, SOCK_DGRAM, 0);
618 	if (s < 0) {
619 		syslog(LOG_ERR, "socket(SOCK_DRAGM): %m");
620 		return -1;
621 	}
622 	memset(&ifr, 0, sizeof(ifr));
623 	strncpy(ifr.i_name, ifname, IFNAMSIZ);
624 	if (ioctl(s, SIOCIFDESTROY, &ifr) < 0) {
625 		syslog(LOG_ERR, "ioctl(SIOCIFDESTROY): %m");
626 		status = -1;
627 	} else
628 		status = 0;
629 	close(s);
630 	return status;
631 }
632