47a9b2639a423624ed72e9bf72b8e615dcc25a7e
[dragonfly.git] / tools / tools / net80211 / wlanwds / wlanwds.c
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  * $FreeBSD: src/tools/tools/net80211/wlanwds/wlanwds.c,v 1.3 2009/04/18 16:14:03 sam Exp $
30  */
31
32 /*
33  * Test app to demonstrate how to handle dynamic WDS links:
34  * o monitor 802.11 events for wds discovery events
35  * o create wds vap's in response to wds discovery events
36  *   and launch a script to handle adding the vap to the
37  *   bridge, etc.
38  * o destroy wds vap's when station leaves
39  */
40 #include <sys/param.h>
41 #include <sys/file.h>
42 #include <sys/socket.h>
43 #include <sys/ioctl.h>
44 #include <sys/sysctl.h>
45 #include <sys/types.h>
46
47 #include <net/if.h>
48 #include "net/if_media.h"
49 #include <net/route.h>
50 #include <net/if_dl.h>
51 #include <netinet/in.h>
52 #include <netinet/if_ether.h>
53 #include <netatalk/at.h>
54 #include "netproto/802_11/ieee80211_ioctl.h"
55 #include "netproto/802_11/ieee80211_dragonfly.h"
56 #include <arpa/inet.h>
57 #include <netdb.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
72 #define IEEE80211_ADDR_EQ(a1,a2)        (memcmp(a1,a2,IEEE80211_ADDR_LEN) == 0)
73 #define IEEE80211_ADDR_COPY(dst,src)    memcpy(dst,src,IEEE80211_ADDR_LEN)
74
75 struct wds {
76         struct wds *next;
77         uint8_t bssid[IEEE80211_ADDR_LEN];      /* bssid of associated sta */
78         char    ifname[IFNAMSIZ];               /* vap interface name */
79 };
80 static struct wds *wds;
81
82 static  const char *script = NULL;
83 static  char **ifnets;
84 static  int nifnets = 0;
85 static  int verbose = 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, struct wds *);
95 static  int wds_vap_destroy(const char *ifname);
96
97 static void
98 usage(const char *progname)
99 {
100         fprintf(stderr, "usage: %s [-fjtv] [-P pidfile] [-s <set_scriptname>] [ifnet0 ... | any]\n",
101                 progname);
102         exit(-1);
103 }
104
105 int
106 main(int argc, char *argv[])
107 {
108         const char *progname = argv[0];
109         const char *pidfile = NULL;
110         int s, c, logmask, bg = 1;
111         char msg[2048];
112
113         logmask = LOG_UPTO(LOG_INFO);
114         while ((c = getopt(argc, argv, "fjP:s:tv")) != -1)
115                 switch (c) {
116                 case 'f':
117                         bg = 0;
118                         break;
119                 case 'j':
120                         discover_on_join = 1;
121                         break;
122                 case 'P':
123                         pidfile = optarg;
124                         break;
125                 case 's':
126                         script = optarg;
127                         break;
128                 case 't':
129                         logmask = LOG_UPTO(LOG_ERR);
130                         break;
131                 case 'v':
132                         logmask = LOG_UPTO(LOG_DEBUG);
133                         break;
134                 case '?':
135                         usage(progname);
136                         /*NOTREACHED*/
137                 }
138         argc -= optind, argv += optind;
139         if (argc == 0) {
140                 fprintf(stderr, "%s: no ifnet's specified to monitor\n",
141                     progname);
142                 usage(progname);
143         }
144         ifnets = argv;
145         nifnets = argc;
146
147         s = socket(PF_ROUTE, SOCK_RAW, 0);
148         if (s < 0)
149                 err(EX_OSERR, "socket");
150         /*
151          * Scan for inherited state.
152          */
153         scanforvaps(s);
154
155         /* XXX what directory to work in? */
156         if (bg && daemon(0, 0) < 0)
157                 err(EX_OSERR, "daemon");
158
159         openlog("wlanwds", LOG_PID | LOG_CONS, LOG_DAEMON);
160         setlogmask(logmask);
161
162         for (;;) {
163                 ssize_t n = read(s, msg, sizeof(msg));
164                 handle_rtmsg((struct rt_msghdr *)msg, n);
165         }
166         return 0;
167 }
168
169 static const char *
170 ether_sprintf(const uint8_t mac[IEEE80211_ADDR_LEN])
171 {
172         static char buf[32];
173
174         snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
175                 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
176         return buf;
177 }
178
179 /*
180  * Fetch a vap's parent ifnet name.
181  */
182 static int
183 getparent(const char *ifname, char parent[IFNAMSIZ+1])
184 {
185         char oid[256];
186         int parentlen;
187
188         /* fetch parent interface name */
189         snprintf(oid, sizeof(oid), "net.wlan.%s.%%parent", ifname+4);
190         parentlen = IFNAMSIZ;
191         if (sysctlbyname(oid, parent, &parentlen, NULL, 0) < 0)
192                 return -1;
193         parent[parentlen] = '\0';
194         return 0;
195 }
196
197 /*
198  * Check if the specified ifnet is one we're supposed to monitor.
199  * The ifnet is assumed to be a vap; we find it's parent and check
200  * it against the set of ifnet's specified on the command line.
201  */
202 static int
203 checkifnet(const char *ifname, int complain)
204 {
205         char parent[256];
206         int i;
207
208         if (getparent(ifname, parent) < 0) {
209                 if (complain)
210                         syslog(LOG_ERR,
211                            "%s: no pointer to parent interface: %m", ifname);
212                 return 0;
213         }
214
215         for (i = 0; i < nifnets; i++)
216                 if (strcasecmp(ifnets[i], "any") == 0 ||
217                     strcmp(ifnets[i], parent) == 0)
218                         return 1;
219         syslog(LOG_DEBUG, "%s: parent %s not being monitored", ifname, parent);
220         return 0;
221 }
222
223 /*
224  * Return 1 if the specified ifnet is a WDS vap.
225  */
226 static int
227 iswdsvap(int s, const char *ifname)
228 {
229         struct ifmediareq ifmr;
230
231         memset(&ifmr, 0, sizeof(ifmr));
232         strncpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name));
233         if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0)
234                 err(-1, "%s: cannot get media", ifname);
235         return (ifmr.ifm_current & IFM_IEEE80211_WDS) != 0;
236 }
237
238 /*
239  * Fetch the bssid for an ifnet.  The caller is assumed
240  * to have already verified this is possible.
241  */
242 static void
243 getbssid(int s, const char *ifname, char bssid[IEEE80211_ADDR_LEN])
244 {
245         struct ieee80211req ireq;
246
247         memset(&ireq, 0, sizeof(ireq));
248         strncpy(ireq.i_name, ifname, sizeof(ireq.i_name));
249         ireq.i_type = IEEE80211_IOC_BSSID;
250         ireq.i_data = bssid;
251         ireq.i_len = IEEE80211_ADDR_LEN;
252         if (ioctl(s, SIOCG80211, &ireq) < 0)
253                 err(-1, "%s: cannot fetch bssid", ifname);
254 }
255
256 /*
257  * Scan the system for WDS vaps associated with the ifnet's we're
258  * supposed to monitor.  Any vaps are added to our internal table
259  * so we can find them (and destroy them) on station leave.
260  */
261 static void
262 scanforvaps(int s)
263 {
264         char ifname[IFNAMSIZ+1];
265         char bssid[IEEE80211_ADDR_LEN];
266         int i;
267
268         /* XXX brutal; should just walk sysctl tree */
269         for (i = 0; i < 128; i++) {
270                 snprintf(ifname, sizeof(ifname), "wlan%d", i);
271                 if (checkifnet(ifname, 0) && iswdsvap(s, ifname)) {
272                         struct wds *p = malloc(sizeof(struct wds));
273                         if (p == NULL)
274                                 err(-1, "%s: malloc failed", __func__);
275                         strlcpy(p->ifname, ifname, IFNAMSIZ);
276                         getbssid(s, ifname, p->bssid);
277                         p->next = wds;
278                         wds = p;
279
280                         syslog(LOG_INFO, "[%s] discover wds vap %s",
281                             ether_sprintf(bssid), ifname);
282                 }
283         }
284 }
285
286 /*
287  * Process a routing socket message.  We handle messages related
288  * to dynamic WDS:
289  * o on WDS discovery (rx of a 4-address frame with DWDS enabled)
290  *   we create a WDS vap for the specified mac address
291  * o on station leave we destroy any associated WDS vap
292  * o on ifnet destroy we update state if this is manual destroy of
293  *   a WDS vap in our table
294  * o if the -j option is supplied on the command line we create
295  *   WDS vaps on station join/rejoin, this is useful for some setups
296  *   where a WDS vap is required for 4-address traffic to flow
297  */
298 static void
299 handle_rtmsg(struct rt_msghdr *rtm, ssize_t msglen)
300 {
301         struct if_announcemsghdr *ifan;
302
303         if (rtm->rtm_version != RTM_VERSION) {
304                 syslog(LOG_ERR, "routing message version %d not understood",
305                     rtm->rtm_version);
306                 return;
307         }
308         switch (rtm->rtm_type) {
309         case RTM_IFANNOUNCE:
310                 ifan = (struct if_announcemsghdr *)rtm;
311                 switch (ifan->ifan_what) {
312                 case IFAN_ARRIVAL:
313                         syslog(LOG_DEBUG,
314                             "RTM_IFANNOUNCE: if# %d, what: arrival",
315                             ifan->ifan_index);
316                         break;
317                 case IFAN_DEPARTURE:
318                         syslog(LOG_DEBUG,
319                             "RTM_IFANNOUNCE: if# %d, what: departure",
320                             ifan->ifan_index);
321                         /* NB: ok to call w/ unmonitored ifnets */
322                         wds_destroy(ifan->ifan_name);
323                         break;
324                 }
325                 break;
326         case RTM_IEEE80211:
327 #define V(type) ((struct type *)(&ifan[1]))
328                 ifan = (struct if_announcemsghdr *)rtm;
329                 switch (ifan->ifan_what) {
330                 case RTM_IEEE80211_DISASSOC:
331                         if (!discover_on_join)
332                                 break;
333                         /* fall thru... */
334                 case RTM_IEEE80211_LEAVE:
335                         if (!checkifnet(ifan->ifan_name, 1))
336                                 break;
337                         syslog(LOG_INFO, "[%s] station leave",
338                             ether_sprintf(V(ieee80211_leave_event)->iev_addr));
339                         wds_leave(V(ieee80211_leave_event)->iev_addr);
340                         break;
341                 case RTM_IEEE80211_JOIN:
342                 case RTM_IEEE80211_REJOIN:
343                 case RTM_IEEE80211_ASSOC:
344                 case RTM_IEEE80211_REASSOC:
345                         if (!discover_on_join)
346                                 break;
347                         /* fall thru... */
348                 case RTM_IEEE80211_WDS:
349                         syslog(LOG_INFO, "[%s] wds discovery",
350                             ether_sprintf(V(ieee80211_wds_event)->iev_addr));
351                         if (!checkifnet(ifan->ifan_name, 1))
352                                 break;
353                         wds_discovery(ifan->ifan_name,
354                             V(ieee80211_wds_event)->iev_addr);
355                         break;
356                 }
357                 break;
358 #undef V
359         }
360 }
361
362 /*
363  * Handle WDS discovery; create a WDS vap for the specified bssid.
364  * If a vap already exists then do nothing (can happen when a flood
365  * of 4-address frames causes multiple events to be queued before
366  * we create a vap).
367  */
368 static void
369 wds_discovery(const char *ifname, const uint8_t bssid[IEEE80211_ADDR_LEN])
370 {
371         struct wds *p;
372         char parent[256];
373         char cmd[1024];
374         int status;
375
376         for (p = wds; p != NULL; p = p->next)
377                 if (IEEE80211_ADDR_EQ(p->bssid, bssid)) {
378                         syslog(LOG_INFO, "[%s] wds vap already created (%s)",
379                             ether_sprintf(bssid), ifname);
380                         return;
381                 }
382         if (getparent(ifname, parent) < 0) {
383                 syslog(LOG_ERR, "%s: no pointer to parent interface: %m",
384                     ifname);
385                 return;
386         }
387
388         p = malloc(sizeof(struct wds));
389         if (p == NULL) {
390                 syslog(LOG_ERR, "%s: malloc failed: %m", __func__);
391                 return;
392         }
393         IEEE80211_ADDR_COPY(p->bssid, bssid);
394         if (wds_vap_create(parent, p) < 0) {
395                 free(p);
396                 return;
397         }
398         /*
399          * Add to table and launch setup script.
400          */
401         p->next = wds;
402         wds = p;
403         syslog(LOG_INFO, "[%s] create wds vap %s", ether_sprintf(bssid),
404             p->ifname);
405         if (script != NULL) {
406                 snprintf(cmd, sizeof(cmd), "%s %s", script, p->ifname);
407                 status = system(cmd);
408                 if (status)
409                         syslog(LOG_ERR, "vap setup script %s exited with "
410                             "status %d", script, status);
411         }
412 }
413
414 /*
415  * Destroy a WDS vap (if known).
416  */
417 static void
418 wds_destroy(const char *ifname)
419 {
420         struct wds *p, **pp;
421
422         for (pp = &wds; (p = *pp) != NULL; pp = &p->next)
423                 if (strncmp(p->ifname, ifname, IFNAMSIZ) == 0)
424                         break;
425         if (p != NULL) {
426                 *pp = p->next;
427                 /* NB: vap already destroyed */
428                 free(p);
429                 return;
430         }
431 }
432
433 /*
434  * Handle a station leave event; destroy any associated WDS vap.
435  */
436 static void
437 wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN])
438 {
439         struct wds *p, **pp;
440
441         for (pp = &wds; (p = *pp) != NULL; pp = &p->next)
442                 if (IEEE80211_ADDR_EQ(p->bssid, bssid))
443                         break;
444         if (p != NULL) {
445                 *pp = p->next;
446                 if (wds_vap_destroy(p->ifname) >= 0)
447                         syslog(LOG_INFO, "[%s] wds vap %s destroyed",
448                             ether_sprintf(bssid), p->ifname);
449                 free(p);
450         }
451 }
452
453 static int
454 wds_vap_create(const char *parent, struct wds *p)
455 {
456         struct ieee80211_clone_params cp;
457         struct ifreq ifr;
458         int s, status;
459
460         memset(&cp, 0, sizeof(cp));
461         strncpy(cp.icp_parent, parent, IFNAMSIZ);
462         cp.icp_opmode = IEEE80211_M_WDS;
463         IEEE80211_ADDR_COPY(cp.icp_bssid, p->bssid);
464
465         memset(&ifr, 0, sizeof(ifr));
466         strncpy(ifr.ifr_name, "wlan", IFNAMSIZ);
467         ifr.ifr_data = (void *) &cp;
468
469         status = -1;
470         s = socket(AF_INET, SOCK_DGRAM, 0);
471         if (s >= 0) {
472                 if (ioctl(s, SIOCIFCREATE2, &ifr) >= 0) {
473                         strlcpy(p->ifname, ifr.ifr_name, IFNAMSIZ);
474                         status = 0;
475                 } else {
476                         syslog(LOG_ERR, "SIOCIFCREATE2("
477                             "mode %u flags 0x%x parent %s bssid %s): %m",
478                             cp.icp_opmode, cp.icp_flags, parent,
479                             ether_sprintf(cp.icp_bssid));
480                 }
481                 close(s);
482         } else
483                 syslog(LOG_ERR, "socket(SOCK_DRAGM): %m");
484         return status;
485 }
486
487 static int
488 wds_vap_destroy(const char *ifname)
489 {
490         struct ieee80211req ifr;
491         int s, status;
492
493         s = socket(AF_INET, SOCK_DGRAM, 0);
494         if (s < 0) {
495                 syslog(LOG_ERR, "socket(SOCK_DRAGM): %m");
496                 return -1;
497         }
498         memset(&ifr, 0, sizeof(ifr));
499         strncpy(ifr.i_name, ifname, IFNAMSIZ);
500         if (ioctl(s, SIOCIFDESTROY, &ifr) < 0) {
501                 syslog(LOG_ERR, "ioctl(SIOCIFDESTROY): %m");
502                 status = -1;
503         } else
504                 status = 0;
505         close(s);
506         return status;
507 }