From 1e7b5f4e249a6c30ef39dcba63dca317fc89e314 Mon Sep 17 00:00:00 2001 From: Florian Forster Date: Tue, 3 Oct 2006 17:06:47 +0200 Subject: [PATCH] Added a first rough draft of the new named plugin. --- src/dnstop.c | 1397 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/dnstop.h | 36 ++ src/named.c | 287 ++++++++++++ 3 files changed, 1720 insertions(+) create mode 100644 src/dnstop.c create mode 100644 src/dnstop.h create mode 100644 src/named.c diff --git a/src/dnstop.c b/src/dnstop.c new file mode 100644 index 00000000..93ad9bfd --- /dev/null +++ b/src/dnstop.c @@ -0,0 +1,1397 @@ +/* + * collectd - src/dnstop.c + * Copyright (C) 2006 Florian octo Forster + * Copyright (C) 2002 The Measurement Factory, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of The Measurement Factory nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Authors: + * The Measurement Factory, Inc. + * Florian octo Forster + */ + +#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 + +#define PCAP_SNAPLEN 1460 +#define MAX_QNAME_SZ 512 +#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 + +#if USE_PPP +#include +#define PPP_ADDRESS_VAL 0xff /* The address byte value */ +#define PPP_CONTROL_VAL 0x03 /* The control byte value */ +#endif + +#ifdef __linux__ +#define uh_dport dest +#endif + +#include "dnstop.h" + +/* + * Type definitions + */ +typedef struct _AgentAddr AgentAddr; +struct _AgentAddr { + struct in_addr src; + int count; + AgentAddr *next; +}; + +typedef struct _StringCounter StringCounter; +struct _StringCounter { + char *s; + int count; + StringCounter *next; +}; + +/* This struct cobbles together Source and Sld */ +typedef struct _StringAddrCounter StringAddrCounter; +struct _StringAddrCounter { + struct in_addr src; + char *str; + int count; + StringAddrCounter *next; +}; + +typedef struct _foo foo; +struct _foo { + int cnt; + void *ptr; +}; + +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; +}; + +typedef struct _AnonMap AnonMap; +struct _AnonMap { + struct in_addr real; + struct in_addr anon; + AnonMap *next; +}; + +typedef int Filter_t(unsigned short, + unsigned short, + const char *, + const struct in_addr, + const struct in_addr); + +typedef int (printer)(const char *, ...); + +/* + * flags/features for non-interactive mode + */ + +#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 + +/* + * Global variables + */ +static int interactive = 1; +static char *device = NULL; +static struct in_addr ignore_addr; +static pcap_t *pcap = NULL; +static char *bpf_program_str = "udp dst port 53 and udp[10:2] & 0x8000 = 0"; +static WINDOW *w; +static unsigned short port53; +static void (*SubReport) (void) = NULL; +static int (*handle_datalink) (const u_char * pkt, int len) = NULL; +static int Quit = 0; +static char *progname = NULL; +static int anon_flag = 0; +static int sld_flag = 0; +static int nld_flag = 0; +static int promisc_flag = 1; +static AnonMap *Anons = NULL; + +static int query_count_intvl = 0; +static int query_count_total = 0; +int qtype_counts[T_MAX]; +static int opcode_counts[OP_MAX]; +static int qclass_counts[C_MAX]; +static AgentAddr *Sources = NULL; +static AgentAddr *Destinations = NULL; +static StringCounter *Tlds = NULL; +static StringCounter *Slds = NULL; +static StringCounter *Nlds = NULL; +static StringAddrCounter *SSC2 = NULL; +static StringAddrCounter *SSC3 = NULL; +#ifdef __OpenBSD__ +static struct bpf_timeval last_ts; +#else +static struct timeval last_ts; +#endif + +/* Prototypes */ +static void SldBySource_report(void); +static void NldBySource_report(void); +static void Sources_report(void); +static void Destinatioreport(void); +static void Qtypes_report(void); +static void Opcodes_report(void); +static void Tld_report(void); +static void Sld_report(void); +static void Nld_report(void); +static void Help_report(void); +static void ResetCounters(void); + +static Filter_t UnknownTldFilter; +static Filter_t AforAFilter; +static Filter_t RFC1918PtrFilter; +static Filter_t *Filter = NULL; + +static printer *print_func = (printer *) printw; + +static struct in_addr +AnonMap_lookup_or_add(AnonMap ** headP, struct in_addr real) +{ + AnonMap **T; + for (T = headP; (*T); T = &(*T)->next) + if ((*T)->real.s_addr == real.s_addr) + return (*T)->anon; + (*T) = calloc(1, sizeof(**T)); + (*T)->real = real; + (*T)->anon.s_addr = random(); + return (*T)->anon; +} + +static char * +anon_inet_ntoa(struct in_addr a) +{ + if (anon_flag) + a = AnonMap_lookup_or_add(&Anons, a); + return inet_ntoa(a); +} + +static AgentAddr * +AgentAddr_lookup_or_add(AgentAddr ** headP, struct in_addr a) +{ + AgentAddr **T; + for (T = headP; (*T); T = &(*T)->next) + if ((*T)->src.s_addr == a.s_addr) + return (*T); + (*T) = calloc(1, sizeof(**T)); + (*T)->src = a; + return (*T); +} + +static StringCounter * +StringCounter_lookup_or_add(StringCounter ** headP, const char *s) +{ + StringCounter **T; + for (T = headP; (*T); T = &(*T)->next) + if (0 == strcmp((*T)->s, s)) + return (*T); + (*T) = calloc(1, sizeof(**T)); + (*T)->s = strdup(s); + return (*T); +} + +static StringAddrCounter * +StringAddrCounter_lookup_or_add(StringAddrCounter ** headP, struct in_addr a, const char *str) +{ + StringAddrCounter **T; + for (T = headP; (*T); T = &(*T)->next) + if (0 == strcmp((*T)->str, str)) + if ((*T)->src.s_addr == a.s_addr) + return (*T); + (*T) = calloc(1, sizeof(**T)); + (*T)->str = strdup(str); + (*T)->src = a; + return (*T); +} + +static int +foo_cmp(const void *A, const void *B) +{ + const foo *a = A; + const foo *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 void +AgentAddr_sort(AgentAddr ** headP) +{ + foo *sortme; + int n_agents = 0; + int i; + AgentAddr *a; + for (a = *headP; a; a = a->next) + n_agents++; + sortme = calloc(n_agents, sizeof(foo)); + n_agents = 0; + for (a = *headP; a; a = a->next) { + sortme[n_agents].cnt = a->count; + sortme[n_agents].ptr = a; + n_agents++; + } + qsort(sortme, n_agents, sizeof(foo), foo_cmp); + for (i = 0; i < n_agents; i++) { + *headP = sortme[i].ptr; + headP = &(*headP)->next; + } + free(sortme); + *headP = NULL; +} + +static void +StringCounter_sort(StringCounter ** headP) +{ + foo *sortme; + int n_things = 0; + int i; + StringCounter *sc; + for (sc = *headP; sc; sc = sc->next) + n_things++; + sortme = calloc(n_things, sizeof(foo)); + n_things = 0; + for (sc = *headP; sc; sc = sc->next) { + sortme[n_things].cnt = sc->count; + sortme[n_things].ptr = sc; + n_things++; + } + qsort(sortme, n_things, sizeof(foo), foo_cmp); + for (i = 0; i < n_things; i++) { + *headP = sortme[i].ptr; + headP = &(*headP)->next; + } + free(sortme); + *headP = NULL; +} + +static void +StringAddrCounter_sort(StringAddrCounter ** headP) +{ + foo *sortme; + int n_things = 0; + int i; + StringAddrCounter *ssc; + for (ssc = *headP; ssc; ssc = ssc->next) + n_things++; + sortme = calloc(n_things, sizeof(foo)); + n_things = 0; + for (ssc = *headP; ssc; ssc = ssc->next) { + sortme[n_things].cnt = ssc->count; + sortme[n_things].ptr = ssc; + n_things++; + } + qsort(sortme, n_things, sizeof(foo), foo_cmp); + for (i = 0; i < n_things; i++) { + *headP = sortme[i].ptr; + headP = &(*headP)->next; + } + free(sortme); + *headP = NULL; +} + +#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; + assert(ns > 0); + do { + if ((*off) >= sz) + break; + c = *(buf + (*off)); + if (c > 191) { + /* blasted compression */ + 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; + ptr = s & 0x3FFF; + /* Make sure the pointer is inside this message */ + if (ptr >= sz) + return 2; + return rfc1035NameUnpack(buf, sz, &ptr, name + no, ns - no); + } else if (c > RFC1035_MAXLABELSZ) { + /* + * "(The 10 and 01 combinations are reserved for future use.)" + */ + break; + return 3; + } else { + (*off)++; + len = (size_t) c; + if (len == 0) + break; + if (len > (ns - 1)) + len = ns - 1; + if ((*off) + len > sz) /* message is too short */ + return 4; + memcpy(name + no, buf + (*off), len); + (*off) += len; + no += len; + *(name + (no++)) = '.'; + } + } while (c > 0); + *(name + no - 1) = '\0'; + /* make sure we didn't allow someone to overflow the name buffer */ + assert(no <= ns); + return 0; +} + +static 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; +} + +static int +handle_dns(const char *buf, int len, const struct in_addr sip, const struct in_addr dip) +{ + 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; + + 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; + 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, sip, dip)) + return 0; + + /* gather stats */ + qtype_counts[qtype]++; + qclass_counts[qclass]++; + opcode_counts[qh.opcode]++; + + s = QnameToNld(qname, 1); + sc = StringCounter_lookup_or_add(&Tlds, s); + sc->count++; + + if (sld_flag) { + s = QnameToNld(qname, 2); + sc = StringCounter_lookup_or_add(&Slds, s); + sc->count++; + + /* increment StringAddrCounter */ + ssc = StringAddrCounter_lookup_or_add(&SSC2, sip, s); + ssc->count++; + + } + if (nld_flag) { + s = QnameToNld(qname, 3); + sc = StringCounter_lookup_or_add(&Nlds, s); + sc->count++; + + /* increment StringAddrCounter */ + ssc = StringAddrCounter_lookup_or_add(&SSC3, sip, s); + ssc->count++; + + } + return 1; +} + +static int +handle_udp(const struct udphdr *udp, int len, struct in_addr sip, struct in_addr dip) +{ + char buf[PCAP_SNAPLEN]; + if (port53 != udp->uh_dport) + return 0; + memcpy(buf, udp + 1, len - sizeof(*udp)); + if (0 == handle_dns(buf, len - sizeof(*udp), sip, dip)) + return 0; + return 1; +} + +static int +handle_ip(const struct ip *ip, int len) +{ + char buf[PCAP_SNAPLEN]; + int offset = ip->ip_hl << 2; + AgentAddr *clt; + AgentAddr *srv; + if (ignore_addr.s_addr) + if (ip->ip_src.s_addr == ignore_addr.s_addr) + return 0; + if (IPPROTO_UDP != ip->ip_p) + return 0; + memcpy(buf, (void *) ip + offset, len - offset); + if (0 == handle_udp((struct udphdr *) buf, len - offset, ip->ip_src, ip->ip_dst)) + return 0; + clt = AgentAddr_lookup_or_add(&Sources, ip->ip_src); + clt->count++; + srv = AgentAddr_lookup_or_add(&Destinations, ip->ip_dst); + srv->count++; + return 1; +} + +#if USE_PPP +static 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_ip((struct ip *) buf, len); +} + +#endif + +static int +handle_null(const u_char * pkt, int len) +{ + unsigned int family; + memcpy(&family, pkt, sizeof(family)); + if (AF_INET != family) + return 0; + return handle_ip((struct ip *) (pkt + 4), len - 4); +} + +#ifdef DLT_LOOP +static int +handle_loop(const u_char * pkt, int len) +{ + unsigned int family; + memcpy(&family, pkt, sizeof(family)); + if (AF_INET != ntohl(family)) + return 0; + return handle_ip((struct ip *) (pkt + 4), len - 4); +} + +#endif + +#ifdef DLT_RAW +static int +handle_raw(const u_char * pkt, int len) +{ + return handle_ip((struct ip *) pkt, len); +} + +#endif + +static int +handle_ether(const u_char * pkt, int len) +{ + char buf[PCAP_SNAPLEN]; + 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; + } + if (ETHERTYPE_IP != etype) + return 0; + memcpy(buf, pkt, len); + return handle_ip((struct ip *) buf, len); +} + +/* public function */ +void handle_pcap(u_char *udata, const struct pcap_pkthdr *hdr, const u_char *pkt) +{ + int status; + + if (hdr->caplen < ETHER_HDR_LEN) + return; + + switch (pcap_datalink (pcap)) + { + case DLT_EN10MB: + status = handle_ether (pkt, hdr->caplen); + break; +#if USE_PPP + case DLT_PPP: + status = handle_ppp (pkt, hdr->caplen); + break; +#endif +#ifdef DLT_LOOP + case DLT_LOOP: + status = handle_loop (pkt, hdr->caplen); + break; +#endif +#ifdef DLT_RAW + case DLT_RAW: + status = handle_raw (pkt, hdr->caplen); + break; +#endif + case DLT_NULL: + status = handle_null (pkt, hdr->caplen); + break; + + default: + fprintf (stderr, "unsupported data link type %d\n", + pcap_datalink(pcap)); + status = 0; + break; + } /* switch (pcap_datalink(pcap)) */ + + if (0 == status) + return; + + query_count_intvl++; + query_count_total++; + last_ts = hdr->ts; +} + +static void +cron_pre(void) +{ + AgentAddr_sort(&Sources); + AgentAddr_sort(&Destinations); + StringCounter_sort(&Tlds); + StringCounter_sort(&Slds); + StringCounter_sort(&Nlds); + StringAddrCounter_sort(&SSC2); + StringAddrCounter_sort(&SSC3); +} + +static void +cron_post(void) +{ + query_count_intvl = 0; +} + +static void +keyboard(void) +{ + int ch; + 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': + SubReport = Tld_report; + break; + case '2': + SubReport = Sld_report; + break; + case '3': + SubReport = Nld_report; + break; + case 'c': + case '@': + SubReport = SldBySource_report; + break; + case '#': + SubReport = NldBySource_report; + break; + case 't': + SubReport = Qtypes_report; + break; + case 'o': + SubReport = Opcodes_report; + break; + case 030: + Quit = 1; + break; + case 022: + ResetCounters(); + break; + case '?': + SubReport = Help_report; + break; + default: + break; + } +} + +static 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(" 1 - TLD list\n"); + print_func(" 2 - SLD list\n"); + print_func(" 3 - 3LD list\n"); + print_func(" @ - SLD+Sources list\n"); + print_func(" # - 3LD+Sources list\n"); + print_func("^R - Reset counters\n"); + print_func("^X - Exit\n"); + print_func("\n"); + print_func("? - this\n"); +} + +static 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 */ +} + +static 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 */ +} + +static int +get_nlines(void) +{ + if (interactive) + return getmaxy(w) - 6; + else + return 50; +} + +static void +StringCounter_report(StringCounter * list, char *what) +{ + StringCounter *sc; + int nlines = get_nlines(); + print_func("%-30s %9s %6s\n", what, "count", "%"); + print_func("%-30s %9s %6s\n", + "------------------------------", "---------", "------"); + for (sc = list; sc; sc = sc->next) { + print_func("%-30.30s %9d %6.1f\n", + sc->s, + sc->count, + 100.0 * sc->count / query_count_total); + if (0 == --nlines) + break; + } +} + +static void +StringCounter_free(StringCounter ** headP) +{ + StringCounter *sc; + void *next; + for (sc = *headP; sc; sc = next) { + next = sc->next; + free(sc->s); + free(sc); + } + *headP = NULL; +} + +static void +StringAddrCounter_free(StringAddrCounter ** headP) +{ + StringAddrCounter *ssc; + void *next; + for (ssc = *headP; ssc; ssc = next) { + next = ssc->next; + free(ssc->str); + free(ssc); + } + *headP = NULL; +} + +static void +Tld_report(void) +{ + StringCounter_report(Tlds, "TLD"); +} + +static void +Sld_report(void) +{ + if (0 == sld_flag) { + print_func("\tYou must start %s with the -s option\n", progname); + print_func("\tto collect 2nd level domain stats.\n", progname); + } else { + StringCounter_report(Slds, "SLD"); + } +} + +static void +Nld_report(void) +{ + if (0 == nld_flag) { + print_func("\tYou must start %s with the -t option\n", progname); + print_func("\tto collect 3nd level domain stats.\n", progname); + } else { + StringCounter_report(Nlds, "3LD"); + } +} + +static void +Qtypes_report(void) +{ + int type; + int nlines = get_nlines(); + print_func("%-10s %9s %6s\n", "Query Type", "count", "%"); + print_func("%-10s %9s %6s\n", "----------", "---------", "------"); + for (type = 0; type < T_MAX; type++) { + if (0 == qtype_counts[type]) + continue; + print_func("%-10s %9d %6.1f\n", + qtype_str(type), + qtype_counts[type], + 100.0 * qtype_counts[type] / query_count_total); + if (0 == --nlines) + break; + } +} + +static void +Opcodes_report(void) +{ + int op; + int nlines = get_nlines(); + print_func("%-10s %9s %6s\n", "Opcode ", "count", "%"); + print_func("%-10s %9s %6s\n", "----------", "---------", "------"); + for (op = 0; op < OP_MAX; op++) { + if (0 == opcode_counts[op]) + continue; + print_func("%-10s %9d %6.1f\n", + opcode_str(op), + opcode_counts[op], + 100.0 * opcode_counts[op] / query_count_total); + if (0 == --nlines) + break; + } +} + +static void +AgentAddr_report(AgentAddr * list, const char *what) +{ + AgentAddr *agent; + int nlines = get_nlines(); + print_func("%-16s %9s %6s\n", what, "count", "%"); + print_func("%-16s %9s %6s\n", "----------------", "---------", "------"); + for (agent = list; agent; agent = agent->next) { + print_func("%-16s %9d %6.1f\n", + anon_inet_ntoa(agent->src), + agent->count, + 100.0 * agent->count / query_count_total); + if (0 == --nlines) + break; + } +} + +static void +Combo_report(StringAddrCounter * list, char *what1, char *what2) +{ + StringAddrCounter *ssc; + int nlines = get_nlines(); + print_func("%-16s %-32s %9s %6s\n", what1, what2, "count", "%"); + print_func("%-16s %-32s %9s %6s\n", + "----------------", "--------------------", "---------", "------"); + for (ssc = list; ssc; ssc = ssc->next) { + print_func("%-16s %-32s %9d %6.1f\n", + anon_inet_ntoa(ssc->src), + ssc->str, + ssc->count, + 100.0 * ssc->count / query_count_total); + if (0 == --nlines) + break; + } +} + +static void +SldBySource_report(void) +{ + if (0 == sld_flag) { + print_func("\tYou must start %s with the -s option\n", progname); + print_func("\tto collect 2nd level domain stats.\n", progname); + } else { + Combo_report(SSC2, "Source", "SLD"); + } +} + +static void +NldBySource_report(void) +{ + if (0 == nld_flag) { + print_func("\tYou must start %s with the -t option\n", progname); + print_func("\tto collect 3nd level domain stats.\n", progname); + } else { + Combo_report(SSC3, "Source", "3LD"); + } +} + + +static void +AgentAddr_free(AgentAddr ** headP) +{ + AgentAddr *aa; + void *next; + for (aa = *headP; aa; aa = next) { + next = aa->next; + free(aa); + } + *headP = NULL; +} + +static void +Sources_report(void) +{ + AgentAddr_report(Sources, "Sources"); +} + +static void +Destinatioreport(void) +{ + AgentAddr_report(Destinations, "Destinations"); +} + +static void +report(void) +{ + move(0, 0); + print_func("%d new queries, %d total queries", + query_count_intvl, query_count_total); + clrtoeol(); + if (last_ts.tv_sec) { + time_t t = (time_t) last_ts.tv_sec; + move(0, 50); + print_func("%s", ctime(&t)); + } + move(2, 0); + clrtobot(); + if (SubReport) + SubReport(); + refresh(); +} + +/* + * === BEGIN FILTERS ========================================================== + */ + +#include "known_tlds.h" + +static int +UnknownTldFilter(unsigned short qt, unsigned short qc, const char *qn, const struct in_addr sip, const struct in_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 */ +} + +static int +AforAFilter(unsigned short qt, unsigned short qc, const char *qn, const struct in_addr sip, const struct in_addr dip) +{ + struct in_addr a; + if (qt != T_A) + return 0; + return inet_aton(qn, &a); +} + +static int +RFC1918PtrFilter(unsigned short qt, unsigned short qc, const char *qn, const struct in_addr sip, const struct in_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; +} + +static 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 ========================================================== + */ + +static void +init_curses(void) +{ + w = initscr(); + cbreak(); + noecho(); + nodelay(w, 1); +} + +static void +ResetCounters(void) +{ + 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)); + AgentAddr_free(&Sources); + AgentAddr_free(&Destinations); + StringCounter_free(&Tlds); + StringCounter_free(&Slds); + StringCounter_free(&Nlds); + StringAddrCounter_free(&SSC2); + StringAddrCounter_free(&SSC3); + memset(&last_ts, '\0', sizeof(last_ts)); +} + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [opts] netdevice|savefile\n", + progname); + 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-s\tEnable 2nd level domain stats collection\n"); + fprintf(stderr, "\t-t\tEnable 3nd level domain stats collection\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); +} + +static 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); +} + +#if 0 +static int +main(int argc, char *argv[]) +{ + char errbuf[PCAP_ERRBUF_SIZE]; + int x; + struct stat sb; + int readfile_state = 0; + struct bpf_program fp; + + port53 = htons(53); + SubReport = Sources_report; + ignore_addr.s_addr = 0; + progname = strdup(strrchr(argv[0], '/') ? strchr(argv[0], '/') + 1 : argv[0]); + srandom(time(NULL)); + ResetCounters(); + + while ((x = getopt(argc, argv, "ab:f:i:pst")) != -1) { + switch (x) { + case 'a': + anon_flag = 1; + break; + case 's': + sld_flag = 1; + break; + case 't': + nld_flag = 1; + break; + case 'p': + promisc_flag = 0; + break; + case 'b': + bpf_program_str = strdup(optarg); + break; + case 'i': + ignore_addr.s_addr = inet_addr(optarg); + break; + case 'f': + set_filter(optarg); + break; + default: + usage(); + break; + } + } + argc -= optind; + argv += optind; + + if (argc < 1) + usage(); + device = strdup(argv[0]); + + 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 + case DLT_NULL: + handle_datalink = handle_null; + break; + default: + fprintf(stderr, "unsupported data link type %d\n", + pcap_datalink(pcap)); + return 1; + break; + } + if (interactive) { + init_curses(); + 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(); + cron_pre(); + report(); + cron_post(); + } + 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(); print_func("\n"); + Tld_report(); print_func("\n"); + Sld_report(); print_func("\n"); + Nld_report(); print_func("\n"); + SldBySource_report(); + } + + pcap_close(pcap); + return 0; +} /* static int main(int argc, char *argv[]) */ +#endif diff --git a/src/dnstop.h b/src/dnstop.h new file mode 100644 index 00000000..3d3eb057 --- /dev/null +++ b/src/dnstop.h @@ -0,0 +1,36 @@ +/* + * collectd - src/dnstop.c + * Copyright (C) 2006 Florian octo Forster + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of The Measurement Factory nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Authors: + * Florian octo Forster + */ + +extern int qtype_counts[T_MAX]; + +void handle_pcap (u_char * udata, const struct pcap_pkthdr *hdr, const u_char * pkt); diff --git a/src/named.c b/src/named.c new file mode 100644 index 00000000..41a5776e --- /dev/null +++ b/src/named.c @@ -0,0 +1,287 @@ +/** + * collectd - src/named.c + * Copyright (C) 2006 Florian octo Forster + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Florian octo Forster + **/ + +#include "collectd.h" +#include "common.h" +#include "plugin.h" + +#define MODULE_NAME "named" + +#if HAVE_LIBPCAP +# define NAMED_HAVE_CONFIG 1 +#else +# define NAMED_HAVE_CONFIG 0 +#endif + +#if HAVE_LIBPCAP +# include "dnstop.h" +# define NAMED_HAVE_READ 1 +#else +# define NAMED_HAVE_READ 0 +#endif + +static char qtype_file = "named/qtype-%s.rrd"; + +static char *qtype_ds_def[] = +{ + "DS:value:COUNTER:"COLLECTD_HEARTBEAT":0:U", + NULL +}; +static int qtype_ds_num = 1; + +#if NAMED_HAVE_CONFIG +#if HAVE_LIBPCAP +static char *config_keys[] = +{ + "Interface", + NULL +}; +static int config_keys_num = 1; +#endif /* HAVE_LIBPCAP */ +#endif /* NAMED_HAVE_CONFIG */ + +#if HAVE_LIBPCAP +static char *pcap_device = NULL; +static int pipe_fd; +#endif + +#if NAMED_HAVE_CONFIG +static int traffic_config (char *key, char *value) +{ +#if HAVE_LIBPCAP + if (strcasecmp (key, "Interface") == 0) + { + if (pcap_device != NULL) + free (pcap_device); + if ((pcap_device = strdup (value)) == NULL) + return (1); + } + else + { + return (-1); + } + + return (0); +#endif /* HAVE_LIBPCAP */ +} +#endif /* NAMED_HAVE_CONFIG */ + +static int named_child_send_data (void) +{ + int values[2 * T_MAX]; + int values_num; + int i; + + values_num = 0; + for (i = 0; i < T_MAX; i++) + { + if (qtype_counts[i] != 0) + { + values[2 * values_num] = i; + values[(2 * values_num) + 1] = qtype_counts[i]; + values_num++; + } + } + + if (swrite (pipe_fd, (const void *) &values_num, sizeof (values_num)) != 0) + { + syslog (LOG_ERR, "named plugin: Writing to pipe failed: %s", + strerror (errno)); + return (-1); + } + + if (swrite (pipe_fd, (const void *) values, 2 * sizeof (int) * values_num) != 0) + { + syslog (LOG_ERR, "named plugin: Writing to pipe failed: %s", + strerror (errno)); + return (-1); + } + + return (values_num); +} + +static void named_child_loop (void) +{ + pcap_t *pcap_obj; + char pcap_error[PCAP_ERRBUF_SIZE]; + + struct pollfd poll_fds[2]; + int status; + + /* Passing `pcap_device == NULL' is okay and the same as passign "any" */ + pcap_obj = pcap_open_live (pcap_device, /* Not promiscuous */ 0, + /* no read timeout */ 0, pcap_error); + if (pcap_obj == NULL) + { + syslog (LOG_ERR, "named plugin: Opening interface `%s' failed: %s", + (pcap_device != NULL) ? pcap_device : "any", + pcap_error); + close (pipe_fd); + return; + } + + /* Set up pipe end */ + poll_fds[0].fd = pipe_fd; + poll_fds[0].events = POLLOUT; + + /* Set up pcap device */ + poll_fds[1].fd = pcap_fileno (pcap_obj); + poll_fds[1].events = POLLIN | POLLPRI; + + while (42) + { + status = poll (poll_fds, 2, -1 /* wait forever for a change */); + + if (status < 0) + { + syslog (LOG_ERR, "named plugin: poll(2) failed: %s", + strerror (errno)); + break; + } + + if (poll_fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) + { + syslog (LOG_NOTICE, "named plugin: Pipe closed. Exiting."); + break; + } + else if (poss_fds[0].revents & POLLOUT) + { + if (named_child_send_data () < 0) + { + break; + } + } + + if (poll_fds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) + { + syslog (LOG_ERR, "named plugin: pcap-device closed. Exiting."); + break; + } + else if (poll_fds[1].revents & (POLLIN | POLLPRI)) + { + /* TODO: Read and analyse packet */ + status = pcap_dispatch (pcap_obj, + 10 /* Only handle 10 packets at a time */, + handle_pcap /* callback */, + NULL /* Whatever this means.. */); + if (status < 0) + { + syslog (LOG_ERR, "named plugin: pcap_dispatch failed: %s", + pcap_geterr (pcap_obj)); + break; + } + } + } /* while (42) */ + + close (pipe_fd); + pcap_close (pcap_obj); +} /* static void named_child_loop (void) */ + +static void named_init (void) +{ +#if HAVE_LIBPCAP + int pipe_fds[2]; + pid_t pid_child; + + if (pipe (pipe_fds) != 0) + { + syslog (LOG_ERR, "named plugin: pipe(2) failed: %s", + strerror (errno)); + return; + } + + /* Fork off child */ + pid_child = fork (); + if (pid_child < 0) + { + syslog (LOG_ERR, "named plugin: fork(2) failed: %s", + strerror (errno)); + close (pipe_fds[0]); + close (pipe_fds[1]); + pcap_close (pcap_obj); + return; + } + else if (pid_child != 0) + { + /* parent: Close the writing end, keep the reading end. */ + pipe_fd = pipe_fds[0]; + close (pipe_fds[1]); + } + else + { + /* child: Close the reading end, keep the writing end. */ + pipe_fd = pipe_fds[1]; + close (pipe_fds[0]); + + named_child_loop (); + exit (0); + } + + fcntl (pipe_fd, F_SETFL, O_NONBLOCK); +#endif +} + +#if NAMED_HAVE_READ +static void named_read (void) +{ + int values[2 * T_MAX]; + int values_num; + int qtype; + int counter; + int i; + + if (sread (pipe_fd, (void *) &values_num, sizeof (values_num)) != 0) + { + syslog (LOG_ERR, "named plugin: Reading from the pipe failed: %s", + strerror (errno)); + return; + } + + assert ((values_num >= 0) && (values_num <= T_MAX)); + + if (sread (pipe_fd, (void *) values, 2 * sizeof (int) * values_num) != 0) + { + syslog (LOG_ERR, "named plugin: Reading from the pipe failed: %s", + strerror (errno)); + return; + } + + for (i = 0; i < values_num; i++) + { + qtype = values[2 * i]; + counter = values[(2 * i) + 1]; + + DBG ("qtype = %i; counter = %i;", qtype, counter); + } +} +#else /* if !NAMED_HAVE_READ */ +# define named_read NULL +#endif + +void module_register (void) +{ + plugin_register (MODULE_NAME, named_init, named_read, NULL); + /* TODO */ + cf_register (MODULE_NAME, named_config, config_keys, config_keys_num); +} + +#undef MODULE_NAME -- 2.11.0