/* * wioscan * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define _STR(S) #S #define STR(S) _STR(S) #define ARP htons(ETHERTYPE_ARP) #define IP htons(ETHERTYPE_IP) typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; #include "list.h" #define elemof(T) (sizeof T/sizeof*T) #define endof(T) (T+elemof(T)) #ifndef offsetof #define offsetof(T,M) ((int)(long)&((T*)0)->M) #endif #define HWMAX 8 union addr { struct sockaddr sa; struct sockaddr_in in; struct sockaddr_ll ll; }; int sock; /* packet socket */ union addr bcast; struct opts { unsigned sort:1; unsigned noown:1; unsigned noethn:1; unsigned proui:1; unsigned isrange:1; unsigned passive:1; unsigned nsend; unsigned wait; } opts = {nsend:8, wait:250}; void print_oui(int sp, u8 a[6]); struct he; void print_he(struct he *he); struct hwaddr { u8 len, addr[HWMAX]; }; static inline int hw_eq(struct hwaddr *h, int hl, u8 *ha) { return h->len == hl && memcmp(h->addr, ha, hl) == 0; } static inline void hw_set(struct hwaddr *h, int hl, u8 *ha) { memcpy(h->addr, ha, (h->len = hl)); } struct ifinfo { int index; char *name; u32 ip, net, mask, bcast; u16 hw_type; struct hwaddr hw; } ifinfo; static inline u32 ip_from_sa(struct sockaddr *sa) { return ntohl(((struct sockaddr_in*)sa)->sin_addr.s_addr); } /* TABLE */ struct list hashtbl[128]; struct he { struct list hash; u32 ip; struct hwaddr hw; struct hwaddr from; }; static void init_hash() __attribute__((constructor)); static void init_hash() { int i; for(i=0;inext; l!=h; l=l->next) { he = list_entry(l, struct he, hash); if(he->ip == ip) goto ret; if(he->ip > ip) break; } v = 0; if(alloc) { he = (struct he*)malloc(sizeof *he); he->ip = ip; list_add(l->prev, &he->hash); ret: if(ret) *ret = he; } return v; } /* INTERFACE */ static int net; static void my__ioctl(int i, struct ifreq *r, char *t) { if(ioctl(net, i, r) < 0) err(1, "ioctl(%s,%s)", t, r->ifr_name); } #define my_ioctl(I,R) my__ioctl(I,R,#I) void fill_ifinfo(char *name) { struct ifreq ir; int flags; ifinfo.index = if_nametoindex(name); if(!ifinfo.index) errx(1, "No such interface: %s", name); ifinfo.name = name; net = socket(PF_INET, SOCK_DGRAM, 0); if(net<0) err(1, "socket(PF_INET)"); strcpy(ir.ifr_name, ifinfo.name); my_ioctl(SIOCGIFFLAGS, &ir); flags = ir.ifr_flags; if(flags & IFF_NOARP) errx(1, "%s: ARP not supported.", name); my_ioctl(SIOCGIFADDR, &ir); ifinfo.ip = ip_from_sa(&ir.ifr_addr); if(flags & IFF_POINTOPOINT) { my_ioctl(SIOCGIFDSTADDR, &ir); ifinfo.net = ip_from_sa(&ir.ifr_dstaddr); ifinfo.mask = (u32)~0; ifinfo.bcast = 0; /* none */ } else { my_ioctl(SIOCGIFNETMASK, &ir); ifinfo.mask = ip_from_sa(&ir.ifr_netmask); my_ioctl(SIOCGIFBRDADDR, &ir); ifinfo.bcast = ip_from_sa(&ir.ifr_broadaddr); ifinfo.net = ifinfo.ip & ifinfo.mask; } close(net); } static inline char *str_ip(u32 ip) { struct in_addr n; n.s_addr = htonl(ip); return inet_ntoa(n); } char *str_hw(u8 *a, int l) { static char buf[3*HWMAX]; char *d = buf; if(!l) return "*"; if(l>HWMAX) l=HWMAX; for(;;) { d += sprintf(d, "%02X", *a++); if(--l <= 0) break; *d++ = ':'; } *d = 0; return buf; } static char *str_addr(union addr *addr) { switch(addr->sa.sa_family) { case AF_INET: return inet_ntoa(addr->in.sin_addr); case AF_PACKET: return str_hw(addr->ll.sll_addr, addr->ll.sll_halen); default: return "???"; } } static inline void setup_socket() { union addr addr; socklen_t l; sock = socket(PF_PACKET, SOCK_DGRAM, 0); if(sock < 0) err(1, "socket(PF_PACKET)"); memset(&addr.ll, 0, sizeof addr.ll); addr.sa.sa_family = AF_PACKET; addr.ll.sll_protocol = ARP; addr.ll.sll_ifindex = ifinfo.index; if(bind(sock, &addr.sa, sizeof addr.ll)<0) err(1, "bind"); l = sizeof addr.ll; if(getsockname(sock, &addr.sa, &l)<0) err(1, "getsockname"); if(addr.ll.sll_halen > HWMAX) errx(1, "hardware address too long (%d)", addr.ll.sll_halen); ifinfo.hw.len = addr.ll.sll_halen; memcpy(ifinfo.hw.addr, addr.ll.sll_addr, sizeof ifinfo.hw.addr); ifinfo.hw_type = addr.ll.sll_hatype; } /* SCAN */ struct arppkt { u16 hrd, pro; u8 hln, pln; u16 op; u8 a[2*HWMAX+2*4]; /* u8 sha[6]; u8 sip[4]; u8 tha[6]; u8 tip[4];*/ }; static inline u8 *get_sha(struct arppkt *pkt) {return pkt->a;} static inline u8 *get_tha(struct arppkt *pkt) {return pkt->a+pkt->hln+4;} static inline u32 get_sip(struct arppkt *pkt) {return ntohl(*(u32*)(pkt->a+pkt->hln));} static inline u32 get_tip(struct arppkt *pkt) {return ntohl(*(u32*)(pkt->a+2*pkt->hln+4));} #if 0 void print_arp(struct arppkt *arp) { u8 *p = arp->a; printf("hrd:%04X pro:%04X ", ntohs(arp->hrd), ntohs(arp->pro)); printf("hln:%d pln:%d op:%d ", arp->hln, arp->pln, ntohs(arp->op)); printf("sha:%s ", str_hw(p, arp->hln)); p+=arp->hln; printf("sip:%s ", str_ip(ntohl(*(u32*)p))); p+=arp->pln; printf("tha:%s ", str_hw(p, arp->hln)); p+=arp->hln; printf("tip:%s\n", str_ip(ntohl(*(u32*)p))); } #endif static struct scan { u32 ip, start, end; } scan; #define IN_RANGE(I) ((I) >= scan.start && (I) <= (u32)(scan.end-1)) int sendscan() { struct arppkt arp; int ns; u8 *p; arp.hrd = htons(ifinfo.hw_type); arp.pro = IP; arp.hln = ifinfo.hw.len; arp.pln = 4; arp.op = htons(1); p = arp.a; memcpy(p, ifinfo.hw.addr, ifinfo.hw.len); p += ifinfo.hw.len; *(u32*)p = htonl(ifinfo.ip); p += 4; memset(p, 0, ifinfo.hw.len); p += ifinfo.hw.len; ns = 0; while(scan.ip != scan.end) { int v; if(scan.ip == ifinfo.bcast || he_for(scan.ip, 0, 0)) { scan.ip++; continue; } *(u32*)p = htonl(scan.ip); v = sendto(sock, &arp, p+4-(u8*)&arp, 0, &bcast.sa, sizeof bcast.ll); if(v<0) { if(errno != ENOBUFS || opts.nsend <= 1) err(1, "send(%s)", str_addr(&bcast)); opts.nsend--; return -1; } scan.ip++; if(++ns >= opts.nsend) break; } return ns; } void compare_resp(struct he *he, union addr *src, int hln, u8 *sha) { if(hw_eq(&he->hw, hln, sha) && hw_eq(&he->from, src->ll.sll_halen, src->ll.sll_addr)) return; fprintf(stderr, "%s: ", str_ip(he->ip)); fprintf(stderr, "inconsistency: %s", str_hw(sha, hln)); if(src->ll.sll_halen != hln || memcmp(src->ll.sll_addr, sha, hln)) fprintf(stderr, " from %s", str_hw(src->ll.sll_addr, src->ll.sll_halen)); fprintf(stderr, ", was %s\n", str_hw(he->hw.addr, he->hw.len)); if(!hw_eq(&he->hw, he->from.len, he->from.addr)) fprintf(stderr, " from %s", str_hw(he->from.addr, he->from.len)); } int arp_recv(struct arppkt *pkt, union addr *src) { socklen_t l = sizeof *src; int v = recvfrom(sock, pkt, sizeof *pkt, 0, &src->sa, &l); if(v < 0) err(1, "recvfrom"); if(v < offsetof(struct arppkt, a)) return 0; if(pkt->pro != IP) return 0; if(pkt->hrd != htons(ifinfo.hw_type) || pkt->hln != ifinfo.hw.len) return 0; if(v < offsetof(struct arppkt, a) + 2*pkt->hln + 2*4) return 0; return 1; } void receive() { union addr addr; struct arppkt arp; struct he *he; u32 ip; if(!arp_recv(&arp, &addr)) return; if(arp.op != htons(2)) /* only responses */ return; ip = get_sip(&arp); if(!he_for(ip, &he, 1)) { hw_set(&he->hw, arp.hln, get_sha(&arp)); hw_set(&he->from, addr.ll.sll_halen, addr.ll.sll_addr); if(opts.sort) return; if(opts.isrange && !IN_RANGE(ip)) return; print_he(he); } else compare_resp(he, &addr, arp.hln, get_sha(&arp)); } /**/ void passive() { for(;;) { struct arppkt arp; union addr src; if(!arp_recv(&arp, &src)) continue; printf("%s: ", str_addr(&src)); printf("%s %-15s ", str_hw(get_sha(&arp),arp.hln), str_ip(get_sip(&arp))); switch(htons(arp.op)) { case 1: printf("Q %s", str_ip(get_tip(&arp))); break; case 2: printf("A %s %s", str_hw(get_tha(&arp),arp.hln), str_ip(get_tip(&arp))); break; default: printf("%X", htons(arp.op)); } putchar('\n'); } } /**/ int waitsock(int n) { int v; struct pollfd pollfd; pollfd.fd = sock; pollfd.events = POLLIN; v = poll(&pollfd, 1, n); if(v < 0) { if(errno != EINTR) err(1, "poll"); v = 0; } return v; } void print_he(struct he *he) { int l, w; if(opts.noown && he->ip == ifinfo.ip) return; printf("%s,", str_hw(he->hw.addr, he->hw.len)); l = 15 - printf("%s", str_ip(he->ip)); w = 0; if(!opts.proui && !hw_eq(&he->from, he->hw.len, he->hw.addr)) w = 1, l = 1; if(opts.proui) print_oui(l, he->hw.addr); else if(!opts.noethn) { #if !defined __dietlibc_ && !defined __UCLIBC__ char nm[1024]; if(!ether_ntohost(nm, (struct ether_addr*)he->hw.addr)) printf("%*s%s", l, "", nm); #endif } if(w) printf(" from %s", str_hw(he->from.addr, he->from.len)); putchar('\n'); } static int parse_iprange(char *p) { char *e; u32 ip=0; int sh; for(sh = 24;; sh -= 8) { unsigned long v; v = strtoul(p, &e, 10); if(p == e || v > 255) return 0; ip |= v << sh; p = e + 1; if(*e == '/') { v = strtoul(p, &e, 10); if(p == e || *e || v > 32) return 0; if(v) { v = 32 - v; if(sh > v) return 0; mask: v = ~0 << v; } scan.start = ip & v; scan.end = scan.start - v; return 1; } if(!sh) break; v = sh; if(!*e) goto mask; if(*e != '.') return 0; if(!*p || *p == '*' && !p[1]) goto mask; } scan.start = ip; scan.end = ip + 1; if(*e == '-') { u32 end = 0, m = ~0; do { unsigned long v = strtoul(p, &e, 10); if(p == e || v > 255) return 0; p = e + 1; end = end<<8 | v; m <<= 8; } while(m && *e); if(*e) return 0; end |= ip & m; if(end < ip) return 0; scan.end = end + 1; return 1; } return *e == 0; } int main(int argc, char **argv) { for(;;) switch(getopt(argc, argv, "fsaepwlh")) { case 'f': opts.sort=0; break; case 's': opts.sort=1; break; case 'a': opts.noown=1; break; case 'e': opts.noethn=1; break; case 'p': opts.proui=1; break; case 'w': opts.nsend=2; opts.wait=1000; break; case 'l': opts.passive=1; break; case 'h': printf( "wioscan [-faep] [interface] [ip-range]\n" "\t-s sort responses\n" "\t-a do not list interface's own address\n" #if !defined __dietlibc_ && !defined __UCLIBC__ "\t-e do not include info from /etc/ethers\n" #endif "\t-p print vendor names\n" "\t-w slow operation\n" "\t-l listen only (not promiscuous)\n" "ip-range: ip ip/bits ip-ip\n" ); return 0; case EOF: goto endopt; } endopt: { char *dev = "eth0"; if(optind '9')) dev = argv[optind++]; fill_ifinfo(dev); setup_socket(); } if(optind>=argc) { scan.start = ifinfo.net; scan.end = (ifinfo.net | ~ifinfo.mask) + 1; } else { if(!parse_iprange(argv[optind])) errx(1, "%s: bad IP range", argv[optind]); opts.isrange = 1; } if(ifinfo.hw_type != ARPHRD_ETHER) opts.proui = 0, opts.noethn = 1; if(opts.passive) passive(); /* hw broadcast address is Linux's secret, this works with Ethernet */ bcast.sa.sa_family = AF_PACKET; bcast.ll.sll_protocol = ARP; bcast.ll.sll_ifindex = ifinfo.index; bcast.ll.sll_hatype = ifinfo.hw_type; bcast.ll.sll_pkttype = PACKET_BROADCAST; /* unused :-( */ bcast.ll.sll_halen = ifinfo.hw.len; memset(bcast.ll.sll_addr, 0xFF, ifinfo.hw.len); if(IN_RANGE(ifinfo.ip)) { /* XXX we should add all our arpable addresses on the interface */ struct he *he; he_for(ifinfo.ip, &he, 1); hw_set(&he->hw, ifinfo.hw.len, ifinfo.hw.addr); hw_set(&he->from, ifinfo.hw.len, ifinfo.hw.addr); if(!opts.sort) print_he(he); } /* 1st scan */ scan.ip = scan.start; while(sendscan()) { while(waitsock(10)) receive(); } /* 2nd scan */ scan.ip = scan.start; while(sendscan()) { while(waitsock(10)) receive(); } while(waitsock(opts.wait)) receive(); if(opts.sort) for(scan.ip = ifinfo.net; scan.ip != scan.end; scan.ip++) { struct he *he; if(he_for(scan.ip, &he, 0)) print_he(he); } return 0; } typedef uint8_t u8; static int fd = -2; static char *ouiptr, *ouiend; static void open_oui() { struct stat st; fd = open("oui", O_RDONLY); if(fd < 0) { fd = open(STR(OUI), O_RDONLY); if(fd < 0) goto err; } if(fstat(fd, &st) < 0 || st.st_size == 0) goto err_cl; ouiptr = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); ouiend = ouiptr + st.st_size; if(ouiptr == MAP_FAILED) { err_cl: close(fd); fd=-1; err: warnx("Can't open OUI database"); return; } #ifdef MADV_SEQUENTIAL madvise(ouiptr, st.st_size, MADV_SEQUENTIAL); #endif } void print_oui(int sp, u8 a[6]) { char addr[7], *p, *q; if(fd < 0) { if(fd == -2) open_oui(); if(fd < 0) return; } sprintf(addr, "%02X%02X%02X", a[0], a[1], a[2]); for(p=ouiptr; p