nexmon – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 /*
2 * Wireless Tools
3 *
4 * Jean II - HPL 99->04
5 *
6 * Main code for "iwevent". This listent for wireless events on rtnetlink.
7 * You need to link this code against "iwcommon.c" and "-lm".
8 *
9 * Part of this code is from Alexey Kuznetsov, part is from Casey Carter,
10 * I've just put the pieces together...
11 * By the way, if you know a way to remove the root restrictions, tell me
12 * about it...
13 *
14 * This file is released under the GPL license.
15 * Copyright (c) 1997-2004 Jean Tourrilhes <jt@hpl.hp.com>
16 */
17  
18 /***************************** INCLUDES *****************************/
19  
20 #include "iwlib.h" /* Header */
21  
22 #include <linux/netlink.h>
23 #include <linux/rtnetlink.h>
24  
25 #include <getopt.h>
26 #include <time.h>
27 #include <sys/time.h>
28  
29 /* Ugly backward compatibility :-( */
30 #ifndef IFLA_WIRELESS
31 #define IFLA_WIRELESS (IFLA_MASTER + 1)
32 #endif /* IFLA_WIRELESS */
33  
34 /****************************** TYPES ******************************/
35  
36 /*
37 * Static information about wireless interface.
38 * We cache this info for performance reason.
39 */
40 typedef struct wireless_iface
41 {
42 /* Linked list */
43 struct wireless_iface * next;
44  
45 /* Interface identification */
46 int ifindex; /* Interface index == black magic */
47  
48 /* Interface data */
49 char ifname[IFNAMSIZ + 1]; /* Interface name */
50 struct iw_range range; /* Wireless static data */
51 int has_range;
52 } wireless_iface;
53  
54 /**************************** VARIABLES ****************************/
55  
56 /* Cache of wireless interfaces */
57 struct wireless_iface * interface_cache = NULL;
58  
59 /************************ RTNETLINK HELPERS ************************/
60 /*
61 * The following code is extracted from :
62 * ----------------------------------------------
63 * libnetlink.c RTnetlink service routines.
64 *
65 * This program is free software; you can redistribute it and/or
66 * modify it under the terms of the GNU General Public License
67 * as published by the Free Software Foundation; either version
68 * 2 of the License, or (at your option) any later version.
69 *
70 * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
71 * -----------------------------------------------
72 */
73  
74 struct rtnl_handle
75 {
76 int fd;
77 struct sockaddr_nl local;
78 struct sockaddr_nl peer;
79 __u32 seq;
80 __u32 dump;
81 };
82  
83 static inline void rtnl_close(struct rtnl_handle *rth)
84 {
85 close(rth->fd);
86 }
87  
88 static inline int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions)
89 {
90 int addr_len;
91  
92 memset(rth, 0, sizeof(rth));
93  
94 rth->fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
95 if (rth->fd < 0) {
96 perror("Cannot open netlink socket");
97 return -1;
98 }
99  
100 memset(&rth->local, 0, sizeof(rth->local));
101 rth->local.nl_family = AF_NETLINK;
102 rth->local.nl_groups = subscriptions;
103  
104 if (bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local)) < 0) {
105 perror("Cannot bind netlink socket");
106 return -1;
107 }
108 addr_len = sizeof(rth->local);
109 if (getsockname(rth->fd, (struct sockaddr*)&rth->local,
110 (socklen_t *) &addr_len) < 0) {
111 perror("Cannot getsockname");
112 return -1;
113 }
114 if (addr_len != sizeof(rth->local)) {
115 fprintf(stderr, "Wrong address length %d\n", addr_len);
116 return -1;
117 }
118 if (rth->local.nl_family != AF_NETLINK) {
119 fprintf(stderr, "Wrong address family %d\n", rth->local.nl_family);
120 return -1;
121 }
122 rth->seq = time(NULL);
123 return 0;
124 }
125  
126 /******************* WIRELESS INTERFACE DATABASE *******************/
127 /*
128 * We keep a few information about each wireless interface on the
129 * system. This avoid to query this info at each event, therefore
130 * reducing overhead.
131 *
132 * Each interface is indexed by the 'ifindex'. As opposed to interface
133 * names, 'ifindex' are never reused (even if you reactivate the same
134 * hardware), so the data we cache will never apply to the wrong
135 * interface.
136 * Because of that, we are pretty lazy when it come to purging the
137 * cache...
138 */
139  
140 /*------------------------------------------------------------------*/
141 /*
142 * Get name of interface based on interface index...
143 */
144 static inline int
145 index2name(int skfd,
146 int ifindex,
147 char * name)
148 {
149 struct ifreq irq;
150 int ret = 0;
151  
152 memset(name, 0, IFNAMSIZ + 1);
153  
154 /* Get interface name */
155 irq.ifr_ifindex = ifindex;
156 if(ioctl(skfd, SIOCGIFNAME, &irq) < 0)
157 ret = -1;
158 else
159 strncpy(name, irq.ifr_name, IFNAMSIZ);
160  
161 return(ret);
162 }
163  
164 /*------------------------------------------------------------------*/
165 /*
166 * Get interface data from cache or live interface
167 */
168 static struct wireless_iface *
169 iw_get_interface_data(int ifindex)
170 {
171 struct wireless_iface * curr;
172 int skfd = -1; /* ioctl socket */
173  
174 /* Search for it in the database */
175 curr = interface_cache;
176 while(curr != NULL)
177 {
178 /* Match ? */
179 if(curr->ifindex == ifindex)
180 {
181 //printf("Cache : found %d-%s\n", curr->ifindex, curr->ifname);
182  
183 /* Return */
184 return(curr);
185 }
186 /* Next entry */
187 curr = curr->next;
188 }
189  
190 /* Create a channel to the NET kernel. Doesn't happen too often, so
191 * socket creation overhead is minimal... */
192 if((skfd = iw_sockets_open()) < 0)
193 {
194 perror("iw_sockets_open");
195 return(NULL);
196 }
197  
198 /* Create new entry, zero, init */
199 curr = calloc(1, sizeof(struct wireless_iface));
200 if(!curr)
201 {
202 fprintf(stderr, "Malloc failed\n");
203 return(NULL);
204 }
205 curr->ifindex = ifindex;
206  
207 /* Extract static data */
208 if(index2name(skfd, ifindex, curr->ifname) < 0)
209 {
210 perror("index2name");
211 free(curr);
212 return(NULL);
213 }
214 curr->has_range = (iw_get_range_info(skfd, curr->ifname, &curr->range) >= 0);
215 //printf("Cache : create %d-%s\n", curr->ifindex, curr->ifname);
216  
217 /* Done */
218 iw_sockets_close(skfd);
219  
220 /* Link it */
221 curr->next = interface_cache;
222 interface_cache = curr;
223  
224 return(curr);
225 }
226  
227 /*------------------------------------------------------------------*/
228 /*
229 * Remove interface data from cache (if it exist)
230 */
231 static void
232 iw_del_interface_data(int ifindex)
233 {
234 struct wireless_iface * curr;
235 struct wireless_iface * prev = NULL;
236 struct wireless_iface * next;
237  
238 /* Go through the list, find the interface, kills it */
239 curr = interface_cache;
240 while(curr)
241 {
242 next = curr->next;
243  
244 /* Got a match ? */
245 if(curr->ifindex == ifindex)
246 {
247 /* Unlink. Root ? */
248 if(!prev)
249 interface_cache = next;
250 else
251 prev->next = next;
252 //printf("Cache : purge %d-%s\n", curr->ifindex, curr->ifname);
253  
254 /* Destroy */
255 free(curr);
256 }
257 else
258 {
259 /* Keep as previous */
260 prev = curr;
261 }
262  
263 /* Next entry */
264 curr = next;
265 }
266 }
267  
268 /********************* WIRELESS EVENT DECODING *********************/
269 /*
270 * Parse the Wireless Event and print it out
271 */
272  
273 /*------------------------------------------------------------------*/
274 /*
275 * Dump a buffer as a serie of hex
276 * Maybe should go in iwlib...
277 * Maybe we should have better formatting like iw_print_key...
278 */
279 static char *
280 iw_hexdump(char * buf,
281 size_t buflen,
282 const unsigned char *data,
283 size_t datalen)
284 {
285 size_t i;
286 char * pos = buf;
287  
288 for(i = 0; i < datalen; i++)
289 pos += snprintf(pos, buf + buflen - pos, "%02X", data[i]);
290 return buf;
291 }
292  
293 /*------------------------------------------------------------------*/
294 /*
295 * Print one element from the scanning results
296 */
297 static inline int
298 print_event_token(struct iw_event * event, /* Extracted token */
299 struct iw_range * iw_range, /* Range info */
300 int has_range)
301 {
302 char buffer[128]; /* Temporary buffer */
303 char buffer2[30]; /* Temporary buffer */
304 char * prefix = (IW_IS_GET(event->cmd) ? "New" : "Set");
305  
306 /* Now, let's decode the event */
307 switch(event->cmd)
308 {
309 /* ----- set events ----- */
310 /* Events that result from a "SET XXX" operation by the user */
311 case SIOCSIWNWID:
312 if(event->u.nwid.disabled)
313 printf("Set NWID:off/any\n");
314 else
315 printf("Set NWID:%X\n", event->u.nwid.value);
316 break;
317 case SIOCSIWFREQ:
318 case SIOCGIWFREQ:
319 {
320 double freq; /* Frequency/channel */
321 int channel = -1; /* Converted to channel */
322 freq = iw_freq2float(&(event->u.freq));
323 if(has_range)
324 {
325 if(freq < KILO)
326 /* Convert channel to frequency if possible */
327 channel = iw_channel_to_freq((int) freq, &freq, iw_range);
328 else
329 /* Convert frequency to channel if possible */
330 channel = iw_freq_to_channel(freq, iw_range);
331 }
332 iw_print_freq(buffer, sizeof(buffer),
333 freq, channel, event->u.freq.flags);
334 printf("%s %s\n", prefix, buffer);
335 }
336 break;
337 case SIOCSIWMODE:
338 printf("Set Mode:%s\n",
339 iw_operation_mode[event->u.mode]);
340 break;
341 case SIOCSIWESSID:
342 case SIOCGIWESSID:
343 {
344 char essid[IW_ESSID_MAX_SIZE+1];
345 memset(essid, '\0', sizeof(essid));
346 if((event->u.essid.pointer) && (event->u.essid.length))
347 memcpy(essid, event->u.essid.pointer, event->u.essid.length);
348 if(event->u.essid.flags)
349 {
350 /* Does it have an ESSID index ? */
351 if((event->u.essid.flags & IW_ENCODE_INDEX) > 1)
352 printf("%s ESSID:\"%s\" [%d]\n", prefix, essid,
353 (event->u.essid.flags & IW_ENCODE_INDEX));
354 else
355 printf("%s ESSID:\"%s\"\n", prefix, essid);
356 }
357 else
358 printf("%s ESSID:off/any\n", prefix);
359 }
360 break;
361 case SIOCSIWENCODE:
362 {
363 unsigned char key[IW_ENCODING_TOKEN_MAX];
364 if(event->u.data.pointer)
365 memcpy(key, event->u.data.pointer, event->u.data.length);
366 else
367 event->u.data.flags |= IW_ENCODE_NOKEY;
368 printf("Set Encryption key:");
369 if(event->u.data.flags & IW_ENCODE_DISABLED)
370 printf("off\n");
371 else
372 {
373 /* Display the key */
374 iw_print_key(buffer, sizeof(buffer), key, event->u.data.length,
375 event->u.data.flags);
376 printf("%s", buffer);
377  
378 /* Other info... */
379 if((event->u.data.flags & IW_ENCODE_INDEX) > 1)
380 printf(" [%d]", event->u.data.flags & IW_ENCODE_INDEX);
381 if(event->u.data.flags & IW_ENCODE_RESTRICTED)
382 printf(" Security mode:restricted");
383 if(event->u.data.flags & IW_ENCODE_OPEN)
384 printf(" Security mode:open");
385 printf("\n");
386 }
387 }
388 break;
389 /* ----- driver events ----- */
390 /* Events generated by the driver when something important happens */
391 case SIOCGIWAP:
392 printf("New Access Point/Cell address:%s\n",
393 iw_sawap_ntop(&event->u.ap_addr, buffer));
394 break;
395 case SIOCGIWSCAN:
396 printf("Scan request completed\n");
397 break;
398 case IWEVTXDROP:
399 printf("Tx packet dropped:%s\n",
400 iw_saether_ntop(&event->u.addr, buffer));
401 break;
402 case IWEVCUSTOM:
403 {
404 char custom[IW_CUSTOM_MAX+1];
405 memset(custom, '\0', sizeof(custom));
406 if((event->u.data.pointer) && (event->u.data.length))
407 memcpy(custom, event->u.data.pointer, event->u.data.length);
408 printf("Custom driver event:%s\n", custom);
409 }
410 break;
411 case IWEVREGISTERED:
412 printf("Registered node:%s\n",
413 iw_saether_ntop(&event->u.addr, buffer));
414 break;
415 case IWEVEXPIRED:
416 printf("Expired node:%s\n",
417 iw_saether_ntop(&event->u.addr, buffer));
418 break;
419 case SIOCGIWTHRSPY:
420 {
421 struct iw_thrspy threshold;
422 if((event->u.data.pointer) && (event->u.data.length))
423 {
424 memcpy(&threshold, event->u.data.pointer,
425 sizeof(struct iw_thrspy));
426 printf("Spy threshold crossed on address:%s\n",
427 iw_saether_ntop(&threshold.addr, buffer));
428 iw_print_stats(buffer, sizeof(buffer),
429 &threshold.qual, iw_range, has_range);
430 printf(" Link %s\n", buffer);
431 }
432 else
433 printf("Invalid Spy Threshold event\n");
434 }
435 break;
436 /* ----- driver WPA events ----- */
437 /* Events generated by the driver, used for WPA operation */
438 case IWEVMICHAELMICFAILURE:
439 if(event->u.data.length >= sizeof(struct iw_michaelmicfailure))
440 {
441 struct iw_michaelmicfailure mf;
442 memcpy(&mf, event->u.data.pointer, sizeof(mf));
443 printf("Michael MIC failure flags:0x%X src_addr:%s tsc:%s\n",
444 mf.flags,
445 iw_saether_ntop(&mf.src_addr, buffer2),
446 iw_hexdump(buffer, sizeof(buffer),
447 mf.tsc, IW_ENCODE_SEQ_MAX_SIZE));
448 }
449 break;
450 case IWEVASSOCREQIE:
451 printf("Association Request IEs:%s\n",
452 iw_hexdump(buffer, sizeof(buffer),
453 event->u.data.pointer, event->u.data.length));
454 break;
455 case IWEVASSOCRESPIE:
456 printf("Association Response IEs:%s\n",
457 iw_hexdump(buffer, sizeof(buffer),
458 event->u.data.pointer, event->u.data.length));
459 break;
460 case IWEVPMKIDCAND:
461 if(event->u.data.length >= sizeof(struct iw_pmkid_cand))
462 {
463 struct iw_pmkid_cand cand;
464 memcpy(&cand, event->u.data.pointer, sizeof(cand));
465 printf("PMKID candidate flags:0x%X index:%d bssid:%s\n",
466 cand.flags, cand.index,
467 iw_saether_ntop(&cand.bssid, buffer));
468 }
469 break;
470 /* ----- junk ----- */
471 /* other junk not currently in use */
472 case SIOCGIWRATE:
473 iw_print_bitrate(buffer, sizeof(buffer), event->u.bitrate.value);
474 printf("New Bit Rate:%s\n", buffer);
475 break;
476 case SIOCGIWNAME:
477 printf("Protocol:%-1.16s\n", event->u.name);
478 break;
479 case IWEVQUAL:
480 {
481 event->u.qual.updated = 0x0; /* Not that reliable, disable */
482 iw_print_stats(buffer, sizeof(buffer),
483 &event->u.qual, iw_range, has_range);
484 printf("Link %s\n", buffer);
485 break;
486 }
487 default:
488 printf("(Unknown Wireless event 0x%04X)\n", event->cmd);
489 } /* switch(event->cmd) */
490  
491 return(0);
492 }
493  
494 /*------------------------------------------------------------------*/
495 /*
496 * Print out all Wireless Events part of the RTNetlink message
497 * Most often, there will be only one event per message, but
498 * just make sure we read everything...
499 */
500 static inline int
501 print_event_stream(int ifindex,
502 char * data,
503 int len)
504 {
505 struct iw_event iwe;
506 struct stream_descr stream;
507 int i = 0;
508 int ret;
509 char buffer[64];
510 struct timeval recv_time;
511 struct timezone tz;
512 struct wireless_iface * wireless_data;
513  
514 /* Get data from cache */
515 wireless_data = iw_get_interface_data(ifindex);
516 if(wireless_data == NULL)
517 return(-1);
518  
519 /* Print received time in readable form */
520 gettimeofday(&recv_time, &tz);
521 iw_print_timeval(buffer, sizeof(buffer), &recv_time, &tz);
522  
523 iw_init_event_stream(&stream, data, len);
524 do
525 {
526 /* Extract an event and print it */
527 ret = iw_extract_event_stream(&stream, &iwe,
528 wireless_data->range.we_version_compiled);
529 if(ret != 0)
530 {
531 if(i++ == 0)
532 printf("%s %-8.16s ", buffer, wireless_data->ifname);
533 else
534 printf(" ");
535 if(ret > 0)
536 print_event_token(&iwe,
537 &wireless_data->range, wireless_data->has_range);
538 else
539 printf("(Invalid event)\n");
540 /* Push data out *now*, in case we are redirected to a pipe */
541 fflush(stdout);
542 }
543 }
544 while(ret > 0);
545  
546 return(0);
547 }
548  
549 /*********************** RTNETLINK EVENT DUMP***********************/
550 /*
551 * Dump the events we receive from rtnetlink
552 * This code is mostly from Casey
553 */
554  
555 /*------------------------------------------------------------------*/
556 /*
557 * Respond to a single RTM_NEWLINK event from the rtnetlink socket.
558 */
559 static int
560 LinkCatcher(struct nlmsghdr *nlh)
561 {
562 struct ifinfomsg* ifi;
563  
564 #if 0
565 fprintf(stderr, "nlmsg_type = %d.\n", nlh->nlmsg_type);
566 #endif
567  
568 ifi = NLMSG_DATA(nlh);
569  
570 /* Code is ugly, but sort of works - Jean II */
571  
572 /* If interface is getting destoyed */
573 if(nlh->nlmsg_type == RTM_DELLINK)
574 {
575 /* Remove from cache (if in cache) */
576 iw_del_interface_data(ifi->ifi_index);
577 return 0;
578 }
579  
580 /* Only keep add/change events */
581 if(nlh->nlmsg_type != RTM_NEWLINK)
582 return 0;
583  
584 /* Check for attributes */
585 if (nlh->nlmsg_len > NLMSG_ALIGN(sizeof(struct ifinfomsg)))
586 {
587 int attrlen = nlh->nlmsg_len - NLMSG_ALIGN(sizeof(struct ifinfomsg));
588 struct rtattr *attr = (void *) ((char *) ifi +
589 NLMSG_ALIGN(sizeof(struct ifinfomsg)));
590  
591 while (RTA_OK(attr, attrlen))
592 {
593 /* Check if the Wireless kind */
594 if(attr->rta_type == IFLA_WIRELESS)
595 {
596 /* Go to display it */
597 print_event_stream(ifi->ifi_index,
598 (char *) attr + RTA_ALIGN(sizeof(struct rtattr)),
599 attr->rta_len - RTA_ALIGN(sizeof(struct rtattr)));
600 }
601 attr = RTA_NEXT(attr, attrlen);
602 }
603 }
604  
605 return 0;
606 }
607  
608 /* ---------------------------------------------------------------- */
609 /*
610 * We must watch the rtnelink socket for events.
611 * This routine handles those events (i.e., call this when rth.fd
612 * is ready to read).
613 */
614 static inline void
615 handle_netlink_events(struct rtnl_handle * rth)
616 {
617 while(1)
618 {
619 struct sockaddr_nl sanl;
620 socklen_t sanllen = sizeof(struct sockaddr_nl);
621  
622 struct nlmsghdr *h;
623 int amt;
624 char buf[8192];
625  
626 amt = recvfrom(rth->fd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr*)&sanl, &sanllen);
627 if(amt < 0)
628 {
629 if(errno != EINTR && errno != EAGAIN)
630 {
631 fprintf(stderr, "%s: error reading netlink: %s.\n",
632 __PRETTY_FUNCTION__, strerror(errno));
633 }
634 return;
635 }
636  
637 if(amt == 0)
638 {
639 fprintf(stderr, "%s: EOF on netlink??\n", __PRETTY_FUNCTION__);
640 return;
641 }
642  
643 h = (struct nlmsghdr*)buf;
644 while(amt >= (int)sizeof(*h))
645 {
646 int len = h->nlmsg_len;
647 int l = len - sizeof(*h);
648  
649 if(l < 0 || len > amt)
650 {
651 fprintf(stderr, "%s: malformed netlink message: len=%d\n", __PRETTY_FUNCTION__, len);
652 break;
653 }
654  
655 switch(h->nlmsg_type)
656 {
657 case RTM_NEWLINK:
658 case RTM_DELLINK:
659 LinkCatcher(h);
660 break;
661 default:
662 #if 0
663 fprintf(stderr, "%s: got nlmsg of type %#x.\n", __PRETTY_FUNCTION__, h->nlmsg_type);
664 #endif
665 break;
666 }
667  
668 len = NLMSG_ALIGN(len);
669 amt -= len;
670 h = (struct nlmsghdr*)((char*)h + len);
671 }
672  
673 if(amt > 0)
674 fprintf(stderr, "%s: remnant of size %d on netlink\n", __PRETTY_FUNCTION__, amt);
675 }
676 }
677  
678 /**************************** MAIN LOOP ****************************/
679  
680 /* ---------------------------------------------------------------- */
681 /*
682 * Wait until we get an event
683 */
684 static inline int
685 wait_for_event(struct rtnl_handle * rth)
686 {
687 #if 0
688 struct timeval tv; /* Select timeout */
689 #endif
690  
691 /* Forever */
692 while(1)
693 {
694 fd_set rfds; /* File descriptors for select */
695 int last_fd; /* Last fd */
696 int ret;
697  
698 /* Guess what ? We must re-generate rfds each time */
699 FD_ZERO(&rfds);
700 FD_SET(rth->fd, &rfds);
701 last_fd = rth->fd;
702  
703 /* Wait until something happens */
704 ret = select(last_fd + 1, &rfds, NULL, NULL, NULL);
705  
706 /* Check if there was an error */
707 if(ret < 0)
708 {
709 if(errno == EAGAIN || errno == EINTR)
710 continue;
711 fprintf(stderr, "Unhandled signal - exiting...\n");
712 break;
713 }
714  
715 /* Check if there was a timeout */
716 if(ret == 0)
717 {
718 continue;
719 }
720  
721 /* Check for interface discovery events. */
722 if(FD_ISSET(rth->fd, &rfds))
723 handle_netlink_events(rth);
724 }
725  
726 return(0);
727 }
728  
729 /******************************* MAIN *******************************/
730  
731 /* ---------------------------------------------------------------- */
732 /*
733 * helper ;-)
734 */
735 static void
736 iw_usage(int status)
737 {
738 fputs("Usage: iwevent [OPTIONS]\n"
739 " Monitors and displays Wireless Events.\n"
740 " Options are:\n"
741 " -h,--help Print this message.\n"
742 " -v,--version Show version of this program.\n",
743 status ? stderr : stdout);
744 exit(status);
745 }
746 /* Command line options */
747 static const struct option long_opts[] = {
748 { "help", no_argument, NULL, 'h' },
749 { "version", no_argument, NULL, 'v' },
750 { NULL, 0, NULL, 0 }
751 };
752  
753 /* ---------------------------------------------------------------- */
754 /*
755 * main body of the program
756 */
757 int
758 main(int argc,
759 char * argv[])
760 {
761 struct rtnl_handle rth;
762 int opt;
763  
764 /* Check command line options */
765 while((opt = getopt_long(argc, argv, "hv", long_opts, NULL)) > 0)
766 {
767 switch(opt)
768 {
769 case 'h':
770 iw_usage(0);
771 break;
772  
773 case 'v':
774 return(iw_print_version_info("iwevent"));
775 break;
776  
777 default:
778 iw_usage(1);
779 break;
780 }
781 }
782 if(optind < argc)
783 {
784 fputs("Too many arguments.\n", stderr);
785 iw_usage(1);
786 }
787  
788 /* Open netlink channel */
789 if(rtnl_open(&rth, RTMGRP_LINK) < 0)
790 {
791 perror("Can't initialize rtnetlink socket");
792 return(1);
793 }
794  
795 fprintf(stderr, "Waiting for Wireless Events from interfaces...\n");
796  
797 /* Do what we have to do */
798 wait_for_event(&rth);
799  
800 /* Cleanup - only if you are pedantic */
801 rtnl_close(&rth);
802  
803 return(0);
804 }