diff options
Diffstat (limited to 'plugins/royparse')
-rw-r--r-- | plugins/royparse/Makefile.am | 22 | ||||
-rw-r--r-- | plugins/royparse/royparse.c | 272 | ||||
-rwxr-xr-x | plugins/royparse/test1.sh | 15 |
3 files changed, 309 insertions, 0 deletions
diff --git a/plugins/royparse/Makefile.am b/plugins/royparse/Makefile.am new file mode 100644 index 0000000..795a277 --- /dev/null +++ b/plugins/royparse/Makefile.am @@ -0,0 +1,22 @@ +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in +CLEANFILES = *.gcda *.gcno *.gcov + +AM_CFLAGS = -I$(srcdir) \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/isc \ + $(SECCOMPFLAGS) + +pkglib_LTLIBRARIES = royparse.la +royparse_la_SOURCES = royparse.c +royparse_la_LDFLAGS = -module -avoid-version + +TESTS = test1.sh +EXTRA_DIST = $(TESTS) +CLEANFILES += test1.out* *.pcap-dist + +if ENABLE_GCOV +gcov-local: + for src in $(royparse_la_SOURCES); do \ + gcov -o .libs -l -r -s "$(srcdir)" "$$src"; \ + done +endif diff --git a/plugins/royparse/royparse.c b/plugins/royparse/royparse.c new file mode 100644 index 0000000..83e26a6 --- /dev/null +++ b/plugins/royparse/royparse.c @@ -0,0 +1,272 @@ +/* + * Author Roy Arends + * + * Copyright (c) 2017-2021, OARC, 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 copyright holder 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 HOLDER 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. + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <netinet/in.h> + +#include "dnscap_common.h" + +#include <errno.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#include <pcap.h> +#include <ldns/ldns.h> + +static logerr_t* logerr; +static char* opt_q = 0; +static char* opt_r = 0; + +pcap_t* pcap; +pcap_dumper_t* q_out = 0; +static FILE* r_out = 0; + +output_t royparse_output; +ia_str_t royparse_ia_str = 0; + +void royparse_usage() +{ + fprintf(stderr, + "\nroyparse splits a pcap into two streams: queries in pcap format and responses in ASCII format.\n" + "\nroyparse.so options:\n" + "\t-? print these instructions and exit\n" + "\t-q <arg> query pcap stream output file name (default: no output)\n" + "\t-r <arg> royparse output file name (default: stdout)\n"); +} + +void royparse_extension(int ext, void* arg) +{ + switch (ext) { + case DNSCAP_EXT_IA_STR: + royparse_ia_str = (ia_str_t)arg; + break; + } +} + +void royparse_getopt(int* argc, char** argv[]) +{ + int c; + + while ((c = getopt(*argc, *argv, "?q:r:")) != EOF) { + switch (c) { + case 'q': + if (opt_q) + free(opt_q); + opt_q = strdup(optarg); + break; + case 'r': + if (opt_r) + free(opt_r); + opt_r = strdup(optarg); + break; + case '?': + royparse_usage(); + if (!optopt || optopt == '?') { + exit(0); + } + // fallthrough + default: + exit(1); + } + } +} + +int royparse_start(logerr_t* a_logerr) +{ + logerr = a_logerr; + + if (opt_q) { + pcap = pcap_open_dead(DLT_RAW, 65535); + q_out = pcap_dump_open(pcap, opt_q); + if (q_out == 0) { + logerr("%s: %s\n", opt_q, strerror(errno)); + exit(1); + } + } + if (opt_r) { + r_out = fopen(opt_r, "w"); + if (r_out == 0) { + logerr("%s: %s\n", opt_r, strerror(errno)); + exit(1); + } + } else { + r_out = stdout; + } + setbuf(r_out, 0); + + return 0; +} + +void royparse_stop() +{ + if (q_out != 0) { + pcap_close(pcap); + pcap_dump_close(q_out); + } + if (r_out != stdout) + fclose(r_out); +} + +int royparse_open(my_bpftimeval ts) +{ + return 0; +} + +int royparse_close(my_bpftimeval ts) +{ + return 0; +} + +void royparse_normalize(char* str) +{ + /* + * The "normalize" function converts upper case characters to lower case, + * and replaces the space and comma characters with a question mark. + */ + + for (; *str; str++) { + if (('A' <= *str) && (*str <= 'Z')) { + *str |= 32; + } else if ((*str == ',') || (*str == ' ')) { + *str = '?'; + } + } +} + +void royparse_output(const char* descr, iaddr from, iaddr to, uint8_t proto, unsigned flags, + unsigned sport, unsigned dport, my_bpftimeval ts, + const u_char* pkt_copy, unsigned olen, + const u_char* payload, unsigned payloadlen) +{ + if (flags & DNSCAP_OUTPUT_ISDNS) { + ldns_buffer* buf = ldns_buffer_new(512); + if (!buf) { + logerr("out of memmory\n"); + exit(1); + } + + ldns_pkt* pkt; + if (ldns_wire2pkt(&pkt, payload, payloadlen) != LDNS_STATUS_OK) { + fprintf(r_out, "ERR\n"); + ldns_buffer_free(buf); + return; + } + if (ldns_pkt_qr(pkt) && sport == 53) { + fprintf(r_out, "%cD_", ldns_pkt_rd(pkt) ? 'R' : 'N'); + + switch (ldns_pkt_get_opcode(pkt)) { + case LDNS_PACKET_QUERY: + fprintf(r_out, "QUERY"); + break; + case LDNS_PACKET_NOTIFY: + fprintf(r_out, "NOTIFY"); + break; + case LDNS_PACKET_UPDATE: + fprintf(r_out, "UPDATE"); + break; + default: + fprintf(r_out, "ELSE"); + } + + fprintf(r_out, "_%u_%cA_", ldns_pkt_ancount(pkt) ? 1 : 0, ldns_pkt_aa(pkt) ? 'A' : 'N'); + + switch (ldns_pkt_get_rcode(pkt)) { + case LDNS_RCODE_NOERROR: + fprintf(r_out, "NOERROR"); + break; + case LDNS_RCODE_FORMERR: + fprintf(r_out, "FORMERR"); + break; + case LDNS_RCODE_NXDOMAIN: + fprintf(r_out, "NXDOMAIN"); + break; + case LDNS_RCODE_NOTIMPL: + fprintf(r_out, "NOTIMP"); + break; + case LDNS_RCODE_REFUSED: + fprintf(r_out, "REFUSED"); + break; + case LDNS_RCODE_NOTAUTH: + fprintf(r_out, "NOTAUTH"); + break; + default: + fprintf(r_out, "ELSE"); + } + + fprintf(r_out, " %s,", royparse_ia_str(to)); + + ldns_rr_list* qds = ldns_pkt_question(pkt); + ldns_rr* qd; + if (qds && (qd = ldns_rr_list_rr(qds, 0))) { + if (ldns_rdf2buffer_str(buf, ldns_rr_owner(qd)) == LDNS_STATUS_OK) { + royparse_normalize((char*)ldns_buffer_begin(buf)); + fprintf(r_out, "%s%s,%u", (char*)ldns_buffer_begin(buf), + ((char*)ldns_buffer_begin(buf))[0] == '.' ? "" : ".", + ldns_rr_get_type(qd)); + } else { + fprintf(r_out, "ERR,ERR"); + } + } else + fprintf(r_out, ","); + + fprintf(r_out, ",%zu,%s%s%s%s", ldns_pkt_size(pkt), ldns_pkt_id(pkt) < 256 ? "-L" : "", + ldns_pkt_tc(pkt) ? "-TC" : "", + ldns_pkt_ad(pkt) ? "-AD" : "", + ldns_pkt_cd(pkt) ? "-CD" : ""); + if (ldns_pkt_edns(pkt)) { + fprintf(r_out, "-%c", ldns_pkt_edns_do(pkt) ? 'D' : 'E'); + } + fprintf(r_out, "\n"); + } else if (opt_q != 0 && !ldns_pkt_qr(pkt) && dport == 53) { + struct pcap_pkthdr h; + if (flags & DNSCAP_OUTPUT_ISLAYER) { + ldns_pkt_free(pkt); + ldns_buffer_free(buf); + return; + } + memset(&h, 0, sizeof h); + h.ts = ts; + h.len = h.caplen = olen; + pcap_dump((u_char*)q_out, &h, pkt_copy); + } + ldns_pkt_free(pkt); + ldns_buffer_free(buf); + } +} diff --git a/plugins/royparse/test1.sh b/plugins/royparse/test1.sh new file mode 100755 index 0000000..ec07886 --- /dev/null +++ b/plugins/royparse/test1.sh @@ -0,0 +1,15 @@ +#!/bin/sh -xe + +plugin=`find . -name 'royparse.so' | head -n 1` +if [ -z "$plugin" ]; then + echo "Unable to find the royparse plugin" + exit 1 +fi + +ln -fs "$srcdir/../../src/test/dns.pcap" dns.pcap-dist + +../../src/dnscap -r dns.pcap-dist -g -P "$plugin" -? +../../src/dnscap -r dns.pcap-dist -g -P "$plugin" +../../src/dnscap -r dns.pcap-dist -g -P "$plugin" -q test1.out +../../src/dnscap -r dns.pcap-dist -g -P "$plugin" -r test1.out +! ../../src/dnscap -r dns.pcap-dist -g -P "$plugin" -X |