1178360Ssam/*-
2191127Ssam * Copyright (c) 2006-2009 Sam Leffler, Errno Consulting
3178360Ssam * All rights reserved.
4178360Ssam *
5178360Ssam * Redistribution and use in source and binary forms, with or without
6178360Ssam * modification, are permitted provided that the following conditions
7178360Ssam * are met:
8178360Ssam * 1. Redistributions of source code must retain the above copyright
9178360Ssam *    notice, this list of conditions and the following disclaimer,
10178360Ssam *    without modification.
11178360Ssam * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12178360Ssam *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
13178360Ssam *    redistribution must be conditioned upon including a substantially
14178360Ssam *    similar Disclaimer requirement for further binary redistribution.
15178360Ssam *
16178360Ssam * NO WARRANTY
17178360Ssam * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18178360Ssam * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19178360Ssam * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
20178360Ssam * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21178360Ssam * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
22178360Ssam * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23178360Ssam * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24178360Ssam * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25178360Ssam * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26178360Ssam * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27178360Ssam * THE POSSIBILITY OF SUCH DAMAGES.
28178360Ssam *
29178360Ssam * $FreeBSD$
30178360Ssam */
31178360Ssam
32178360Ssam/*
33178360Ssam * Test app to demonstrate how to handle dynamic WDS links:
34178360Ssam * o monitor 802.11 events for wds discovery events
35178360Ssam * o create wds vap's in response to wds discovery events
36178360Ssam *   and launch a script to handle adding the vap to the
37178360Ssam *   bridge, etc.
38178360Ssam * o destroy wds vap's when station leaves
39178360Ssam */
40178360Ssam#include <sys/param.h>
41178360Ssam#include <sys/file.h>
42178360Ssam#include <sys/socket.h>
43178360Ssam#include <sys/ioctl.h>
44178360Ssam#include <sys/sysctl.h>
45178360Ssam#include <sys/types.h>
46178360Ssam
47178360Ssam#include <net/if.h>
48178360Ssam#include "net/if_media.h"
49178360Ssam#include <net/route.h>
50178360Ssam#include <net/if_dl.h>
51178360Ssam#include <netinet/in.h>
52178360Ssam#include <netinet/if_ether.h>
53178360Ssam#include "net80211/ieee80211_ioctl.h"
54178360Ssam#include "net80211/ieee80211_freebsd.h"
55178360Ssam#include <arpa/inet.h>
56178360Ssam#include <netdb.h>
57178360Ssam
58178360Ssam#include <ctype.h>
59178360Ssam#include <err.h>
60178360Ssam#include <errno.h>
61178360Ssam#include <paths.h>
62191247Ssam#include <stdarg.h>
63178360Ssam#include <stdio.h>
64178360Ssam#include <stdlib.h>
65178360Ssam#include <string.h>
66178360Ssam#include <sysexits.h>
67191247Ssam#include <syslog.h>
68178360Ssam#include <unistd.h>
69178360Ssam#include <ifaddrs.h>
70178360Ssam
71178360Ssam#define	IEEE80211_ADDR_EQ(a1,a2)	(memcmp(a1,a2,IEEE80211_ADDR_LEN) == 0)
72178360Ssam#define	IEEE80211_ADDR_COPY(dst,src)	memcpy(dst,src,IEEE80211_ADDR_LEN)
73178360Ssam
74178360Ssamstruct wds {
75178360Ssam	struct wds *next;
76178360Ssam	uint8_t	bssid[IEEE80211_ADDR_LEN];	/* bssid of associated sta */
77178360Ssam	char	ifname[IFNAMSIZ];		/* vap interface name */
78178360Ssam};
79178360Ssamstatic struct wds *wds;
80178360Ssam
81191247Ssamstatic	const char *script = NULL;
82191247Ssamstatic	char **ifnets;
83191247Ssamstatic	int nifnets = 0;
84178360Ssamstatic	int verbose = 0;
85191127Ssamstatic	int discover_on_join = 0;
86178360Ssam
87191247Ssamstatic	void scanforvaps(int s);
88191247Ssamstatic	void handle_rtmsg(struct rt_msghdr *rtm, ssize_t msglen);
89178360Ssamstatic	void wds_discovery(const char *ifname,
90178360Ssam		const uint8_t bssid[IEEE80211_ADDR_LEN]);
91178360Ssamstatic	void wds_destroy(const char *ifname);
92178360Ssamstatic	void wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN]);
93178360Ssamstatic	int wds_vap_create(const char *ifname, struct wds *);
94178360Ssamstatic	int wds_vap_destroy(const char *ifname);
95178360Ssam
96191247Ssamstatic void
97191247Ssamusage(const char *progname)
98191247Ssam{
99191247Ssam	fprintf(stderr, "usage: %s [-fjtv] [-P pidfile] [-s <set_scriptname>] [ifnet0 ... | any]\n",
100191247Ssam		progname);
101191247Ssam	exit(-1);
102191247Ssam}
103191247Ssam
104178360Ssamint
105178360Ssammain(int argc, char *argv[])
106178360Ssam{
107191247Ssam	const char *progname = argv[0];
108191247Ssam	const char *pidfile = NULL;
109191247Ssam	int s, c, logmask, bg = 1;
110178360Ssam	char msg[2048];
111178360Ssam
112191247Ssam	logmask = LOG_UPTO(LOG_INFO);
113191247Ssam	while ((c = getopt(argc, argv, "fjP:s:tv")) != -1)
114178360Ssam		switch (c) {
115191247Ssam		case 'f':
116191247Ssam			bg = 0;
117191247Ssam			break;
118191127Ssam		case 'j':
119191127Ssam			discover_on_join = 1;
120178360Ssam			break;
121191247Ssam		case 'P':
122191247Ssam			pidfile = optarg;
123191247Ssam			break;
124178360Ssam		case 's':
125178360Ssam			script = optarg;
126178360Ssam			break;
127191247Ssam		case 't':
128191247Ssam			logmask = LOG_UPTO(LOG_ERR);
129191247Ssam			break;
130178360Ssam		case 'v':
131191247Ssam			logmask = LOG_UPTO(LOG_DEBUG);
132178360Ssam			break;
133178360Ssam		case '?':
134191247Ssam			usage(progname);
135178360Ssam			/*NOTREACHED*/
136178360Ssam		}
137191247Ssam	argc -= optind, argv += optind;
138191247Ssam	if (argc == 0) {
139191247Ssam		fprintf(stderr, "%s: no ifnet's specified to monitor\n",
140191247Ssam		    progname);
141191247Ssam		usage(progname);
142191247Ssam	}
143191247Ssam	ifnets = argv;
144191247Ssam	nifnets = argc;
145178360Ssam
146178360Ssam	s = socket(PF_ROUTE, SOCK_RAW, 0);
147178360Ssam	if (s < 0)
148178360Ssam		err(EX_OSERR, "socket");
149191247Ssam	/*
150191247Ssam	 * Scan for inherited state.
151191247Ssam	 */
152191247Ssam	scanforvaps(s);
153191247Ssam
154191247Ssam	/* XXX what directory to work in? */
155191247Ssam	if (bg && daemon(0, 0) < 0)
156191247Ssam		err(EX_OSERR, "daemon");
157191247Ssam
158191247Ssam	openlog("wlanwds", LOG_PID | LOG_CONS, LOG_DAEMON);
159191247Ssam	setlogmask(logmask);
160191247Ssam
161191247Ssam	for (;;) {
162191247Ssam		ssize_t n = read(s, msg, sizeof(msg));
163178360Ssam		handle_rtmsg((struct rt_msghdr *)msg, n);
164178360Ssam	}
165178360Ssam	return 0;
166178360Ssam}
167178360Ssam
168178360Ssamstatic const char *
169191247Ssamether_sprintf(const uint8_t mac[IEEE80211_ADDR_LEN])
170178360Ssam{
171178360Ssam	static char buf[32];
172178360Ssam
173178360Ssam	snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
174178360Ssam		mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
175178360Ssam	return buf;
176178360Ssam}
177178360Ssam
178191247Ssam/*
179191247Ssam * Fetch a vap's parent ifnet name.
180191247Ssam */
181191247Ssamstatic int
182191247Ssamgetparent(const char *ifname, char parent[IFNAMSIZ+1])
183191247Ssam{
184191247Ssam	char oid[256];
185191247Ssam	int parentlen;
186191247Ssam
187191247Ssam	/* fetch parent interface name */
188191247Ssam	snprintf(oid, sizeof(oid), "net.wlan.%s.%%parent", ifname+4);
189191247Ssam	parentlen = IFNAMSIZ;
190191247Ssam	if (sysctlbyname(oid, parent, &parentlen, NULL, 0) < 0)
191191247Ssam		return -1;
192191247Ssam	parent[parentlen] = '\0';
193191247Ssam	return 0;
194191247Ssam}
195191247Ssam
196191247Ssam/*
197191247Ssam * Check if the specified ifnet is one we're supposed to monitor.
198191247Ssam * The ifnet is assumed to be a vap; we find it's parent and check
199191247Ssam * it against the set of ifnet's specified on the command line.
200191247Ssam */
201191247Ssamstatic int
202191247Ssamcheckifnet(const char *ifname, int complain)
203191247Ssam{
204191247Ssam	char parent[256];
205191247Ssam	int i;
206191247Ssam
207191247Ssam	if (getparent(ifname, parent) < 0) {
208191247Ssam		if (complain)
209191247Ssam			syslog(LOG_ERR,
210191247Ssam			   "%s: no pointer to parent interface: %m", ifname);
211191247Ssam		return 0;
212191247Ssam	}
213191247Ssam
214191247Ssam	for (i = 0; i < nifnets; i++)
215191247Ssam		if (strcasecmp(ifnets[i], "any") == 0 ||
216191247Ssam		    strcmp(ifnets[i], parent) == 0)
217191247Ssam			return 1;
218191247Ssam	syslog(LOG_DEBUG, "%s: parent %s not being monitored", ifname, parent);
219191247Ssam	return 0;
220191247Ssam}
221191247Ssam
222191247Ssam/*
223191247Ssam * Return 1 if the specified ifnet is a WDS vap.
224191247Ssam */
225191247Ssamstatic int
226191247Ssamiswdsvap(int s, const char *ifname)
227191247Ssam{
228191247Ssam	struct ifmediareq ifmr;
229191247Ssam
230191247Ssam	memset(&ifmr, 0, sizeof(ifmr));
231191247Ssam	strncpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name));
232191247Ssam	if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0)
233191247Ssam		err(-1, "%s: cannot get media", ifname);
234191247Ssam	return (ifmr.ifm_current & IFM_IEEE80211_WDS) != 0;
235191247Ssam}
236191247Ssam
237191247Ssam/*
238191247Ssam * Fetch the bssid for an ifnet.  The caller is assumed
239191247Ssam * to have already verified this is possible.
240191247Ssam */
241178360Ssamstatic void
242191247Ssamgetbssid(int s, const char *ifname, char bssid[IEEE80211_ADDR_LEN])
243178360Ssam{
244191247Ssam	struct ieee80211req ireq;
245191247Ssam
246191247Ssam	memset(&ireq, 0, sizeof(ireq));
247191247Ssam	strncpy(ireq.i_name, ifname, sizeof(ireq.i_name));
248191247Ssam	ireq.i_type = IEEE80211_IOC_BSSID;
249191247Ssam	ireq.i_data = bssid;
250191247Ssam	ireq.i_len = IEEE80211_ADDR_LEN;
251191247Ssam	if (ioctl(s, SIOCG80211, &ireq) < 0)
252191247Ssam		err(-1, "%s: cannot fetch bssid", ifname);
253191247Ssam}
254191247Ssam
255191247Ssam/*
256191247Ssam * Scan the system for WDS vaps associated with the ifnet's we're
257191247Ssam * supposed to monitor.  Any vaps are added to our internal table
258191247Ssam * so we can find them (and destroy them) on station leave.
259191247Ssam */
260191247Ssamstatic void
261191247Ssamscanforvaps(int s)
262191247Ssam{
263191247Ssam	char ifname[IFNAMSIZ+1];
264191247Ssam	char bssid[IEEE80211_ADDR_LEN];
265191247Ssam	int i;
266191247Ssam
267191247Ssam	/* XXX brutal; should just walk sysctl tree */
268191247Ssam	for (i = 0; i < 128; i++) {
269191247Ssam		snprintf(ifname, sizeof(ifname), "wlan%d", i);
270191247Ssam		if (checkifnet(ifname, 0) && iswdsvap(s, ifname)) {
271191247Ssam			struct wds *p = malloc(sizeof(struct wds));
272191247Ssam			if (p == NULL)
273191247Ssam				err(-1, "%s: malloc failed", __func__);
274191247Ssam			strlcpy(p->ifname, ifname, IFNAMSIZ);
275191247Ssam			getbssid(s, ifname, p->bssid);
276191247Ssam			p->next = wds;
277191247Ssam			wds = p;
278191247Ssam
279191247Ssam			syslog(LOG_INFO, "[%s] discover wds vap %s",
280191247Ssam			    ether_sprintf(bssid), ifname);
281191247Ssam		}
282191247Ssam	}
283191247Ssam}
284191247Ssam
285191247Ssam/*
286191247Ssam * Process a routing socket message.  We handle messages related
287191247Ssam * to dynamic WDS:
288191247Ssam * o on WDS discovery (rx of a 4-address frame with DWDS enabled)
289191247Ssam *   we create a WDS vap for the specified mac address
290191247Ssam * o on station leave we destroy any associated WDS vap
291191247Ssam * o on ifnet destroy we update state if this is manual destroy of
292191247Ssam *   a WDS vap in our table
293191247Ssam * o if the -j option is supplied on the command line we create
294191247Ssam *   WDS vaps on station join/rejoin, this is useful for some setups
295191247Ssam *   where a WDS vap is required for 4-address traffic to flow
296191247Ssam */
297191247Ssamstatic void
298191247Ssamhandle_rtmsg(struct rt_msghdr *rtm, ssize_t msglen)
299191247Ssam{
300178360Ssam	struct if_announcemsghdr *ifan;
301178360Ssam
302178360Ssam	if (rtm->rtm_version != RTM_VERSION) {
303191247Ssam		syslog(LOG_ERR, "routing message version %d not understood",
304178360Ssam		    rtm->rtm_version);
305178360Ssam		return;
306178360Ssam	}
307178360Ssam	switch (rtm->rtm_type) {
308178360Ssam	case RTM_IFANNOUNCE:
309178360Ssam		ifan = (struct if_announcemsghdr *)rtm;
310178360Ssam		switch (ifan->ifan_what) {
311178360Ssam		case IFAN_ARRIVAL:
312191247Ssam			syslog(LOG_DEBUG,
313191247Ssam			    "RTM_IFANNOUNCE: if# %d, what: arrival",
314191247Ssam			    ifan->ifan_index);
315178360Ssam			break;
316178360Ssam		case IFAN_DEPARTURE:
317191247Ssam			syslog(LOG_DEBUG,
318191247Ssam			    "RTM_IFANNOUNCE: if# %d, what: departure",
319191247Ssam			    ifan->ifan_index);
320191247Ssam			/* NB: ok to call w/ unmonitored ifnets */
321178360Ssam			wds_destroy(ifan->ifan_name);
322178360Ssam			break;
323178360Ssam		}
324178360Ssam		break;
325178360Ssam	case RTM_IEEE80211:
326178360Ssam#define	V(type)	((struct type *)(&ifan[1]))
327178360Ssam		ifan = (struct if_announcemsghdr *)rtm;
328178360Ssam		switch (ifan->ifan_what) {
329191247Ssam		case RTM_IEEE80211_DISASSOC:
330191247Ssam			if (!discover_on_join)
331191247Ssam				break;
332191247Ssam			/* fall thru... */
333178360Ssam		case RTM_IEEE80211_LEAVE:
334191247Ssam			if (!checkifnet(ifan->ifan_name, 1))
335191247Ssam				break;
336191247Ssam			syslog(LOG_INFO, "[%s] station leave",
337191247Ssam			    ether_sprintf(V(ieee80211_leave_event)->iev_addr));
338178360Ssam			wds_leave(V(ieee80211_leave_event)->iev_addr);
339178360Ssam			break;
340191127Ssam		case RTM_IEEE80211_JOIN:
341191127Ssam		case RTM_IEEE80211_REJOIN:
342191247Ssam		case RTM_IEEE80211_ASSOC:
343191247Ssam		case RTM_IEEE80211_REASSOC:
344191127Ssam			if (!discover_on_join)
345191127Ssam				break;
346191127Ssam			/* fall thru... */
347178360Ssam		case RTM_IEEE80211_WDS:
348191247Ssam			syslog(LOG_INFO, "[%s] wds discovery",
349191247Ssam			    ether_sprintf(V(ieee80211_wds_event)->iev_addr));
350191247Ssam			if (!checkifnet(ifan->ifan_name, 1))
351191247Ssam				break;
352191127Ssam			wds_discovery(ifan->ifan_name,
353191127Ssam			    V(ieee80211_wds_event)->iev_addr);
354178360Ssam			break;
355178360Ssam		}
356178360Ssam		break;
357178360Ssam#undef V
358178360Ssam	}
359178360Ssam}
360178360Ssam
361191247Ssam/*
362191247Ssam * Handle WDS discovery; create a WDS vap for the specified bssid.
363191247Ssam * If a vap already exists then do nothing (can happen when a flood
364191247Ssam * of 4-address frames causes multiple events to be queued before
365191247Ssam * we create a vap).
366191247Ssam */
367178360Ssamstatic void
368178360Ssamwds_discovery(const char *ifname, const uint8_t bssid[IEEE80211_ADDR_LEN])
369178360Ssam{
370178360Ssam	struct wds *p;
371191247Ssam	char parent[256];
372191247Ssam	char cmd[1024];
373191247Ssam	int status;
374178360Ssam
375178360Ssam	for (p = wds; p != NULL; p = p->next)
376178360Ssam		if (IEEE80211_ADDR_EQ(p->bssid, bssid)) {
377191247Ssam			syslog(LOG_INFO, "[%s] wds vap already created (%s)",
378191247Ssam			    ether_sprintf(bssid), ifname);
379178360Ssam			return;
380178360Ssam		}
381191247Ssam	if (getparent(ifname, parent) < 0) {
382191247Ssam		syslog(LOG_ERR, "%s: no pointer to parent interface: %m",
383191247Ssam		    ifname);
384191127Ssam		return;
385191127Ssam	}
386191127Ssam
387178360Ssam	p = malloc(sizeof(struct wds));
388178360Ssam	if (p == NULL) {
389191247Ssam		syslog(LOG_ERR, "%s: malloc failed: %m", __func__);
390178360Ssam		return;
391178360Ssam	}
392178360Ssam	IEEE80211_ADDR_COPY(p->bssid, bssid);
393191247Ssam	if (wds_vap_create(parent, p) < 0) {
394191247Ssam		free(p);
395191247Ssam		return;
396191247Ssam	}
397191247Ssam	/*
398191247Ssam	 * Add to table and launch setup script.
399191247Ssam	 */
400191247Ssam	p->next = wds;
401191247Ssam	wds = p;
402191247Ssam	syslog(LOG_INFO, "[%s] create wds vap %s", ether_sprintf(bssid),
403191247Ssam	    p->ifname);
404191247Ssam	if (script != NULL) {
405191127Ssam		snprintf(cmd, sizeof(cmd), "%s %s", script, p->ifname);
406178360Ssam		status = system(cmd);
407178360Ssam		if (status)
408191247Ssam			syslog(LOG_ERR, "vap setup script %s exited with "
409191247Ssam			    "status %d", script, status);
410191247Ssam	}
411178360Ssam}
412178360Ssam
413191247Ssam/*
414191247Ssam * Destroy a WDS vap (if known).
415191247Ssam */
416178360Ssamstatic void
417178360Ssamwds_destroy(const char *ifname)
418178360Ssam{
419178360Ssam	struct wds *p, **pp;
420178360Ssam
421178360Ssam	for (pp = &wds; (p = *pp) != NULL; pp = &p->next)
422178360Ssam		if (strncmp(p->ifname, ifname, IFNAMSIZ) == 0)
423178360Ssam			break;
424191247Ssam	if (p != NULL) {
425191247Ssam		*pp = p->next;
426191247Ssam		/* NB: vap already destroyed */
427191247Ssam		free(p);
428178360Ssam		return;
429191247Ssam	}
430178360Ssam}
431178360Ssam
432191247Ssam/*
433191247Ssam * Handle a station leave event; destroy any associated WDS vap.
434191247Ssam */
435178360Ssamstatic void
436178360Ssamwds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN])
437178360Ssam{
438178360Ssam	struct wds *p, **pp;
439178360Ssam
440178360Ssam	for (pp = &wds; (p = *pp) != NULL; pp = &p->next)
441178360Ssam		if (IEEE80211_ADDR_EQ(p->bssid, bssid))
442178360Ssam			break;
443191247Ssam	if (p != NULL) {
444191247Ssam		*pp = p->next;
445191247Ssam		if (wds_vap_destroy(p->ifname) >= 0)
446191247Ssam			syslog(LOG_INFO, "[%s] wds vap %s destroyed",
447191247Ssam			    ether_sprintf(bssid), p->ifname);
448191247Ssam		free(p);
449191247Ssam	}
450178360Ssam}
451178360Ssam
452178360Ssamstatic int
453178360Ssamwds_vap_create(const char *parent, struct wds *p)
454178360Ssam{
455178360Ssam	struct ieee80211_clone_params cp;
456178360Ssam	struct ifreq ifr;
457178360Ssam	int s, status;
458178360Ssam
459178360Ssam	memset(&cp, 0, sizeof(cp));
460178360Ssam	strncpy(cp.icp_parent, parent, IFNAMSIZ);
461178360Ssam	cp.icp_opmode = IEEE80211_M_WDS;
462178360Ssam	IEEE80211_ADDR_COPY(cp.icp_bssid, p->bssid);
463178360Ssam
464178360Ssam	memset(&ifr, 0, sizeof(ifr));
465178360Ssam	strncpy(ifr.ifr_name, "wlan", IFNAMSIZ);
466178360Ssam	ifr.ifr_data = (void *) &cp;
467178360Ssam
468178360Ssam	status = -1;
469178360Ssam	s = socket(AF_INET, SOCK_DGRAM, 0);
470178360Ssam	if (s >= 0) {
471178360Ssam		if (ioctl(s, SIOCIFCREATE2, &ifr) >= 0) {
472178360Ssam			strlcpy(p->ifname, ifr.ifr_name, IFNAMSIZ);
473178360Ssam			status = 0;
474178360Ssam		} else {
475191247Ssam			syslog(LOG_ERR, "SIOCIFCREATE2("
476191247Ssam			    "mode %u flags 0x%x parent %s bssid %s): %m",
477178360Ssam			    cp.icp_opmode, cp.icp_flags, parent,
478178360Ssam			    ether_sprintf(cp.icp_bssid));
479178360Ssam		}
480178360Ssam		close(s);
481178360Ssam	} else
482191247Ssam		syslog(LOG_ERR, "socket(SOCK_DRAGM): %m");
483178360Ssam	return status;
484178360Ssam}
485178360Ssam
486178360Ssamstatic int
487178360Ssamwds_vap_destroy(const char *ifname)
488178360Ssam{
489178360Ssam	struct ieee80211req ifr;
490178360Ssam	int s, status;
491178360Ssam
492178360Ssam	s = socket(AF_INET, SOCK_DGRAM, 0);
493178360Ssam	if (s < 0) {
494191247Ssam		syslog(LOG_ERR, "socket(SOCK_DRAGM): %m");
495178360Ssam		return -1;
496178360Ssam	}
497178360Ssam	memset(&ifr, 0, sizeof(ifr));
498178360Ssam	strncpy(ifr.i_name, ifname, IFNAMSIZ);
499178360Ssam	if (ioctl(s, SIOCIFDESTROY, &ifr) < 0) {
500191247Ssam		syslog(LOG_ERR, "ioctl(SIOCIFDESTROY): %m");
501178360Ssam		status = -1;
502178360Ssam	} else
503178360Ssam		status = 0;
504178360Ssam	close(s);
505178360Ssam	return status;
506178360Ssam}
507