/* * $Id: dnstop.c,v 1.81 2007/05/11 04:51:12 wessels Exp $ * * http://dnstop.measurement-factory.com/ * * Copyright (c) 2002, The Measurement Factory, Inc. All rights reserved. See * the LICENSE file for details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __APPLE__ #include #endif #include #include #include #include #include #include #include #include #include #include "hashtbl.h" static hashkeycmp cmp_in6_addr; static hashfunc in_addr_hash; #define PCAP_SNAPLEN 1460 #define MAX_QNAME_SZ 512 #define DNS_MSG_HDR_SZ 12 #ifndef ETHER_HDR_LEN #define ETHER_ADDR_LEN 6 #define ETHER_TYPE_LEN 2 #define ETHER_HDR_LEN (ETHER_ADDR_LEN * 2 + ETHER_TYPE_LEN) #endif #ifndef ETHERTYPE_8021Q #define ETHERTYPE_8021Q 0x8100 #endif #ifndef ETHERTYPE_IPV6 #define ETHERTYPE_IPV6 0x86DD #endif #if USE_PPP #include #define PPP_ADDRESS_VAL 0xff /* The address byte value */ #define PPP_CONTROL_VAL 0x03 /* The control byte value */ #endif #if defined(__linux__) || defined(__GLIBC__) || defined(__GNU__) #define uh_dport dest #define uh_sport source #endif typedef struct { struct in6_addr src; int count; } AgentAddr; typedef struct { char *s; int count; } StringCounter; typedef struct { struct in6_addr addr; char *str; } StringAddr; /* This struct cobbles together Source and Nld */ typedef struct { StringAddr straddr; int count; } StringAddrCounter; typedef struct { int cnt; void *ptr; } SortItem; typedef struct _rfc1035_header rfc1035_header; struct _rfc1035_header { unsigned short id; unsigned int qr:1; unsigned int opcode:4; unsigned int aa:1; unsigned int tc:1; unsigned int rd:1; unsigned int ra:1; unsigned int rcode:4; unsigned short qdcount; unsigned short ancount; unsigned short nscount; unsigned short arcount; }; struct ip_list_s { struct in6_addr addr; void *data; struct ip_list_s *next; }; typedef struct ip_list_s ip_list_t; char *device = NULL; pcap_t *pcap = NULL; /* * bpf_program_str used to default to: * * udp dst port 53 and udp[10:2] & 0x8000 = 0 * * but that didn't work so well with IPv6. Now we have the command line options * -Q and -R to choose counting queries, responses, or both. */ char *bpf_program_str = "udp port 53"; WINDOW *w; static unsigned short port53; void (*SubReport) (void)= NULL; int (*handle_datalink) (const u_char * pkt, int len)= NULL; int Quit = 0; char *progname = NULL; int anon_flag = 0; int max_level = 2; int cur_level = 1; int promisc_flag = 1; ip_list_t *IgnoreList = NULL; int do_redraw = 1; int opt_count_queries = 0; int opt_count_replies = 0; int opt_count_ipv4 = 0; int opt_count_ipv6 = 0; /* * flags/features for non-interactive mode */ int interactive = 1; typedef int (printer) (const char *,...); printer *print_func = (printer *) printw; typedef const char *(col_fmt) (const SortItem *); #define T_MAX 65536 #ifndef T_A6 #define T_A6 38 #endif #ifndef T_SRV #define T_SRV 33 #endif #define C_MAX 65536 #define OP_MAX 16 #define RC_MAX 16 int query_count_intvl = 0; int query_count_total = 0; int reply_count_intvl = 0; int reply_count_total = 0; int qtype_counts[T_MAX]; int opcode_counts[OP_MAX]; int rcode_counts[RC_MAX]; int qclass_counts[C_MAX]; hashtbl *Sources = NULL; hashtbl *Destinations = NULL; hashtbl *Domains[10]; hashtbl *DomSrcs[10]; #ifdef __OpenBSD__ struct bpf_timeval last_ts; #else struct timeval last_ts; #endif time_t report_interval = 1; /* Prototypes */ void Sources_report(void); void Destinatioreport(void); void Qtypes_report(void); void Opcodes_report(void); void Rcodes_report(void); void Domain_report(); void DomSrc_report(); void Help_report(void); void ResetCounters(void); void report(void); typedef int Filter_t(unsigned short, unsigned short, const char *, const struct in6_addr *, const struct in6_addr *); Filter_t UnknownTldFilter; Filter_t AforAFilter; Filter_t RFC1918PtrFilter; Filter_t *Filter = NULL; /* * Compare two IP addresses. Start at the high end because the common case * will be IPv4 addresses which are all the same for the first 12 bytes. */ int cmp_in6_addr(const void *A, const void *B) { const struct in6_addr *a = A; const struct in6_addr *b = B; int i = 16; /* assert(sizeof(struct in6_addr) == 16); */ while (i--) { if (a->s6_addr[i] != b->s6_addr[i]) return (a->s6_addr[i] > b->s6_addr[i] ? 1 : -1); } return 0; } inline int ignore_list_match(const struct in6_addr *addr) { ip_list_t *ptr; for (ptr = IgnoreList; ptr != NULL; ptr = ptr->next) if (cmp_in6_addr(addr, &ptr->addr) == 0) return (1); return (0); } /* int ignore_list_match */ void ignore_list_add(const struct in6_addr *addr) { ip_list_t *new; if (ignore_list_match(addr) != 0) return; new = malloc(sizeof(ip_list_t)); if (new == NULL) { perror("malloc"); return; } memcpy(&new->addr, addr, sizeof(struct in6_addr)); new->next = IgnoreList; IgnoreList = new; } /* void ignore_list_add */ void ignore_list_add_name(const char *name) { struct addrinfo *ai_list; struct addrinfo *ai_ptr; struct in6_addr addr; int status; status = getaddrinfo(name, NULL, NULL, &ai_list); if (status != 0) return; for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) { if (ai_ptr->ai_family == AF_INET) { memset(&addr, '\0', sizeof(addr)); addr.s6_addr[10] = 0xFF; addr.s6_addr[11] = 0xFF; memcpy(addr.s6_addr + 12, &((struct sockaddr_in *)ai_ptr->ai_addr)->sin_addr, 4); ignore_list_add(&addr); } else { ignore_list_add(&((struct sockaddr_in6 *)ai_ptr->ai_addr)->sin6_addr); } } /* for */ freeaddrinfo(ai_list); } void in6_addr_from_buffer(struct in6_addr *ia, const void *buf, size_t buf_len, int family) { memset(ia, 0, sizeof(struct in6_addr)); if ((AF_INET == family) && (sizeof(uint32_t) == buf_len)) { ia->s6_addr[10] = 0xFF; ia->s6_addr[11] = 0xFF; memcpy(ia->s6_addr + 12, buf, buf_len); } else if ((AF_INET6 == family) && (sizeof(struct in6_addr) == buf_len)) { memcpy(ia, buf, buf_len); } } void allocate_anonymous_address(struct in6_addr *anon_addr, const struct in6_addr *orig_addr) { static ip_list_t *list = NULL; static uint32_t next_num = 0; ip_list_t *ptr; memset(anon_addr, 0, sizeof(struct in6_addr)); for (ptr = list; ptr != NULL; ptr = ptr->next) { if (cmp_in6_addr(orig_addr, &ptr->addr) == 0) break; } if (ptr == NULL) { ptr = (ip_list_t *) malloc(sizeof(ip_list_t) + sizeof(uint32_t)); if (ptr == NULL) return; ptr->addr = *orig_addr; ptr->data = (void *)(ptr + 1); *((uint32_t *) ptr->data) = next_num; next_num++; ptr->next = list; list = ptr; } memcpy(anon_addr->s6_addr + 12, ptr->data, 4); } int is_v4_in_v6(const struct in6_addr *addr) { int i; for (i = 0; i < 10; i++) if (addr->s6_addr[i] != 0) return (0); if ((addr->s6_addr[10] != 0xFF) || (addr->s6_addr[11] != 0xFF)) return (0); return 1; } char * anon_inet_ntoa(const struct in6_addr *addr) { static char buffer[INET6_ADDRSTRLEN]; struct in6_addr anon_addr; if (anon_flag) { allocate_anonymous_address(&anon_addr, addr); addr = &anon_addr; } if (is_v4_in_v6(addr)) { struct in_addr v4addr; memcpy(&v4addr.s_addr, addr->s6_addr + 12, 4); if (inet_ntop(AF_INET, (const void *)&v4addr, buffer, sizeof(buffer)) == NULL) return (NULL); } else { if (inet_ntop(AF_INET6, (const void *)addr, buffer, sizeof(buffer)) == NULL) return (NULL); } return (buffer); } AgentAddr * AgentAddr_lookup_or_add(hashtbl * tbl, struct in6_addr *addr) { AgentAddr *x = hash_find(addr, tbl); if (NULL == x) { x = calloc(1, sizeof(*x)); x->src = *addr; hash_add(&x->src, x, tbl); } return x; } static unsigned int string_hash(const void *s) { return hashendian(s, strlen(s), 0); } static int string_cmp(const void *a, const void *b) { return strcmp(a, b); } StringCounter * StringCounter_lookup_or_add(hashtbl * tbl, const char *s) { StringCounter *x = hash_find(s, tbl); if (NULL == x) { x = calloc(1, sizeof(*x)); x->s = strdup(s); hash_add(x->s, x, tbl); } return x; } static unsigned int stringaddr_hash(const void *p) { const StringAddr *sa = p; unsigned int h1 = hashendian(sa->str, strlen(sa->str), 0); unsigned int h2 = hashword((uint32_t *)&sa->addr, 1, h1); return h2; } static int stringaddr_cmp(const void *a, const void *b) { const StringAddr *A = a; const StringAddr *B = b; int x = strcmp(A->str, B->str); if (x) return x; return cmp_in6_addr(&A->addr, &B->addr); } StringAddrCounter * StringAddrCounter_lookup_or_add(hashtbl * tbl, const struct in6_addr *addr, const char *str) { StringAddr sa; StringAddrCounter *x; sa.addr = *addr; sa.str = (char *)str; x = hash_find(&sa, tbl); if (NULL == x) { x = calloc(1, sizeof(*x)); x->straddr.str = strdup(str); x->straddr.addr = *addr; hash_add(&x->straddr, x, tbl); } return x; } int SortItem_cmp(const void *A, const void *B) { const SortItem *a = A; const SortItem *b = B; if (a->cnt < b->cnt) return 1; if (a->cnt > b->cnt) return -1; if (a->ptr < b->ptr) return 1; if (a->ptr > b->ptr) return -1; return 0; } static unsigned int in_addr_hash(const void *key) { if (is_v4_in_v6(key)) return hashword((uint32_t *) key + 3, 1, 0); return hashword(key, 4, 0); } #define RFC1035_MAXLABELSZ 63 static int rfc1035NameUnpack(const char *buf, size_t sz, off_t * off, char *name, size_t ns ) { off_t no = 0; unsigned char c; size_t len; static int loop_detect = 0; if (loop_detect > 2) return 4; /* compression loop */ if (ns <= 0) return 4; /* probably compression loop */ do { if ((*off) >= sz) break; c = *(buf + (*off)); if (c > 191) { /* blasted compression */ int rc; unsigned short s; off_t ptr; memcpy(&s, buf + (*off), sizeof(s)); s = ntohs(s); (*off) += sizeof(s); /* Sanity check */ if ((*off) >= sz) return 1; /* message too short */ ptr = s & 0x3FFF; /* Make sure the pointer is inside this message */ if (ptr >= sz) return 2; /* bad compression ptr */ if (ptr < DNS_MSG_HDR_SZ) return 2; /* bad compression ptr */ loop_detect++; rc = rfc1035NameUnpack(buf, sz, &ptr, name + no, ns - no); loop_detect--; return rc; } else if (c > RFC1035_MAXLABELSZ) { /* * "(The 10 and 01 combinations are reserved for future use.)" */ return 3; /* reserved label/compression flags */ break; } else { (*off)++; len = (size_t) c; if (len == 0) break; if (len > (ns - 1)) len = ns - 1; if ((*off) + len > sz) return 4; /* message is too short */ if (no + len + 1 > ns) return 5; /* qname would overflow name buffer */ memcpy(name + no, buf + (*off), len); (*off) += len; no += len; *(name + (no++)) = '.'; } } while (c > 0); if (no > 0) *(name + no - 1) = '\0'; /* make sure we didn't allow someone to overflow the name buffer */ assert(no <= ns); return 0; } const char * QnameToNld(const char *qname, int nld) { const char *t = strrchr(qname, '.'); int dotcount = 1; if (NULL == t) t = qname; if (0 == strcmp(t, ".arpa")) dotcount--; while (t > qname && dotcount < nld) { t--; if ('.' == *t) dotcount++; } if (t > qname) t++; return t; } int handle_dns(const char *buf, int len, const struct in6_addr *src_addr, const struct in6_addr *dst_addr) { rfc1035_header qh; unsigned short us; char qname[MAX_QNAME_SZ]; unsigned short qtype; unsigned short qclass; off_t offset; char *t; const char *s; int x; StringCounter *sc; StringAddrCounter *ssc; int lvl; if (len < sizeof(qh)) return 0; memcpy(&us, buf + 00, 2); qh.id = ntohs(us); memcpy(&us, buf + 2, 2); us = ntohs(us); qh.qr = (us >> 15) & 0x01; if (0 == qh.qr && 0 == opt_count_queries) return 0; if (1 == qh.qr && 0 == opt_count_replies) return 0; qh.opcode = (us >> 11) & 0x0F; qh.aa = (us >> 10) & 0x01; qh.tc = (us >> 9) & 0x01; qh.rd = (us >> 8) & 0x01; qh.ra = (us >> 7) & 0x01; qh.rcode = us & 0x0F; memcpy(&us, buf + 4, 2); qh.qdcount = ntohs(us); memcpy(&us, buf + 6, 2); qh.ancount = ntohs(us); memcpy(&us, buf + 8, 2); qh.nscount = ntohs(us); memcpy(&us, buf + 10, 2); qh.arcount = ntohs(us); offset = sizeof(qh); memset(qname, '\0', MAX_QNAME_SZ); x = rfc1035NameUnpack(buf, len, &offset, qname, MAX_QNAME_SZ); if (0 != x) return 0; if ('\0' == qname[0]) strcpy(qname, "."); while ((t = strchr(qname, '\n'))) *t = ' '; while ((t = strchr(qname, '\r'))) *t = ' '; for (t = qname; *t; t++) *t = tolower(*t); memcpy(&us, buf + offset, 2); qtype = ntohs(us); memcpy(&us, buf + offset + 2, 2); qclass = ntohs(us); if (Filter && 0 == Filter(qtype, qclass, qname, src_addr, dst_addr)) return 0; /* gather stats */ qtype_counts[qtype]++; qclass_counts[qclass]++; opcode_counts[qh.opcode]++; rcode_counts[qh.rcode]++; for (lvl = 1; lvl <= max_level; lvl++) { s = QnameToNld(qname, lvl); sc = StringCounter_lookup_or_add(Domains[lvl], s); sc->count++; ssc = StringAddrCounter_lookup_or_add(DomSrcs[lvl], src_addr, s); ssc->count++; } if (0 == qh.qr) { query_count_intvl++; query_count_total++; } else { reply_count_intvl++; reply_count_total++; } return 1; } int handle_udp(const struct udphdr *udp, int len, const struct in6_addr *src_addr, const struct in6_addr *dst_addr) { char buf[PCAP_SNAPLEN]; if (port53 != udp->uh_dport && port53 != udp->uh_sport) return 0; memcpy(buf, udp + 1, len - sizeof(*udp)); if (0 == handle_dns(buf, len - sizeof(*udp), src_addr, dst_addr)) return 0; return 1; } #if USE_IPV6 int handle_ipv6(struct ip6_hdr *ipv6, int len) { char buf[PCAP_SNAPLEN]; int offset; int nexthdr; struct in6_addr src_addr; struct in6_addr dst_addr; uint16_t payload_len; AgentAddr *agent; if (0 == opt_count_ipv6) return 0; offset = sizeof(struct ip6_hdr); nexthdr = ipv6->ip6_nxt; src_addr = ipv6->ip6_src; dst_addr = ipv6->ip6_dst; payload_len = ntohs(ipv6->ip6_plen); if (ignore_list_match(&src_addr)) return (0); /* * Parse extension headers. This only handles the standard headers, as * defined in RFC 2460, correctly. Fragments are discarded. */ while ((IPPROTO_ROUTING == nexthdr) /* routing header */ ||(IPPROTO_HOPOPTS == nexthdr) /* Hop-by-Hop options. */ ||(IPPROTO_FRAGMENT == nexthdr) /* fragmentation header. */ ||(IPPROTO_DSTOPTS == nexthdr) /* destination options. */ ||(IPPROTO_DSTOPTS == nexthdr) /* destination options. */ ||(IPPROTO_AH == nexthdr) /* destination options. */ ||(IPPROTO_ESP == nexthdr)) { /* encapsulating security payload. */ struct { uint8_t nexthdr; uint8_t length; } ext_hdr; uint16_t ext_hdr_len; /* Catch broken packets */ if ((offset + sizeof(ext_hdr)) > len) return (0); /* Cannot handle fragments. */ if (IPPROTO_FRAGMENT == nexthdr) return (0); memcpy(&ext_hdr, (char *)ipv6 + offset, sizeof(ext_hdr)); nexthdr = ext_hdr.nexthdr; ext_hdr_len = (8 * (ntohs(ext_hdr.length) + 1)); /* This header is longer than the packets payload.. WTF? */ if (ext_hdr_len > payload_len) return (0); offset += ext_hdr_len; payload_len -= ext_hdr_len; } /* while */ /* Catch broken and empty packets */ if (((offset + payload_len) > len) || (payload_len == 0) || (payload_len > PCAP_SNAPLEN)) return (0); if (IPPROTO_UDP != nexthdr) return (0); memcpy(buf, (char *)ipv6 + offset, payload_len); if (handle_udp((struct udphdr *)buf, payload_len, &src_addr, &dst_addr) == 0) return (0); if ((agent = AgentAddr_lookup_or_add(Sources, &src_addr)) != NULL) agent->count++; if ((agent = AgentAddr_lookup_or_add(Destinations, &dst_addr)) != NULL) agent->count++; return (1); /* Success */ } #endif int handle_ipv4(const struct ip *ip, int len) { char buf[PCAP_SNAPLEN]; int offset = ip->ip_hl << 2; AgentAddr *clt; AgentAddr *srv; struct in6_addr src_addr; struct in6_addr dst_addr; #if USE_IPV6 if (ip->ip_v == 6) return (handle_ipv6((struct ip6_hdr *)ip, len)); #endif if (0 == opt_count_ipv4) return 0; in6_addr_from_buffer(&src_addr, &ip->ip_src.s_addr, sizeof(ip->ip_src.s_addr), AF_INET); in6_addr_from_buffer(&dst_addr, &ip->ip_dst.s_addr, sizeof(ip->ip_dst.s_addr), AF_INET); if (ignore_list_match(&src_addr)) return (0); if (IPPROTO_UDP != ip->ip_p) return 0; memcpy(buf, (char *)ip + offset, len - offset); if (0 == handle_udp((struct udphdr *)buf, len - offset, &src_addr, &dst_addr)) return 0; clt = AgentAddr_lookup_or_add(Sources, &src_addr); clt->count++; srv = AgentAddr_lookup_or_add(Destinations, &dst_addr); srv->count++; return 1; } #if USE_PPP int handle_ppp(const u_char * pkt, int len) { char buf[PCAP_SNAPLEN]; unsigned short us; unsigned short proto; if (len < 2) return 0; if (*pkt == PPP_ADDRESS_VAL && *(pkt + 1) == PPP_CONTROL_VAL) { pkt += 2; /* ACFC not used */ len -= 2; } if (len < 2) return 0; if (*pkt % 2) { proto = *pkt; /* PFC is used */ pkt++; len--; } else { memcpy(&us, pkt, sizeof(us)); proto = ntohs(us); pkt += 2; len -= 2; } if (ETHERTYPE_IP != proto && PPP_IP != proto) return 0; memcpy(buf, pkt, len); return handle_ipv4((struct ip *)buf, len); } #endif int handle_null(const u_char * pkt, int len) { unsigned int family; memcpy(&family, pkt, sizeof(family)); if (AF_INET == family) return handle_ipv4((struct ip *)(pkt + 4), len - 4); if (AF_INET6 == family) return handle_ipv6((struct ip6_hdr *)(pkt + 4), len - 4); return 0; } #ifdef DLT_LOOP int handle_loop(const u_char * pkt, int len) { unsigned int family; memcpy(&family, pkt, sizeof(family)); if (AF_INET == ntohl(family)) return handle_ipv4((struct ip *)(pkt + 4), len - 4); if (AF_INET6 == ntohl(family)) return handle_ipv6((struct ip6_hdr *)(pkt + 4), len - 4); return 0; } #endif #ifdef DLT_RAW int handle_raw(const u_char * pkt, int len) { return handle_ipv4((struct ip *)pkt, len); } #endif int handle_ip(const u_char * pkt, int len, unsigned short etype) { char buf[PCAP_SNAPLEN]; #if USE_IPV6 if (ETHERTYPE_IPV6 == etype) { memcpy(buf, pkt, len); return (handle_ipv6((struct ip6_hdr *)buf, len)); } else #endif if (ETHERTYPE_IP == etype) { memcpy(buf, pkt, len); return handle_ipv4((struct ip *)buf, len); } return 0; } int handle_ether(const u_char * pkt, int len) { struct ether_header *e = (void *)pkt; unsigned short etype = ntohs(e->ether_type); if (len < ETHER_HDR_LEN) return 0; pkt += ETHER_HDR_LEN; len -= ETHER_HDR_LEN; if (ETHERTYPE_8021Q == etype) { etype = ntohs(*(unsigned short *)(pkt + 2)); pkt += 4; len -= 4; } return handle_ip(pkt, len, etype); } #ifdef DLT_LINUX_SLL static int handle_linux_sll(const u_char * pkt, int len) { struct sll_header { uint16_t pkt_type; uint16_t dev_type; uint16_t addr_len; uint8_t addr[8]; uint16_t proto_type; } *hdr; uint16_t etype; if (len < sizeof(struct sll_header)) return (0); hdr = (struct sll_header *)pkt; pkt = (u_char *) (hdr + 1); len -= sizeof(struct sll_header); etype = ntohs(hdr->proto_type); return handle_ip(pkt, len, etype); } #endif /* DLT_LINUX_SLL */ void handle_pcap(u_char * udata, const struct pcap_pkthdr *hdr, const u_char * pkt) { if (hdr->caplen < ETHER_HDR_LEN) return; if (0 == handle_datalink(pkt, hdr->caplen)) return; last_ts = hdr->ts; } void cron_pre(void) { (void)0; } void cron_post(void) { query_count_intvl = 0; reply_count_intvl = 0; } void redraw() { cron_pre(); report(); cron_post(); do_redraw = 0; } void keyboard(void) { int ch; int old_do_redraw = do_redraw; /* * The screen should be redrawn after any valid key is pressed. */ do_redraw = 1; ch = getch() & 0xff; if (ch >= 'A' && ch <= 'Z') ch += 'a' - 'A'; switch (ch) { case 's': SubReport = Sources_report; break; case 'd': SubReport = Destinatioreport; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': SubReport = Domain_report; cur_level = ch - '0'; break; case '!': SubReport = DomSrc_report; cur_level = 1; break; case 'c': case '@': SubReport = DomSrc_report; cur_level = 2; break; case '#': SubReport = DomSrc_report; cur_level = 3; break; case '$': SubReport = DomSrc_report; cur_level = 4; break; case '%': SubReport = DomSrc_report; cur_level = 5; break; case '^': SubReport = DomSrc_report; cur_level = 6; break; case '&': SubReport = DomSrc_report; cur_level = 7; break; case '*': SubReport = DomSrc_report; cur_level = 8; break; case '(': SubReport = DomSrc_report; cur_level = 9; break; case 't': SubReport = Qtypes_report; break; case 'o': SubReport = Opcodes_report; break; case 'r': SubReport = Rcodes_report; break; case 030: Quit = 1; break; case 022: ResetCounters(); break; case '?': SubReport = Help_report; break; case ' ': /* noop - just redraw the screen */ break; default: do_redraw = old_do_redraw; break; } } void gotsigalrm(int sig) { do_redraw = 1; signal(sig, gotsigalrm); } void Help_report(void) { print_func(" s - Sources list\n"); print_func(" d - Destinations list\n"); print_func(" t - Query types\n"); print_func(" o - Opcodes\n"); print_func(" r - Rcodes\n"); print_func(" 1 - 1st level Query Names" "\t! - with Sources\n"); print_func(" 2 - 2nd level Query Names" "\t@ - with Sources\n"); print_func(" 3 - 3rd level Query Names" "\t# - with Sources\n"); print_func(" 4 - 4th level Query Names" "\t$ - with Sources\n"); print_func(" 5 - 5th level Query Names" "\t%% - with Sources\n"); print_func(" 6 - 6th level Query Names" "\t^ - with Sources\n"); print_func(" 7 - 7th level Query Names" "\t& - with Sources\n"); print_func(" 8 - 8th level Query Names" "\t* - with Sources\n"); print_func(" 9 - 9th level Query Names" "\t( - with Sources\n"); print_func("^R - Reset counters\n"); print_func("^X - Exit\n"); print_func("\n"); print_func(" ? - this\n"); } char * qtype_str(int t) { static char buf[30]; switch (t) { case T_A: return "A?"; break; case T_NS: return "NS?"; break; case T_CNAME: return "CNAME?"; break; case T_SOA: return "SOA?"; break; case T_PTR: return "PTR?"; break; case T_MX: return "MX?"; break; case T_TXT: return "TXT?"; break; case T_SIG: return "SIG?"; break; case T_KEY: return "KEY?"; break; case T_AAAA: return "AAAA?"; break; case T_LOC: return "LOC?"; break; case T_SRV: return "SRV?"; break; case T_A6: return "A6?"; break; case T_ANY: return "ANY?"; break; default: snprintf(buf, 30, "#%d?", t); return buf; } /* NOTREACHED */ } char * opcode_str(int o) { static char buf[30]; switch (o) { case 0: return "Query"; break; case 1: return "Iquery"; break; case 2: return "Status"; break; case 4: return "Notify"; break; case 5: return "Update"; break; default: snprintf(buf, 30, "Opcode%d", o); return buf; } /* NOTREACHED */ } char * rcode_str(int r) { static char buf[30]; switch (r) { case 0: return "Noerror"; break; case 1: return "Formerr"; break; case 2: return "Servfail"; break; case 3: return "Nxdomain"; break; case 4: return "Notimpl"; break; case 5: return "Refused"; break; case 6: return "Yxdomain"; break; case 7: return "Yxrrset"; break; case 8: return "Nxrrset"; break; case 9: return "Notauth"; break; case 10: return "Notzone"; break; default: snprintf(buf, 30, "Rcode%d", r); return buf; } /* NOTREACHED */ } int get_nlines(void) { if (interactive) return getmaxy(w) - 6; else return 50; } int get_ncols(void) { if (interactive) return getmaxx(w); else return 80; } const char * StringCounter_col_fmt(const SortItem * si) { StringCounter *sc = si->ptr; return sc->s; } const char * dashes(int n) { static char *buf = "-----------------------------------------------" "-----------------------------------------------------------------" "-----------------------------------------------------------------" "-----------------------------------------------------------------" "-----------------------------------------------------------------" "-----------------------------------------------------------------"; return &buf[strlen(buf) - n]; } void Table_report(SortItem * sorted, int rows, const char *col1, const char *col2, col_fmt F1, col_fmt F2, int base) { int W1 = strlen(col1); int W2 = col2 ? strlen(col2) : 0; int WC = 9; /* width of "Count" column */ int WP = 6; /* width of "Percent" column */ int i; int nlines = get_nlines(); int ncols = get_ncols(); char fmt1[64]; char fmt2[64]; if (nlines > rows) nlines = rows; for (i = 0; i < nlines; i++) { const char *t = F1(sorted + i); if (W1 < strlen(t)) W1 = strlen(t); } if (W1 + 1 + WC + 1 + WP + 1 > ncols) W1 = ncols - 1 - WC - 1 - WP - 1; if (NULL == col2 || NULL == F2) { snprintf(fmt1, 64, "%%-%d.%ds %%%ds %%%ds\n", W1, W1, WC, WP); snprintf(fmt2, 64, "%%-%d.%ds %%%dd %%%d.1f\n", W1, W1, WC, WP); print_func(fmt1, col1, "Count", "%"); print_func(fmt1, dashes(W1), dashes(WC), dashes(WP)); for (i = 0; i < nlines; i++) { const char *t = F1(sorted + i); print_func(fmt2, t, (sorted + i)->cnt, 100.0 * (sorted + i)->cnt / base); } } else { for (i = 0; i < nlines; i++) { const char *t = F2(sorted + i); if (W2 < strlen(t)) W2 = strlen(t); } if (W2 + 1 + W1 + 1 + WC + 1 + WP + 1 > ncols) W2 = ncols - 1 - W1 - 1 - WC - 1 - WP - 1; snprintf(fmt1, 64, "%%-%d.%ds %%-%d.%ds %%%ds %%%ds\n", W1, W1, W2, W2, WC, WP); snprintf(fmt2, 64, "%%-%d.%ds %%-%d.%ds %%%dd %%%d.1f\n", W1, W1, W2, W2, WC, WP); print_func(fmt1, col1, col2, "Count", "%"); print_func(fmt1, dashes(W1), dashes(W2), dashes(WC), dashes(WP)); for (i = 0; i < nlines; i++) { const char *t = F1(sorted + i); const char *q = F2(sorted + i); print_func(fmt2, t, q, (sorted + i)->cnt, 100.0 * (sorted + i)->cnt / base); } } } void StringCounter_report(hashtbl * tbl, char *what) { int sortsize = hash_count(tbl); SortItem *sortme = calloc(sortsize, sizeof(SortItem)); StringCounter *sc; hash_iter_init(tbl); sortsize = 0; while ((sc = hash_iterate(tbl))) { sortme[sortsize].cnt = sc->count; sortme[sortsize].ptr = sc; sortsize++; } qsort(sortme, sortsize, sizeof(SortItem), SortItem_cmp); Table_report(sortme, sortsize, what, NULL, StringCounter_col_fmt, NULL, query_count_total + reply_count_total); free(sortme); } void StringAddrCounter_free(void *p) { StringAddrCounter *ssc = p; free(ssc->straddr.str); } void Domain_report(void) { if (cur_level > max_level) { print_func("\tYou must start %s with -l %d\n", progname, cur_level); print_func("\tto collect this level of domain stats.\n", progname); return; } StringCounter_report(Domains[cur_level], "Query Name"); } const char * Qtype_col_fmt(const SortItem * si) { return si->ptr; } void Qtypes_report(void) { int type; SortItem *sortme = calloc(T_MAX, sizeof(SortItem)); int sortsize = 0; for (type = 0; type < T_MAX; type++) { if (0 == qtype_counts[type]) continue; sortme[sortsize].cnt = qtype_counts[type]; sortme[sortsize].ptr = qtype_str(type); /* XXX danger */ sortsize++; } qsort(sortme, sortsize, sizeof(SortItem), SortItem_cmp); Table_report(sortme, sortsize, "Query Type", NULL, Qtype_col_fmt, NULL, query_count_total + reply_count_total); free(sortme); } void Opcodes_report(void) { int op; SortItem *sortme = calloc(OP_MAX, sizeof(SortItem)); int sortsize = 0; for (op = 0; op < OP_MAX; op++) { if (0 == opcode_counts[op]) continue; sortme[sortsize].cnt = opcode_counts[op]; sortme[sortsize].ptr = opcode_str(op); /* XXX danger */ sortsize++; } qsort(sortme, sortsize, sizeof(SortItem), SortItem_cmp); Table_report(sortme, sortsize, "Opcode", NULL, Qtype_col_fmt, NULL, query_count_total + reply_count_total); free(sortme); } void Rcodes_report(void) { int rc; SortItem *sortme = calloc(OP_MAX, sizeof(SortItem)); int sortsize = 0; for (rc = 0; rc < RC_MAX; rc++) { if (0 == rcode_counts[rc]) continue; sortme[sortsize].cnt = rcode_counts[rc]; sortme[sortsize].ptr = rcode_str(rc); /* XXX danger */ sortsize++; } qsort(sortme, sortsize, sizeof(SortItem), SortItem_cmp); Table_report(sortme, sortsize, "Rcode", NULL, Qtype_col_fmt, NULL, query_count_total + reply_count_total); free(sortme); } const char * AgentAddr_col_fmt(const SortItem * si) { AgentAddr *a = si->ptr; return anon_inet_ntoa(&a->src); } void AgentAddr_report(hashtbl * tbl, const char *what) { int sortsize = hash_count(tbl); SortItem *sortme = calloc(sortsize, sizeof(SortItem)); AgentAddr *a; hash_iter_init(tbl); sortsize = 0; while ((a = hash_iterate(tbl))) { sortme[sortsize].cnt = a->count; sortme[sortsize].ptr = a; sortsize++; } qsort(sortme, sortsize, sizeof(SortItem), SortItem_cmp); Table_report(sortme, sortsize, what, NULL, AgentAddr_col_fmt, NULL, query_count_total + reply_count_total); free(sortme); } const char * StringAddr_col1_fmt(const SortItem * si) { StringAddrCounter *ssc = si->ptr; return anon_inet_ntoa(&ssc->straddr.addr); } const char * StringAddr_col2_fmt(const SortItem * si) { StringAddrCounter *ssc = si->ptr; return ssc->straddr.str; } void StringAddrCounter_report(hashtbl * tbl, char *what1, char *what2) { int sortsize = hash_count(tbl); SortItem *sortme = calloc(sortsize, sizeof(SortItem)); StringAddrCounter *ssc; hash_iter_init(tbl); sortsize = 0; while ((ssc = hash_iterate(tbl))) { sortme[sortsize].cnt = ssc->count; sortme[sortsize].ptr = ssc; sortsize++; } qsort(sortme, sortsize, sizeof(SortItem), SortItem_cmp); Table_report(sortme, sortsize, what1, what2, StringAddr_col1_fmt, StringAddr_col2_fmt, query_count_total + reply_count_total); free(sortme); } void DomSrc_report(void) { if (cur_level > max_level) { print_func("\tYou must start %s with -l %d\n", progname, cur_level); print_func("\tto collect this level of domain stats.\n", progname); return; } StringAddrCounter_report(DomSrcs[cur_level], "Source", "Query Name"); } void Sources_report(void) { AgentAddr_report(Sources, "Sources"); } void Destinatioreport(void) { AgentAddr_report(Destinations, "Destinations"); } void report(void) { int Y = 0; time_t t; move(Y, 0); if (opt_count_queries) { print_func("Queries: %d new, %d total", query_count_intvl, query_count_total); clrtoeol(); Y++; } if (opt_count_replies) { move(Y, 0); print_func("Replies: %d new, %d total", reply_count_intvl, reply_count_total); clrtoeol(); Y++; } t = time(NULL); move(0, get_ncols() - 25); print_func("%s", ctime(&t)); move(Y + 1, 0); clrtobot(); if (SubReport) SubReport(); refresh(); } /* * === BEGIN FILTERS ========================================================== */ #include "known_tlds.h" int UnknownTldFilter(unsigned short qt, unsigned short qc, const char *qn, const struct in6_addr *sip, const struct in6_addr *dip) { const char *tld = QnameToNld(qn, 1); unsigned int i; if (NULL == tld) return 1; /* tld is unknown */ for (i = 0; KnownTLDS[i]; i++) if (0 == strcmp(KnownTLDS[i], tld)) return 0; /* tld is known */ return 1; /* tld is unknown */ } int AforAFilter(unsigned short qt, unsigned short qc, const char *qn, const struct in6_addr *sip, const struct in6_addr *dip) { struct in_addr a; if (qt != T_A) return 0; return inet_aton(qn, &a); } int RFC1918PtrFilter(unsigned short qt, unsigned short qc, const char *qn, const struct in6_addr *sip, const struct in6_addr *dip) { char *t; char q[128]; unsigned int i = 0; if (qt != T_PTR) return 0; strncpy(q, qn, sizeof(q) - 1); q[sizeof(q) - 1] = '\0'; t = strstr(q, ".in-addr.arpa"); if (NULL == t) return 0; *t = '\0'; for (t = strtok(q, "."); t; t = strtok(NULL, ".")) { i >>= 8; i |= ((atoi(t) & 0xff) << 24); } if ((i & 0xff000000) == 0x0a000000) return 1; if ((i & 0xfff00000) == 0xac100000) return 1; if ((i & 0xffff0000) == 0xc0a80000) return 1; return 0; } void set_filter(const char *fn) { if (0 == strcmp(fn, "unknown-tlds")) Filter = UnknownTldFilter; else if (0 == strcmp(fn, "A-for-A")) Filter = AforAFilter; else if (0 == strcmp(fn, "rfc1918-ptr")) Filter = RFC1918PtrFilter; else Filter = NULL; } /* * === END FILTERS ========================================================== */ void init_curses(void) { w = initscr(); cbreak(); noecho(); nodelay(w, 1); } void ResetCounters(void) { int lvl; if (NULL == Sources) Sources = hash_create(16384, in_addr_hash, cmp_in6_addr); if (NULL == Destinations) Destinations = hash_create(16384, in_addr_hash, cmp_in6_addr); for (lvl = 1; lvl <= max_level; lvl++) { if (NULL != Domains[lvl]) continue; Domains[lvl] = hash_create(8192, string_hash, string_cmp); DomSrcs[lvl] = hash_create(8192, stringaddr_hash, stringaddr_cmp); } query_count_intvl = 0; query_count_total = 0; memset(qtype_counts, '\0', sizeof(qtype_counts)); memset(qclass_counts, '\0', sizeof(qclass_counts)); memset(opcode_counts, '\0', sizeof(opcode_counts)); memset(rcode_counts, '\0', sizeof(rcode_counts)); hash_free(Sources, free); hash_free(Destinations, free); for (lvl = 1; lvl <= max_level; lvl++) { hash_free(Domains[lvl], free); hash_free(DomSrcs[lvl], StringAddrCounter_free); } memset(&last_ts, '\0', sizeof(last_ts)); } void usage(void) { fprintf(stderr, "usage: %s [opts] netdevice|savefile\n", progname); fprintf(stderr, "\t-4\tCount IPv4 packets\n"); fprintf(stderr, "\t-6\tCount IPv6 packets\n"); fprintf(stderr, "\t-a\tAnonymize IP Addrs\n"); fprintf(stderr, "\t-b expr\tBPF program code\n"); fprintf(stderr, "\t-i addr\tIgnore this source IP address\n"); fprintf(stderr, "\t-p\tDon't put interface in promiscuous mode\n"); fprintf(stderr, "\t-r\tRedraw interval, in seconds\n"); fprintf(stderr, "\t-l N\tEnable domain stats up to N components\n"); fprintf(stderr, "\t-f\tfilter-name\n"); fprintf(stderr, "\n"); fprintf(stderr, "Available filters:\n"); fprintf(stderr, "\tunknown-tlds\n"); fprintf(stderr, "\tA-for-A\n"); fprintf(stderr, "\trfc1918-ptr\n"); exit(1); } int pcap_select(pcap_t * p, int sec, int usec) { fd_set R; struct timeval to; FD_ZERO(&R); FD_SET(pcap_fileno(p), &R); to.tv_sec = sec; to.tv_usec = usec; return select(pcap_fileno(p) + 1, &R, NULL, NULL, &to); } int main(int argc, char *argv[]) { char errbuf[PCAP_ERRBUF_SIZE]; int x; struct stat sb; int readfile_state = 0; int redraw_interval = 1; struct itimerval redraw_itv; struct bpf_program fp; port53 = htons(53); SubReport = Sources_report; progname = strdup(strrchr(argv[0], '/') ? strchr(argv[0], '/') + 1 : argv[0]); srandom(time(NULL)); while ((x = getopt(argc, argv, "46ab:f:i:l:pr:QR")) != -1) { switch (x) { case '4': opt_count_ipv4 = 1; break; case '6': opt_count_ipv6 = 1; break; case 'a': anon_flag = 1; break; case 's': max_level = 2; break; case 't': max_level = 3; break; case 'l': max_level = atoi(optarg); if (max_level < 1 || max_level > 9) usage(); break; case 'p': promisc_flag = 0; break; case 'b': bpf_program_str = strdup(optarg); break; case 'i': ignore_list_add_name(optarg); break; case 'f': set_filter(optarg); break; case 'r': redraw_interval = atoi(optarg); break; case 'Q': opt_count_queries = 1; break; case 'R': opt_count_replies = 1; break; default: usage(); break; } } argc -= optind; argv += optind; if (argc < 1) usage(); device = strdup(argv[0]); if (0 == opt_count_queries && 0 == opt_count_replies) opt_count_queries = 1; if (0 == opt_count_ipv4 && 0 == opt_count_ipv6) opt_count_ipv4 = opt_count_ipv6 = 1; if (0 == stat(device, &sb)) readfile_state = 1; if (readfile_state) { pcap = pcap_open_offline(device, errbuf); } else { pcap = pcap_open_live(device, PCAP_SNAPLEN, promisc_flag, 1000, errbuf); } if (NULL == pcap) { fprintf(stderr, "pcap_open_*: %s\n", errbuf); exit(1); } if (0 == isatty(1)) { if (0 == readfile_state) { fprintf(stderr, "Non-interactive mode requires savefile argument\n"); exit(1); } interactive = 0; print_func = printf; } memset(&fp, '\0', sizeof(fp)); x = pcap_compile(pcap, &fp, bpf_program_str, 1, 0); if (x < 0) { fprintf(stderr, "pcap_compile failed\n"); exit(1); } x = pcap_setfilter(pcap, &fp); if (x < 0) { fprintf(stderr, "pcap_setfilter failed\n"); exit(1); } /* * non-blocking call added for Mac OS X bugfix. Sent by Max Horn. ref * http://www.tcpdump.org/lists/workers/2002/09/msg00033.html */ x = pcap_setnonblock(pcap, 1, errbuf); if (x < 0) { fprintf(stderr, "pcap_setnonblock failed: %s\n", errbuf); exit(1); } switch (pcap_datalink(pcap)) { case DLT_EN10MB: handle_datalink = handle_ether; break; #if USE_PPP case DLT_PPP: handle_datalink = handle_ppp; break; #endif #ifdef DLT_LOOP case DLT_LOOP: handle_datalink = handle_loop; break; #endif #ifdef DLT_RAW case DLT_RAW: handle_datalink = handle_raw; break; #endif #ifdef DLT_LINUX_SLL case DLT_LINUX_SLL: handle_datalink = handle_linux_sll; break; #endif case DLT_NULL: handle_datalink = handle_null; break; default: fprintf(stderr, "unsupported data link type %d\n", pcap_datalink(pcap)); return 1; break; } ResetCounters(); if (interactive) { init_curses(); redraw(); if (redraw_interval) { signal(SIGALRM, gotsigalrm); redraw_itv.it_interval.tv_sec = redraw_interval; redraw_itv.it_interval.tv_usec = 0; redraw_itv.it_value.tv_sec = redraw_interval; redraw_itv.it_value.tv_usec = 0; setitimer(ITIMER_REAL, &redraw_itv, NULL); } while (0 == Quit) { if (readfile_state < 2) { /* * On some OSes select() might return 0 even when there are * packets to process. Thus, we always ignore its return value * and just call pcap_dispatch() anyway. */ if (0 == readfile_state) /* interactive */ pcap_select(pcap, 1, 0); x = pcap_dispatch(pcap, 50, handle_pcap, NULL); } if (0 == x && 1 == readfile_state) { /* block on keyboard until user quits */ readfile_state++; nodelay(w, 0); } keyboard(); if (do_redraw || 0 == redraw_interval) redraw(); } endwin(); /* klin, Thu Nov 28 08:56:51 2002 */ } else { while (pcap_dispatch(pcap, 50, handle_pcap, NULL)) (void)0; cron_pre(); Sources_report(); print_func("\n"); Destinatioreport(); print_func("\n"); Qtypes_report(); print_func("\n"); Opcodes_report(); Rcodes_report(); for (cur_level = 1; cur_level <= max_level; cur_level++) { print_func("\n"); Domain_report(); } for (cur_level = 1; cur_level <= max_level; cur_level++) { print_func("\n"); DomSrc_report(); } } pcap_close(pcap); return 0; }