]> git.ipfire.org Git - ipfire-2.x.git/blame - src/misc-progs/wioscan.c
Merge branch 'ipsec' into next
[ipfire-2.x.git] / src / misc-progs / wioscan.c
CommitLineData
0d6cc79d
SF
1/*
2 * wioscan
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * version 2 as published by the Free Software Foundation.
7 */
8
9#define _GNU_SOURCE
10#include <sys/types.h>
11#include <unistd.h>
12#include <string.h>
13#include <stdlib.h>
14#include <stdio.h>
15#include <poll.h>
16#include <errno.h>
17#include <err.h>
18#include <sys/ioctl.h>
19#include <sys/socket.h>
20#include <netpacket/packet.h>
21#include <net/ethernet.h>
22#include <net/if.h>
23#include <net/if_arp.h>
24#include <netinet/ether.h>
25#include <arpa/inet.h>
26#include <stdint.h>
27#include <fcntl.h>
28#include <sys/stat.h>
29#include <sys/mman.h>
30
31#define _STR(S) #S
32#define STR(S) _STR(S)
33
34#define ARP htons(ETHERTYPE_ARP)
35#define IP htons(ETHERTYPE_IP)
36
37typedef uint8_t u8;
38typedef uint16_t u16;
39typedef uint32_t u32;
40
41#include "list.h"
42#define elemof(T) (sizeof T/sizeof*T)
43#define endof(T) (T+elemof(T))
44#ifndef offsetof
45#define offsetof(T,M) ((int)(long)&((T*)0)->M)
46#endif
47
48#define HWMAX 8
49
50union addr {
51 struct sockaddr sa;
52 struct sockaddr_in in;
53 struct sockaddr_ll ll;
54};
55
56int sock; /* packet socket */
57union addr bcast;
58
59struct opts {
60 unsigned sort:1;
61 unsigned noown:1;
62 unsigned noethn:1;
63 unsigned proui:1;
64 unsigned isrange:1;
65 unsigned passive:1;
66 unsigned nsend;
67 unsigned wait;
68} opts = {nsend:8, wait:250};
69
70void print_oui(int sp, u8 a[6]);
71
72struct he;
73void print_he(struct he *he);
74
75struct hwaddr {
76 u8 len, addr[HWMAX];
77};
78
79static inline hw_eq(struct hwaddr *h, int hl, u8 *ha)
80{
81 return h->len == hl && memcmp(h->addr, ha, hl) == 0;
82}
83
84static inline void hw_set(struct hwaddr *h, int hl, u8 *ha)
85{
86 memcpy(h->addr, ha, (h->len = hl));
87}
88
89struct ifinfo {
90 int index;
91 char *name;
92 u32 ip, net, mask, bcast;
93 u16 hw_type;
94 struct hwaddr hw;
95} ifinfo;
96
97static inline u32 ip_from_sa(struct sockaddr *sa)
98{
99 return ntohl(((struct sockaddr_in*)sa)->sin_addr.s_addr);
100}
101
102/* TABLE */
103
104struct list hashtbl[128];
105struct he {
106 struct list hash;
107 u32 ip;
108 struct hwaddr hw;
109 struct hwaddr from;
110};
111
112static void init_hash() __attribute__((constructor));
113static void init_hash()
114{
115 int i;
116 for(i=0;i<elemof(hashtbl);i++) list_init(&hashtbl[i]);
117}
118
119int he_for(u32 ip, struct he **ret, int alloc)
120{
121 struct list *h, *l;
122 struct he *he;
123 int v = 1;
124 h = &hashtbl[ip & elemof(hashtbl)-1];
125 for(l=h->next; l!=h; l=l->next) {
126 he = list_entry(l, struct he, hash);
127 if(he->ip == ip)
128 goto ret;
129 if(he->ip > ip)
130 break;
131 }
132 v = 0;
133 if(alloc) {
134 he = (struct he*)malloc(sizeof *he);
135 he->ip = ip;
136 list_add(l->prev, &he->hash);
137ret:
138 if(ret) *ret = he;
139 }
140 return v;
141}
142
143/* INTERFACE */
144
145static int net;
146
147static void my__ioctl(int i, struct ifreq *r, char *t)
148{
149 if(ioctl(net, i, r) < 0)
150 err(1, "ioctl(%s,%s)", t, r->ifr_name);
151}
152#define my_ioctl(I,R) my__ioctl(I,R,#I)
153
154void fill_ifinfo(char *name)
155{
156 struct ifreq ir;
157 int flags;
158
159 ifinfo.index = if_nametoindex(name);
160 if(!ifinfo.index) errx(1, "No such interface: %s", name);
161 ifinfo.name = name;
162
163 net = socket(PF_INET, SOCK_DGRAM, 0);
164 if(net<0) err(1, "socket(PF_INET)");
165 strcpy(ir.ifr_name, ifinfo.name);
166 my_ioctl(SIOCGIFFLAGS, &ir);
167 flags = ir.ifr_flags;
168 if(flags & IFF_NOARP) errx(1, "%s: ARP not supported.", name);
169 my_ioctl(SIOCGIFADDR, &ir);
170 ifinfo.ip = ip_from_sa(&ir.ifr_addr);
171 if(flags & IFF_POINTOPOINT) {
172 my_ioctl(SIOCGIFDSTADDR, &ir);
173 ifinfo.net = ip_from_sa(&ir.ifr_dstaddr);
174 ifinfo.mask = (u32)~0;
175 ifinfo.bcast = 0; /* none */
176 } else {
177 my_ioctl(SIOCGIFNETMASK, &ir);
178 ifinfo.mask = ip_from_sa(&ir.ifr_netmask);
179 my_ioctl(SIOCGIFBRDADDR, &ir);
180 ifinfo.bcast = ip_from_sa(&ir.ifr_broadaddr);
181 ifinfo.net = ifinfo.ip & ifinfo.mask;
182 }
183 close(net);
184}
185
186static inline char *str_ip(u32 ip)
187{
188 struct in_addr n;
189 n.s_addr = htonl(ip);
190 return inet_ntoa(n);
191}
192
193char *str_hw(u8 *a, int l)
194{
195 static char buf[3*HWMAX];
196 char *d = buf;
197 if(!l) return "*";
198 if(l>HWMAX) l=HWMAX;
199 for(;;) {
200 d += sprintf(d, "%02X", *a++);
201 if(--l <= 0) break;
202 *d++ = ':';
203 }
204 *d = 0;
205 return buf;
206}
207
208static char *str_addr(union addr *addr)
209{
210 switch(addr->sa.sa_family) {
211 case AF_INET: return inet_ntoa(addr->in.sin_addr);
212 case AF_PACKET: return str_hw(addr->ll.sll_addr, addr->ll.sll_halen);
213 default: return "???";
214 }
215}
216
217static inline void setup_socket()
218{
219 union addr addr;
220 socklen_t l;
221
222 sock = socket(PF_PACKET, SOCK_DGRAM, 0);
223 if(sock < 0) err(1, "socket(PF_PACKET)");
224
225 memset(&addr.ll, 0, sizeof addr.ll);
226 addr.sa.sa_family = AF_PACKET;
227 addr.ll.sll_protocol = ARP;
228 addr.ll.sll_ifindex = ifinfo.index;
229
230 if(bind(sock, &addr.sa, sizeof addr.ll)<0)
231 err(1, "bind");
232 l = sizeof addr.ll;
233 if(getsockname(sock, &addr.sa, &l)<0)
234 err(1, "getsockname");
235
236 if(addr.ll.sll_halen > HWMAX)
237 errx(1, "hardware address too long (%d)", addr.ll.sll_halen);
238 ifinfo.hw.len = addr.ll.sll_halen;
239 memcpy(ifinfo.hw.addr, addr.ll.sll_addr, sizeof ifinfo.hw.addr);
240 ifinfo.hw_type = addr.ll.sll_hatype;
241}
242
243/* SCAN */
244
245struct arppkt {
246 u16 hrd, pro;
247 u8 hln, pln;
248 u16 op;
249 u8 a[2*HWMAX+2*4];
250/* u8 sha[6];
251 u8 sip[4];
252 u8 tha[6];
253 u8 tip[4];*/
254};
255
256static inline u8 *get_sha(struct arppkt *pkt) {return pkt->a;}
257static inline u8 *get_tha(struct arppkt *pkt) {return pkt->a+pkt->hln+4;}
258static inline u32 get_sip(struct arppkt *pkt) {return ntohl(*(u32*)(pkt->a+pkt->hln));}
259static inline u32 get_tip(struct arppkt *pkt) {return ntohl(*(u32*)(pkt->a+2*pkt->hln+4));}
260
261#if 0
262void print_arp(struct arppkt *arp)
263{
264 u8 *p = arp->a;
265 printf("hrd:%04X pro:%04X ", ntohs(arp->hrd), ntohs(arp->pro));
266 printf("hln:%d pln:%d op:%d ", arp->hln, arp->pln, ntohs(arp->op));
267 printf("sha:%s ", str_hw(p, arp->hln)); p+=arp->hln;
268 printf("sip:%s ", str_ip(ntohl(*(u32*)p))); p+=arp->pln;
269 printf("tha:%s ", str_hw(p, arp->hln)); p+=arp->hln;
270 printf("tip:%s\n", str_ip(ntohl(*(u32*)p)));
271}
272#endif
273
274static struct scan {
275 u32 ip, start, end;
276} scan;
277
278#define IN_RANGE(I) ((I) >= scan.start && (I) <= (u32)(scan.end-1))
279
280int sendscan()
281{
282 struct arppkt arp;
283 int ns;
284 u8 *p;
285
286 arp.hrd = htons(ifinfo.hw_type);
287 arp.pro = IP;
288 arp.hln = ifinfo.hw.len;
289 arp.pln = 4;
290 arp.op = htons(1);
291 p = arp.a;
292 memcpy(p, ifinfo.hw.addr, ifinfo.hw.len); p += ifinfo.hw.len;
293 *(u32*)p = htonl(ifinfo.ip); p += 4;
294 memset(p, 0, ifinfo.hw.len); p += ifinfo.hw.len;
295
296 ns = 0;
297 while(scan.ip != scan.end) {
298 int v;
299 if(scan.ip == ifinfo.bcast || he_for(scan.ip, 0, 0)) {
300 scan.ip++;
301 continue;
302 }
303 *(u32*)p = htonl(scan.ip);
304 v = sendto(sock, &arp, p+4-(u8*)&arp, 0, &bcast.sa, sizeof bcast.ll);
305 if(v<0) {
306 if(errno != ENOBUFS || opts.nsend <= 1)
307 err(1, "send(%s)", str_addr(&bcast));
308 opts.nsend--;
309 return -1;
310 }
311 scan.ip++;
312 if(++ns >= opts.nsend) break;
313 }
314 return ns;
315}
316
317void compare_resp(struct he *he, union addr *src, int hln, u8 *sha)
318{
319 if(hw_eq(&he->hw, hln, sha)
320 && hw_eq(&he->from, src->ll.sll_halen, src->ll.sll_addr))
321 return;
322
323 fprintf(stderr, "%s: ", str_ip(he->ip));
324 fprintf(stderr, "inconsistency: %s", str_hw(sha, hln));
325 if(src->ll.sll_halen != hln || memcmp(src->ll.sll_addr, sha, hln))
326 fprintf(stderr, " from %s",
327 str_hw(src->ll.sll_addr, src->ll.sll_halen));
328 fprintf(stderr, ", was %s\n", str_hw(he->hw.addr, he->hw.len));
329 if(!hw_eq(&he->hw, he->from.len, he->from.addr))
330 fprintf(stderr, " from %s",
331 str_hw(he->from.addr, he->from.len));
332}
333
334int arp_recv(struct arppkt *pkt, union addr *src)
335{
336 socklen_t l = sizeof *src;
337 int v = recvfrom(sock, pkt, sizeof *pkt, 0, &src->sa, &l);
338 if(v < 0) err(1, "recvfrom");
339 if(v < offsetof(struct arppkt, a))
340 return 0;
341 if(pkt->pro != IP)
342 return 0;
343 if(pkt->hrd != htons(ifinfo.hw_type) || pkt->hln != ifinfo.hw.len)
344 return 0;
345 if(v < offsetof(struct arppkt, a) + 2*pkt->hln + 2*4)
346 return 0;
347 return 1;
348}
349
350void receive()
351{
352 union addr addr;
353 struct arppkt arp;
354 struct he *he;
355 u32 ip;
356
357 if(!arp_recv(&arp, &addr))
358 return;
359 if(arp.op != htons(2)) /* only responses */
360 return;
361
362 ip = get_sip(&arp);
363
364 if(!he_for(ip, &he, 1)) {
365 hw_set(&he->hw, arp.hln, get_sha(&arp));
366 hw_set(&he->from, addr.ll.sll_halen, addr.ll.sll_addr);
367 if(opts.sort) return;
368 if(opts.isrange && !IN_RANGE(ip)) return;
369 print_he(he);
370 } else
371 compare_resp(he, &addr, arp.hln, get_sha(&arp));
372}
373
374/**/
375
376void passive()
377{
378 for(;;) {
379 struct arppkt arp;
380 union addr src;
381 if(!arp_recv(&arp, &src))
382 continue;
383 printf("%s: ", str_addr(&src));
384 printf("%s %-15s ", str_hw(get_sha(&arp),arp.hln),
385 str_ip(get_sip(&arp)));
386 switch(htons(arp.op)) {
387 case 1:
388 printf("Q %s", str_ip(get_tip(&arp)));
389 break;
390 case 2:
391 printf("A %s %s", str_hw(get_tha(&arp),arp.hln),
392 str_ip(get_tip(&arp)));
393 break;
394 default:
395 printf("%X", htons(arp.op));
396 }
397 putchar('\n');
398 }
399}
400
401/**/
402
403int waitsock(int n)
404{
405 int v;
406 struct pollfd pollfd;
407 pollfd.fd = sock;
408 pollfd.events = POLLIN;
409 v = poll(&pollfd, 1, n);
410 if(v < 0) {
411 if(errno != EINTR)
412 err(1, "poll");
413 v = 0;
414 }
415 return v;
416}
417
418void print_he(struct he *he)
419{
420 int l, w;
421 if(opts.noown && he->ip == ifinfo.ip)
422 return;
423 printf("%s,", str_hw(he->hw.addr, he->hw.len));
424 l = 15 - printf("%s", str_ip(he->ip));
425 w = 0;
426 if(!opts.proui && !hw_eq(&he->from, he->hw.len, he->hw.addr))
427 w = 1, l = 1;
428
429 if(opts.proui)
430 print_oui(l, he->hw.addr);
431 else if(!opts.noethn) {
432#if !defined __dietlibc_ && !defined __UCLIBC__
433 char nm[1024];
434 if(!ether_ntohost(nm, (struct ether_addr*)he->hw.addr))
435 printf("%*s%s", l, "", nm);
436#endif
437 }
438 if(w)
439 printf(" from %s", str_hw(he->from.addr, he->from.len));
440 putchar('\n');
441}
442
443static int parse_iprange(char *p)
444{
445 char *e;
446 u32 ip=0;
447 int sh;
448
449 for(sh = 24;; sh -= 8) {
450 unsigned long v;
451
452 v = strtoul(p, &e, 10);
453 if(p == e || v > 255)
454 return 0;
455
456 ip |= v << sh;
457
458 p = e + 1;
459 if(*e == '/') {
460 v = strtoul(p, &e, 10);
461 if(p == e || *e || v > 32)
462 return 0;
463 if(v) {
464 v = 32 - v;
465 if(sh > v)
466 return 0;
467mask:
468 v = ~0 << v;
469 }
470 scan.start = ip & v;
471 scan.end = scan.start - v;
472 return 1;
473 }
474
475 if(!sh) break;
476
477 v = sh;
478 if(!*e)
479 goto mask;
480
481 if(*e != '.')
482 return 0;
483
484 if(!*p || *p == '*' && !p[1])
485 goto mask;
486 }
487
488 scan.start = ip;
489 scan.end = ip + 1;
490
491 if(*e == '-') {
492 u32 end = 0, m = ~0;
493
494 do {
495 unsigned long v = strtoul(p, &e, 10);
496 if(p == e || v > 255)
497 return 0;
498 p = e + 1;
499 end = end<<8 | v;
500 m <<= 8;
501 } while(m && *e);
502
503 if(*e)
504 return 0;
505
506 end |= ip & m;
507 if(end < ip)
508 return 0;
509
510 scan.end = end + 1;
511 return 1;
512 }
513 return *e == 0;
514}
515
516int main(int argc, char **argv)
517{
518 for(;;) switch(getopt(argc, argv, "fsaepwlh")) {
519 case 'f': opts.sort=0; break;
520 case 's': opts.sort=1; break;
521 case 'a': opts.noown=1; break;
522 case 'e': opts.noethn=1; break;
523 case 'p': opts.proui=1; break;
524 case 'w': opts.nsend=2; opts.wait=1000; break;
525 case 'l': opts.passive=1; break;
526 case 'h':
527 printf(
528 "wioscan [-faep] [interface] [ip-range]\n"
529 "\t-s sort responses\n"
530 "\t-a do not list interface's own address\n"
531#if !defined __dietlibc_ && !defined __UCLIBC__
532 "\t-e do not include info from /etc/ethers\n"
533#endif
534 "\t-p print vendor names\n"
535 "\t-w slow operation\n"
536 "\t-l listen only (not promiscuous)\n"
537 "ip-range: ip ip/bits ip-ip\n"
538 );
539 return 0;
540 case EOF:
541 goto endopt;
542 }
543endopt:
544
545 {
546 char *dev = "eth0";
547 if(optind<argc && (*argv[optind] < '0' || *argv[optind] > '9'))
548 dev = argv[optind++];
549 fill_ifinfo(dev);
550 setup_socket();
551 }
552
553 if(optind>=argc) {
554 scan.start = ifinfo.net;
555 scan.end = (ifinfo.net | ~ifinfo.mask) + 1;
556 } else {
557 if(!parse_iprange(argv[optind]))
558 errx(1, "%s: bad IP range", argv[optind]);
559 opts.isrange = 1;
560 }
561
562 if(ifinfo.hw_type != ARPHRD_ETHER)
563 opts.proui = 0, opts.noethn = 1;
564
565 if(opts.passive)
566 passive();
567
568 /* hw broadcast address is Linux's secret, this works with Ethernet */
569 bcast.sa.sa_family = AF_PACKET;
570 bcast.ll.sll_protocol = ARP;
571 bcast.ll.sll_ifindex = ifinfo.index;
572 bcast.ll.sll_hatype = ifinfo.hw_type;
573 bcast.ll.sll_pkttype = PACKET_BROADCAST; /* unused :-( */
574 bcast.ll.sll_halen = ifinfo.hw.len;
575 memset(bcast.ll.sll_addr, 0xFF, ifinfo.hw.len);
576
577 if(IN_RANGE(ifinfo.ip)) {
578 /* XXX we should add all our arpable addresses on the interface */
579 struct he *he;
580 he_for(ifinfo.ip, &he, 1);
581 hw_set(&he->hw, ifinfo.hw.len, ifinfo.hw.addr);
582 hw_set(&he->from, ifinfo.hw.len, ifinfo.hw.addr);
583 if(!opts.sort)
584 print_he(he);
585 }
586
587 /* 1st scan */
588 scan.ip = scan.start;
589 while(sendscan()) {
590 while(waitsock(10))
591 receive();
592 }
593 /* 2nd scan */
594 scan.ip = scan.start;
595 while(sendscan()) {
596 while(waitsock(10))
597 receive();
598 }
599 while(waitsock(opts.wait))
600 receive();
601
602 if(opts.sort) for(scan.ip = ifinfo.net; scan.ip != scan.end; scan.ip++) {
603 struct he *he;
604 if(he_for(scan.ip, &he, 0))
605 print_he(he);
606 }
607 return 0;
608}
609
610
611typedef uint8_t u8;
612static int fd = -2;
613static char *ouiptr, *ouiend;
614
615static void open_oui()
616{
617 struct stat st;
618 fd = open("oui", O_RDONLY);
619 if(fd < 0) {
620 fd = open(STR(OUI), O_RDONLY);
621 if(fd < 0) goto err;
622 }
623 if(fstat(fd, &st) < 0 || st.st_size == 0) goto err_cl;
624 ouiptr = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
625 ouiend = ouiptr + st.st_size;
626 if(ouiptr == MAP_FAILED) {
627err_cl:
628 close(fd); fd=-1;
629err:
630 warnx("Can't open OUI database");
631 return;
632 }
633#ifdef MADV_SEQUENTIAL
634 madvise(ouiptr, st.st_size, MADV_SEQUENTIAL);
635#endif
636}
637
638void print_oui(int sp, u8 a[6])
639{
640 char addr[7], *p, *q;
641 if(fd < 0) {
642 if(fd == -2)
643 open_oui();
644 if(fd < 0)
645 return;
646 }
647 sprintf(addr, "%02X%02X%02X", a[0], a[1], a[2]);
648
649 for(p=ouiptr; p<ouiend; p=q+1) {
650 q = memchr(p, '\n', ouiend-p);
651 if(!q) q=ouiend;
652 if(q-p < 8 || memcmp(p, addr, 6))
653 continue;
654
655 p += 7;
656print:
657 printf("%*s%.*s", sp, "", (int)(q-p), p);
658 return;
659 }
660 if(a[0]==0 && a[1]==0xFF) {
661 p = "(generated)";
662 q = p + 11;
663 goto print;
664 }
665}