1153317Ssam/*-
2174244Ssam * Copyright (c) 2002-2007 Sam Leffler, Errno Consulting
3153317Ssam * All rights reserved.
4153317Ssam *
5153317Ssam * Redistribution and use in source and binary forms, with or without
6153317Ssam * modification, are permitted provided that the following conditions
7153317Ssam * are met:
8153317Ssam * 1. Redistributions of source code must retain the above copyright
9153317Ssam *    notice, this list of conditions and the following disclaimer,
10153317Ssam *    without modification.
11153317Ssam * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12153317Ssam *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
13153317Ssam *    redistribution must be conditioned upon including a substantially
14153317Ssam *    similar Disclaimer requirement for further binary redistribution.
15153317Ssam *
16153317Ssam * NO WARRANTY
17153317Ssam * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18153317Ssam * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19153317Ssam * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
20153317Ssam * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21153317Ssam * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
22153317Ssam * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23153317Ssam * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24153317Ssam * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25153317Ssam * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26153317Ssam * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27153317Ssam * THE POSSIBILITY OF SUCH DAMAGES.
28153317Ssam *
29153317Ssam * $FreeBSD$
30153317Ssam */
31153317Ssam
32153317Ssam/*
33153317Ssam * Monitor 802.11 events using a routing socket.
34153317Ssam * Code liberaly swiped from route(8).
35153317Ssam */
36153317Ssam#include <sys/param.h>
37153317Ssam#include <sys/file.h>
38153317Ssam#include <sys/socket.h>
39153317Ssam#include <sys/ioctl.h>
40153317Ssam#include <sys/sysctl.h>
41153317Ssam#include <sys/types.h>
42153317Ssam
43153317Ssam#include <net/if.h>
44153317Ssam#include <net/route.h>
45153317Ssam#include <net/if_dl.h>
46153317Ssam#include <netinet/in.h>
47153317Ssam#include <netinet/if_ether.h>
48160053Ssam#ifdef __NetBSD__
49160053Ssam#include <net80211/ieee80211_netbsd.h>
50160053Ssam#elif __FreeBSD__
51153317Ssam#include <net80211/ieee80211_freebsd.h>
52160053Ssam#else
53160053Ssam#error	"No support for your operating system!"
54160053Ssam#endif
55153317Ssam#include <arpa/inet.h>
56153317Ssam#include <netdb.h>
57153317Ssam
58153317Ssam#include <ctype.h>
59153317Ssam#include <err.h>
60153317Ssam#include <errno.h>
61153317Ssam#include <paths.h>
62153317Ssam#include <stdio.h>
63153317Ssam#include <stdlib.h>
64153317Ssam#include <string.h>
65153317Ssam#include <sysexits.h>
66153317Ssam#include <unistd.h>
67153317Ssam#include <ifaddrs.h>
68153317Ssam
69178698Ssam/* XXX */
70178698Ssamenum ieee80211_notify_cac_event {
71178698Ssam	IEEE80211_NOTIFY_CAC_START  = 0, /* CAC timer started */
72178698Ssam	IEEE80211_NOTIFY_CAC_STOP   = 1, /* CAC intentionally stopped */
73178698Ssam	IEEE80211_NOTIFY_CAC_RADAR  = 2, /* CAC stopped due to radar detectio */
74178698Ssam	IEEE80211_NOTIFY_CAC_EXPIRE = 3, /* CAC expired w/o radar */
75178698Ssam};
76178698Ssam
77153317Ssamstatic	void print_rtmsg(struct rt_msghdr *rtm, int msglen);
78153317Ssam
79153317Ssamint	nflag = 0;
80153317Ssam
81153317Ssamint
82153317Ssammain(int argc, char *argv[])
83153317Ssam{
84153317Ssam	int n, s;
85153317Ssam	char msg[2048];
86153317Ssam
87153317Ssam	s = socket(PF_ROUTE, SOCK_RAW, 0);
88153317Ssam	if (s < 0)
89153317Ssam		err(EX_OSERR, "socket");
90153317Ssam	for(;;) {
91153317Ssam		n = read(s, msg, 2048);
92153317Ssam		print_rtmsg((struct rt_msghdr *)msg, n);
93153317Ssam	}
94153317Ssam	return 0;
95153317Ssam}
96153317Ssam
97153317Ssamstatic void
98177504Ssambprintf(FILE *fp, int b, char *s)
99153317Ssam{
100153317Ssam	int i;
101153317Ssam	int gotsome = 0;
102153317Ssam
103153317Ssam	if (b == 0)
104153317Ssam		return;
105153317Ssam	while ((i = *s++) != 0) {
106153317Ssam		if (b & (1 << (i-1))) {
107153317Ssam			if (gotsome == 0)
108153317Ssam				i = '<';
109153317Ssam			else
110153317Ssam				i = ',';
111153317Ssam			(void) putc(i, fp);
112153317Ssam			gotsome = 1;
113153317Ssam			for (; (i = *s) > 32; s++)
114153317Ssam				(void) putc(i, fp);
115153317Ssam		} else
116153317Ssam			while (*s > 32)
117153317Ssam				s++;
118153317Ssam	}
119153317Ssam	if (gotsome)
120153317Ssam		putc('>', fp);
121153317Ssam}
122153317Ssam
123153317Ssamchar metricnames[] =
124153317Ssam"\011pksent\010rttvar\7rtt\6ssthresh\5sendpipe\4recvpipe\3expire\2hopcount"
125153317Ssam"\1mtu";
126153317Ssamchar routeflags[] =
127153317Ssam"\1UP\2GATEWAY\3HOST\4REJECT\5DYNAMIC\6MODIFIED\7DONE\010MASK_PRESENT"
128153317Ssam"\011CLONING\012XRESOLVE\013LLINFO\014STATIC\015BLACKHOLE\016b016"
129153317Ssam"\017PROTO2\020PROTO1\021PRCLONING\022WASCLONED\023PROTO3\024CHAINDELETE"
130153317Ssam"\025PINNED\026LOCAL\027BROADCAST\030MULTICAST";
131153317Ssamchar ifnetflags[] =
132153317Ssam"\1UP\2BROADCAST\3DEBUG\4LOOPBACK\5PTP\6b6\7RUNNING\010NOARP"
133153317Ssam"\011PPROMISC\012ALLMULTI\013OACTIVE\014SIMPLEX\015LINK0\016LINK1"
134153317Ssam"\017LINK2\020MULTICAST";
135153317Ssamchar addrnames[] =
136153317Ssam"\1DST\2GATEWAY\3NETMASK\4GENMASK\5IFP\6IFA\7AUTHOR\010BRD";
137153317Ssam
138177504Ssamstatic const char *
139177504Ssamroutename(struct sockaddr *sa)
140153317Ssam{
141153317Ssam	char *cp;
142153317Ssam	static char line[MAXHOSTNAMELEN + 1];
143153317Ssam	struct hostent *hp;
144153317Ssam	static char domain[MAXHOSTNAMELEN + 1];
145153317Ssam	static int first = 1, n;
146153317Ssam
147153317Ssam	if (first) {
148153317Ssam		first = 0;
149153317Ssam		if (gethostname(domain, MAXHOSTNAMELEN) == 0 &&
150153317Ssam		    (cp = strchr(domain, '.'))) {
151153317Ssam			domain[MAXHOSTNAMELEN] = '\0';
152153317Ssam			(void) strcpy(domain, cp + 1);
153153317Ssam		} else
154153317Ssam			domain[0] = 0;
155153317Ssam	}
156153317Ssam
157153317Ssam	if (sa->sa_len == 0)
158153317Ssam		strcpy(line, "default");
159153317Ssam	else switch (sa->sa_family) {
160153317Ssam
161153317Ssam	case AF_INET:
162153317Ssam	    {	struct in_addr in;
163153317Ssam		in = ((struct sockaddr_in *)sa)->sin_addr;
164153317Ssam
165153317Ssam		cp = 0;
166153317Ssam		if (in.s_addr == INADDR_ANY || sa->sa_len < 4)
167153317Ssam			cp = "default";
168153317Ssam		if (cp == 0 && !nflag) {
169153317Ssam			hp = gethostbyaddr((char *)&in, sizeof (struct in_addr),
170153317Ssam				AF_INET);
171153317Ssam			if (hp) {
172153317Ssam				if ((cp = strchr(hp->h_name, '.')) &&
173153317Ssam				    !strcmp(cp + 1, domain))
174153317Ssam					*cp = 0;
175153317Ssam				cp = hp->h_name;
176153317Ssam			}
177153317Ssam		}
178153317Ssam		if (cp) {
179153317Ssam			strncpy(line, cp, sizeof(line) - 1);
180153317Ssam			line[sizeof(line) - 1] = '\0';
181153317Ssam		} else
182153317Ssam			(void) sprintf(line, "%s", inet_ntoa(in));
183153317Ssam		break;
184153317Ssam	    }
185153317Ssam
186153317Ssam#ifdef INET6
187153317Ssam	case AF_INET6:
188153317Ssam	{
189153317Ssam		struct sockaddr_in6 sin6; /* use static var for safety */
190153317Ssam		int niflags = 0;
191153317Ssam#ifdef NI_WITHSCOPEID
192153317Ssam		niflags = NI_WITHSCOPEID;
193153317Ssam#endif
194153317Ssam
195153317Ssam		memset(&sin6, 0, sizeof(sin6));
196153317Ssam		memcpy(&sin6, sa, sa->sa_len);
197153317Ssam		sin6.sin6_len = sizeof(struct sockaddr_in6);
198153317Ssam		sin6.sin6_family = AF_INET6;
199153317Ssam#ifdef __KAME__
200153317Ssam		if (sa->sa_len == sizeof(struct sockaddr_in6) &&
201153317Ssam		    (IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr) ||
202153317Ssam		     IN6_IS_ADDR_MC_LINKLOCAL(&sin6.sin6_addr)) &&
203153317Ssam		    sin6.sin6_scope_id == 0) {
204153317Ssam			sin6.sin6_scope_id =
205153317Ssam			    ntohs(*(u_int16_t *)&sin6.sin6_addr.s6_addr[2]);
206153317Ssam			sin6.sin6_addr.s6_addr[2] = 0;
207153317Ssam			sin6.sin6_addr.s6_addr[3] = 0;
208153317Ssam		}
209153317Ssam#endif
210153317Ssam		if (nflag)
211153317Ssam			niflags |= NI_NUMERICHOST;
212153317Ssam		if (getnameinfo((struct sockaddr *)&sin6, sin6.sin6_len,
213153317Ssam		    line, sizeof(line), NULL, 0, niflags) != 0)
214153317Ssam			strncpy(line, "invalid", sizeof(line));
215153317Ssam
216153317Ssam		return(line);
217153317Ssam	}
218153317Ssam#endif
219153317Ssam
220153317Ssam	case AF_LINK:
221153317Ssam		return (link_ntoa((struct sockaddr_dl *)sa));
222153317Ssam
223153317Ssam	default:
224153317Ssam	    {	u_short *s = (u_short *)sa;
225153317Ssam		u_short *slim = s + ((sa->sa_len + 1) >> 1);
226153317Ssam		char *cp = line + sprintf(line, "(%d)", sa->sa_family);
227153317Ssam		char *cpe = line + sizeof(line);
228153317Ssam
229153317Ssam		while (++s < slim && cp < cpe) /* start with sa->sa_data */
230153317Ssam			if ((n = snprintf(cp, cpe - cp, " %x", *s)) > 0)
231153317Ssam				cp += n;
232153317Ssam			else
233153317Ssam				*cp = '\0';
234153317Ssam		break;
235153317Ssam	    }
236153317Ssam	}
237153317Ssam	return (line);
238153317Ssam}
239153317Ssam
240160053Ssam#ifndef SA_SIZE
241160053Ssam/*
242160053Ssam * This macro returns the size of a struct sockaddr when passed
243160053Ssam * through a routing socket. Basically we round up sa_len to
244160053Ssam * a multiple of sizeof(long), with a minimum of sizeof(long).
245160053Ssam * The check for a NULL pointer is just a convenience, probably never used.
246160053Ssam * The case sa_len == 0 should only apply to empty structures.
247160053Ssam */
248160053Ssam#define SA_SIZE(sa)						\
249160053Ssam    (  (!(sa) || ((struct sockaddr *)(sa))->sa_len == 0) ?	\
250160053Ssam	sizeof(long)		:				\
251160053Ssam	1 + ( (((struct sockaddr *)(sa))->sa_len - 1) | (sizeof(long) - 1) ) )
252160053Ssam#endif
253153317Ssam
254153317Ssamstatic void
255153317Ssampmsg_addrs(char *cp, int addrs)
256153317Ssam{
257153317Ssam	struct sockaddr *sa;
258153317Ssam	int i;
259153317Ssam
260153317Ssam	if (addrs == 0) {
261153317Ssam		(void) putchar('\n');
262153317Ssam		return;
263153317Ssam	}
264153317Ssam	printf("\nsockaddrs: ");
265153317Ssam	bprintf(stdout, addrs, addrnames);
266153317Ssam	putchar('\n');
267153317Ssam	for (i = 1; i; i <<= 1)
268153317Ssam		if (i & addrs) {
269153317Ssam			sa = (struct sockaddr *)cp;
270153317Ssam			printf(" %s", routename(sa));
271153317Ssam			cp += SA_SIZE(sa);
272153317Ssam		}
273153317Ssam	putchar('\n');
274153317Ssam}
275153317Ssam
276153317Ssamstatic const char *
277153317Ssamether_sprintf(const uint8_t mac[6])
278153317Ssam{
279153317Ssam	static char buf[32];
280153317Ssam
281153317Ssam	snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
282153317Ssam		mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
283153317Ssam	return buf;
284153317Ssam}
285153317Ssam
286153317Ssamstatic void
287153317Ssamprint_rtmsg(struct rt_msghdr *rtm, int msglen)
288153317Ssam{
289153317Ssam	struct if_msghdr *ifm;
290153317Ssam	struct if_announcemsghdr *ifan;
291153317Ssam	time_t now = time(NULL);
292153317Ssam	char *cnow = ctime(&now);
293153317Ssam
294153317Ssam	if (rtm->rtm_version != RTM_VERSION) {
295153317Ssam		(void) printf("routing message version %d not understood\n",
296153317Ssam		    rtm->rtm_version);
297153317Ssam		return;
298153317Ssam	}
299153317Ssam	switch (rtm->rtm_type) {
300153317Ssam	case RTM_IFINFO:
301153317Ssam		ifm = (struct if_msghdr *)rtm;
302153317Ssam		printf("%.19s RTM_IFINFO: if# %d, ",
303153317Ssam			cnow, ifm->ifm_index);
304153317Ssam		switch (ifm->ifm_data.ifi_link_state) {
305153317Ssam		case LINK_STATE_DOWN:
306177504Ssam			printf("link: down, flags:");
307153317Ssam			break;
308153317Ssam		case LINK_STATE_UP:
309177504Ssam			printf("link: up, flags:");
310153317Ssam			break;
311153317Ssam		default:
312177504Ssam			printf("link: unknown<%d>, flags:",
313177504Ssam			    ifm->ifm_data.ifi_link_state);
314153317Ssam			break;
315153317Ssam		}
316153317Ssam		bprintf(stdout, ifm->ifm_flags, ifnetflags);
317153317Ssam		pmsg_addrs((char *)(ifm + 1), ifm->ifm_addrs);
318179122Sthompsa		fflush(stdout);
319153317Ssam		break;
320153317Ssam	case RTM_IFANNOUNCE:
321153317Ssam		ifan = (struct if_announcemsghdr *)rtm;
322153317Ssam		printf("%.19s RTM_IFANNOUNCE: if# %d, what: ",
323153317Ssam			cnow, ifan->ifan_index);
324153317Ssam		switch (ifan->ifan_what) {
325153317Ssam		case IFAN_ARRIVAL:
326153317Ssam			printf("arrival");
327153317Ssam			break;
328153317Ssam		case IFAN_DEPARTURE:
329153317Ssam			printf("departure");
330153317Ssam			break;
331153317Ssam		default:
332153317Ssam			printf("#%d", ifan->ifan_what);
333153317Ssam			break;
334153317Ssam		}
335153317Ssam		printf("\n");
336179122Sthompsa		fflush(stdout);
337153317Ssam		break;
338153317Ssam	case RTM_IEEE80211:
339153317Ssam#define	V(type)	((struct type *)(&ifan[1]))
340153317Ssam		ifan = (struct if_announcemsghdr *)rtm;
341177504Ssam		printf("%.19s RTM_IEEE80211: if# %d, ", cnow, ifan->ifan_index);
342153317Ssam		switch (ifan->ifan_what) {
343153317Ssam		case RTM_IEEE80211_ASSOC:
344153317Ssam			printf("associate with %s",
345153317Ssam			    ether_sprintf(V(ieee80211_join_event)->iev_addr));
346153317Ssam			break;
347153317Ssam		case RTM_IEEE80211_REASSOC:
348153317Ssam			printf("reassociate with %s",
349153317Ssam			    ether_sprintf(V(ieee80211_join_event)->iev_addr));
350153317Ssam			break;
351153317Ssam		case RTM_IEEE80211_DISASSOC:
352153317Ssam			printf("disassociate");
353153317Ssam			break;
354153317Ssam		case RTM_IEEE80211_JOIN:
355153317Ssam		case RTM_IEEE80211_REJOIN:
356153317Ssam			printf("%s station %sjoin",
357160053Ssam			    ether_sprintf(V(ieee80211_join_event)->iev_addr),
358160053Ssam			    ifan->ifan_what == RTM_IEEE80211_REJOIN ? "re" : ""
359160053Ssam			);
360153317Ssam			break;
361153317Ssam		case RTM_IEEE80211_LEAVE:
362153317Ssam			printf("%s station leave",
363153317Ssam			    ether_sprintf(V(ieee80211_leave_event)->iev_addr));
364153317Ssam			break;
365153317Ssam		case RTM_IEEE80211_SCAN:
366153317Ssam			printf("scan complete");
367153317Ssam			break;
368153317Ssam		case RTM_IEEE80211_REPLAY:
369153317Ssam			printf("replay failure: src %s "
370153317Ssam			    , ether_sprintf(V(ieee80211_replay_event)->iev_src)
371153317Ssam			);
372153317Ssam			printf("dst %s cipher %u keyix %u keyrsc %llu rsc %llu"
373153317Ssam			    , ether_sprintf(V(ieee80211_replay_event)->iev_dst)
374153317Ssam			    , V(ieee80211_replay_event)->iev_cipher
375153317Ssam			    , V(ieee80211_replay_event)->iev_keyix
376153317Ssam			    , V(ieee80211_replay_event)->iev_keyrsc
377153317Ssam			    , V(ieee80211_replay_event)->iev_rsc
378153317Ssam			);
379153317Ssam			break;
380153317Ssam		case RTM_IEEE80211_MICHAEL:
381153317Ssam			printf("michael failure: src %s "
382153317Ssam			    , ether_sprintf(V(ieee80211_michael_event)->iev_src)
383153317Ssam			);
384153317Ssam			printf("dst %s cipher %u keyix %u"
385153317Ssam			    , ether_sprintf(V(ieee80211_michael_event)->iev_dst)
386153317Ssam			    , V(ieee80211_michael_event)->iev_cipher
387153317Ssam			    , V(ieee80211_michael_event)->iev_keyix
388153317Ssam			);
389153317Ssam			break;
390178698Ssam		case RTM_IEEE80211_WDS:
391178698Ssam			printf("%s wds discovery",
392178698Ssam			    ether_sprintf(V(ieee80211_wds_event)->iev_addr));
393178698Ssam			break;
394178698Ssam		case RTM_IEEE80211_CSA:
395178698Ssam			printf("channel switch announcement: channel %u (%u MHz flags 0x%x) mode %d count %d"
396178698Ssam			    , V(ieee80211_csa_event)->iev_ieee
397178698Ssam			    , V(ieee80211_csa_event)->iev_freq
398178698Ssam			    , V(ieee80211_csa_event)->iev_flags
399178698Ssam			    , V(ieee80211_csa_event)->iev_mode
400178698Ssam			    , V(ieee80211_csa_event)->iev_count
401178698Ssam			);
402178698Ssam			break;
403178698Ssam		case RTM_IEEE80211_CAC:
404178698Ssam			printf("channel availability check "
405178698Ssam			    "(channel %u, %u MHz flags 0x%x) "
406178698Ssam			    , V(ieee80211_cac_event)->iev_ieee
407178698Ssam			    , V(ieee80211_cac_event)->iev_freq
408178698Ssam			    , V(ieee80211_cac_event)->iev_flags
409178698Ssam			);
410178698Ssam			switch (V(ieee80211_cac_event)->iev_type) {
411178698Ssam			case IEEE80211_NOTIFY_CAC_START:
412178698Ssam				printf("start timer");
413178698Ssam				break;
414178698Ssam			case IEEE80211_NOTIFY_CAC_STOP:
415178698Ssam				printf("stop timer");
416178698Ssam				break;
417178698Ssam			case IEEE80211_NOTIFY_CAC_EXPIRE:
418178698Ssam				printf("timer expired");
419178698Ssam				break;
420178698Ssam			case IEEE80211_NOTIFY_CAC_RADAR:
421178698Ssam				printf("radar detected");
422178698Ssam				break;
423178698Ssam			default:
424178698Ssam				printf("unknown type %d",
425178698Ssam				   V(ieee80211_cac_event)->iev_type);
426178698Ssam				break;
427178698Ssam			}
428178698Ssam			break;
429178698Ssam		case RTM_IEEE80211_DEAUTH:
430178698Ssam			printf("%s wds deauth",
431178698Ssam			    ether_sprintf(V(ieee80211_deauth_event)->iev_addr));
432178698Ssam			break;
433178698Ssam		case RTM_IEEE80211_AUTH:
434178698Ssam			printf("%s node authenticate",
435178698Ssam			    ether_sprintf(V(ieee80211_auth_event)->iev_addr));
436178698Ssam			break;
437178698Ssam		case RTM_IEEE80211_COUNTRY:
438178698Ssam			printf("%s adopt country code '%c%c'",
439178698Ssam			    ether_sprintf(V(ieee80211_country_event)->iev_addr),
440178698Ssam			    V(ieee80211_country_event)->iev_cc[0],
441178698Ssam			    V(ieee80211_country_event)->iev_cc[1]);
442178698Ssam			break;
443178698Ssam		case RTM_IEEE80211_RADIO:
444178698Ssam			printf("radio %s",
445178698Ssam			    V(ieee80211_radio_event)->iev_state ? "ON" : "OFF");
446178698Ssam			break;
447153317Ssam		default:
448177504Ssam			printf("what: #%d", ifan->ifan_what);
449153317Ssam			break;
450153317Ssam		}
451153317Ssam		printf("\n");
452179122Sthompsa		fflush(stdout);
453153317Ssam		break;
454153317Ssam#undef V
455153317Ssam	}
456153317Ssam}
457