summaryrefslogtreecommitdiffstats
path: root/plugins/rssm
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/rssm')
-rw-r--r--plugins/rssm/.gitignore1
-rw-r--r--plugins/rssm/Makefile.am42
-rw-r--r--plugins/rssm/README.md41
-rwxr-xr-xplugins/rssm/dnscap-rssm-rssac002209
-rw-r--r--plugins/rssm/dnscap-rssm-rssac002.1.in98
-rw-r--r--plugins/rssm/rssm.c696
-rw-r--r--plugins/rssm/test1.gold58
-rwxr-xr-xplugins/rssm/test1.sh11
-rw-r--r--plugins/rssm/test2.gold43
-rwxr-xr-xplugins/rssm/test2.sh5
-rw-r--r--plugins/rssm/test3.gold57
-rwxr-xr-xplugins/rssm/test3.sh11
-rwxr-xr-xplugins/rssm/test4.sh14
-rw-r--r--plugins/rssm/test5.gold58
-rwxr-xr-xplugins/rssm/test5.sh11
15 files changed, 1355 insertions, 0 deletions
diff --git a/plugins/rssm/.gitignore b/plugins/rssm/.gitignore
new file mode 100644
index 0000000..7d3ffec
--- /dev/null
+++ b/plugins/rssm/.gitignore
@@ -0,0 +1 @@
+hashtbl.c
diff --git a/plugins/rssm/Makefile.am b/plugins/rssm/Makefile.am
new file mode 100644
index 0000000..08e4429
--- /dev/null
+++ b/plugins/rssm/Makefile.am
@@ -0,0 +1,42 @@
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
+CLEANFILES = $(srcdir)/hashtbl.c \
+ hashtbl.c *.gcda *.gcno *.gcov
+
+AM_CFLAGS = -I$(srcdir) \
+ -I$(top_srcdir)/src \
+ -I$(top_srcdir)/isc \
+ $(SECCOMPFLAGS) \
+ $(libldns_CFLAGS)
+
+pkglib_LTLIBRARIES = rssm.la
+rssm_la_SOURCES = rssm.c
+nodist_rssm_la_SOURCES = hashtbl.c
+BUILT_SOURCES = hashtbl.c
+rssm_la_LDFLAGS = -module -avoid-version $(libldns_LIBS)
+TESTS = test1.sh test2.sh test3.sh test4.sh test5.sh
+EXTRA_DIST = $(TESTS) test1.gold test2.gold dnscap-rssm-rssac002.1.in \
+ test3.gold test5.gold
+dist_bin_SCRIPTS = dnscap-rssm-rssac002
+man1_MANS = dnscap-rssm-rssac002.1
+CLEANFILES += test1.20161020.152301.075993 test2.out $(man1_MANS) \
+ test3.20181127.155200.414188 test4.*20161020.152301.075993 \
+ test5.20180110.112241.543825
+
+if ENABLE_GCOV
+gcov-local:
+ for src in $(rssm_la_SOURCES) $(nodist_rssm_la_SOURCES); do \
+ gcov -o .libs -l -r -s "$(srcdir)" "$$src"; \
+ done
+endif
+
+hashtbl.c: $(top_srcdir)/src/hashtbl.c
+ cp $(top_srcdir)/src/hashtbl.c ./
+
+$(srcdir)/hashtbl.c: $(top_srcdir)/src/hashtbl.c
+ cp $(top_srcdir)/src/hashtbl.c $(srcdir)/
+
+dnscap-rssm-rssac002.1: dnscap-rssm-rssac002.1.in Makefile
+ sed -e 's,[@]PACKAGE_VERSION[@],$(PACKAGE_VERSION),g' \
+ -e 's,[@]PACKAGE_URL[@],$(PACKAGE_URL),g' \
+ -e 's,[@]PACKAGE_BUGREPORT[@],$(PACKAGE_BUGREPORT),g' \
+ < $(srcdir)/dnscap-rssm-rssac002.1.in > dnscap-rssm-rssac002.1
diff --git a/plugins/rssm/README.md b/plugins/rssm/README.md
new file mode 100644
index 0000000..98fbde4
--- /dev/null
+++ b/plugins/rssm/README.md
@@ -0,0 +1,41 @@
+# Root Server Scaling Measurement (RSSM) plugin
+
+This plugin collects data as described by the [RSSAC002v3 specification](https://www.icann.org/en/system/files/files/rssac-002-measurements-root-06jun16-en.pdf)
+which has been created by [ICANN Root Server System Advisory Committee](https://www.icann.org/groups/rssac) (RSSAC).
+
+## Additions
+
+As the RSSAC002v3 specification states that measurements should be saved per
+24 hours interval, this plugin produces additional metrics that can be used
+to compile the 24 hours measurements allowing for variable time between
+output generation.
+
+Metric `dnscap-rssm-sources` has a hash entry called `sources` which lists
+IP addresses and the number of times they appeared.
+
+Metric `dnscap-rssm-aggregated-sources` has a hash entry called `aggregated-sources`
+which lists the aggregated IPv6 addresses by a /64 net and the number of times
+it has appeared.
+
+## Merge Tool
+
+The Perl script `dnscap-rssm-rssac002` is included and installed with `dnscap`
+and can be used to multiple combine RSSM plugin RSSAC002v3 YAML output files
+into one file.
+
+The script will merge and remove metric specific to this plugin and replace
+others to fill in correct values for the new time period. The earliest
+`start-period` found will be used for all metrics.
+
+**NOTE** no parsing of `start-period` is performed, it is up to the operator
+to only give input files related to the same 24 hour period.
+
+Options:
+- `--no-recompile`: Disabled the combining of metrics and the removal of
+ metrics specific to this plugin
+- `--keep-dnscap-rssm`: Do the combining but keep the metrics specific to
+ this plugin
+- `--sort`: Output will always start with `version:`, `service:`,
+ `start-period:` and `metric:`, rest of the values are not ordered by label.
+ This option enabled sorting of them, which is not required by the
+ specification but may help in debugging and testing cases.
diff --git a/plugins/rssm/dnscap-rssm-rssac002 b/plugins/rssm/dnscap-rssm-rssac002
new file mode 100755
index 0000000..f7eaaf6
--- /dev/null
+++ b/plugins/rssm/dnscap-rssm-rssac002
@@ -0,0 +1,209 @@
+#!/usr/bin/env perl
+#
+# Copyright (c) 2018-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.
+
+use strict;
+use warnings;
+use YAML;
+
+unless (scalar @ARGV > 1) {
+ print "usage: dnscap-rssm-rssac002 [--no-recompile|--keep-dnscap-rssm|--sort] <YAML files to merge...>\n";
+ exit(1);
+}
+
+my %service = ();
+my $earliest_start_period;
+my $recompile = 1;
+my $keep_dnscap_rssm = 0;
+my $sort = 0;
+
+foreach my $file (@ARGV) {
+ if ($file eq '--no-recompile') {
+ $recompile = 0;
+ next;
+ } elsif ($file eq '--keep-dnscap-rssm') {
+ $keep_dnscap_rssm = 1;
+ next;
+ } elsif ($file eq '--sort') {
+ $sort = 1;
+ next;
+ }
+ foreach my $doc (YAML::LoadFile($file)) {
+ my $version = delete $doc->{version};
+ my $service = delete $doc->{service};
+ my $start_period = delete $doc->{'start-period'};
+ my $metric = delete $doc->{metric};
+ unless ($version) {
+ die "$file: not valid RSSAC002 YAML, missing version";
+ }
+ unless ($service) {
+ die "$file: not valid RSSAC002 YAML, missing service";
+ }
+ unless ($start_period) {
+ die "$file: not valid RSSAC002 YAML, missing start-period";
+ }
+ unless ($metric) {
+ die "$file: not valid RSSAC002 YAML, missing metric";
+ }
+ unless ($version eq 'rssac002v3') {
+ die "$file: unsupported RSSAC002 version $version";
+ }
+
+ push(@{$service{$service}->{$metric}}, $doc);
+
+ if (!$earliest_start_period or $start_period lt $earliest_start_period) {
+ $earliest_start_period = $start_period;
+ }
+ }
+}
+
+foreach my $service (keys %service) {
+ foreach my $metric (keys %{$service{$service}}) {
+ my %doc = ();
+ foreach (@{$service{$service}->{$metric}}) {
+ eval {
+ merge(\%doc, $_);
+ };
+ if ($@) {
+ die "service $service metric $metric: $@";
+ }
+ }
+ $service{$service}->{$metric} = \%doc;
+ }
+}
+
+if ($recompile) {
+ foreach my $service (keys %service) {
+ my ($ipv4, $ipv6, $aggregated) = (0, 0, 0);
+ my $metric;
+
+ if ($keep_dnscap_rssm) {
+ $metric = $service{$service}->{'dnscap-rssm-sources'};
+ } else {
+ $metric = delete $service{$service}->{'dnscap-rssm-sources'};
+ }
+ if ($metric) {
+ if (ref($metric->{sources}) eq 'HASH') {
+ foreach my $ip (keys %{$metric->{sources}}) {
+ if ($ip =~ /:/o) {
+ $ipv6++;
+ } else {
+ $ipv4++;
+ }
+ }
+ }
+ }
+
+ if ($keep_dnscap_rssm) {
+ $metric = $service{$service}->{'dnscap-rssm-aggregated-sources'};
+ } else {
+ $metric = delete $service{$service}->{'dnscap-rssm-aggregated-sources'};
+ }
+ if ($metric) {
+ if (ref($metric->{'aggregated-sources'}) eq 'HASH') {
+ my @keys = keys %{$metric->{'aggregated-sources'}};
+ $aggregated += scalar @keys;
+ }
+ }
+
+ $service{$service}->{'unique-sources'} = {
+ 'num-sources-ipv4' => $ipv4,
+ 'num-sources-ipv6' => $ipv6,
+ 'num-sources-ipv6-aggregate' => $aggregated,
+ };
+ }
+}
+
+if ($sort) {
+ my $first = 1;
+ $YAML::SortKeys = 1;
+ foreach my $service (sort keys %service) {
+ foreach my $metric (sort keys %{$service{$service}}) {
+ if ($first) {
+ $first = 0;
+ } else {
+ print "\n";
+ }
+ print YAML::Dump({
+ version => "rssac002v3",
+ service => $service,
+ 'start-period' => $earliest_start_period,
+ metric => $metric,
+ %{ $service{$service}->{$metric} },
+ });
+ }
+ }
+} else {
+ my $first = 1;
+ $YAML::SortKeys = 0;
+ foreach my $service (keys %service) {
+ foreach my $metric (keys %{$service{$service}}) {
+ if ($first) {
+ $first = 0;
+ } else {
+ print "\n";
+ }
+ print YAML::Dump({
+ version => "rssac002v3",
+ service => $service,
+ 'start-period' => $earliest_start_period,
+ metric => $metric,
+ %{ $service{$service}->{$metric} },
+ });
+ }
+ }
+}
+
+sub merge {
+ my ( $doc, $measurements ) = @_;
+
+ foreach my $key (keys %$measurements) {
+ if (ref($doc->{$key}) eq 'HASH') {
+ unless (ref($measurements->{$key}) eq 'HASH') {
+ die "invalid measurement types for key $key: not a hash";
+ }
+ eval {
+ merge($doc->{$key}, $measurements->{$key});
+ };
+ die $@ if ($@);
+ next;
+ }
+ if (defined($doc->{$key})) {
+ if (defined($measurements->{$key}) and $measurements->{$key} ne '') {
+ $doc->{$key} += $measurements->{$key};
+ }
+ } else {
+ $doc->{$key} = $measurements->{$key};
+ }
+ }
+}
diff --git a/plugins/rssm/dnscap-rssm-rssac002.1.in b/plugins/rssm/dnscap-rssm-rssac002.1.in
new file mode 100644
index 0000000..a625d9b
--- /dev/null
+++ b/plugins/rssm/dnscap-rssm-rssac002.1.in
@@ -0,0 +1,98 @@
+.\" 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.
+.\"
+.TH dnscap-rssm-rssac002 1 "dnscap-v@PACKAGE_VERSION@" "dnscap RSSAC002v3 Tool"
+.SH NAME
+dnscap-rssm-rssac002 \- Combine RSSAC002v3 YAML files
+.SH SYNOPSIS
+.B dnscap-rssm-rssac002
+[
+.B \--no-recompile
+.B \--keep-dnscap-rssm
+.B \--sort
+]
+.I files...
+.SH DESCRIPTION
+This Perl script will merge and remove metric specific to this plugin and
+replace others to fill in correct values for the new time period.
+The earliest
+.I start-period
+found will be used for all metrics.
+.LP
+.B NOTE
+no parsing of
+.I start-period
+is performed, it is up to the operator to only give input files related
+to the same 24 hour period.
+.SH OPTIONS
+.TP
+.B \--no-recompile
+Disabled the combining of metrics and the removal of metrics specific to
+this plugin.
+.TP
+.B \--keep-dnscap-rssm
+Do the combining but keep the metrics specific to this plugin.
+.TP
+.B \--sort
+Output will always start with
+.IR version: ,
+.IR service: ,
+.I start-period:
+and
+.IR metric: ,
+rest of the values are not ordered by label.
+This option enabled sorting of them, which is not required by the
+specification but may help in debugging and testing cases.
+.SH SEE ALSO
+.BR dnscap (1)
+.SH AUTHORS
+Jerry Lundström, DNS-OARC
+.LP
+Maintained by DNS-OARC
+.LP
+.RS
+.I https://www.dns-oarc.net/
+.RE
+.LP
+.SH BUGS
+For issues and feature requests please use:
+.LP
+.RS
+\fI@PACKAGE_URL@\fP
+.RE
+.LP
+For question and help please use:
+.LP
+.RS
+\fI@PACKAGE_BUGREPORT@\fP
+.RE
+.LP
diff --git a/plugins/rssm/rssm.c b/plugins/rssm/rssm.c
new file mode 100644
index 0000000..45b0ec9
--- /dev/null
+++ b/plugins/rssm/rssm.c
@@ -0,0 +1,696 @@
+/*
+ * Copyright (c) 2016-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"
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <memory.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <arpa/nameser.h>
+#if HAVE_ARPA_NAMESER_COMPAT_H
+#include <arpa/nameser_compat.h>
+#endif
+
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+
+#include <ldns/ldns.h>
+
+#include "dnscap_common.h"
+
+#include "hashtbl.h"
+
+static logerr_t* logerr;
+static my_bpftimeval open_ts;
+static my_bpftimeval close_ts;
+#define COUNTS_PREFIX_DEFAULT "rssm"
+static char* counts_prefix = 0;
+static char* sources_prefix = 0;
+static char* aggregated_prefix = 0;
+static int dont_fork_on_close = 0;
+static int sources_into_counters = 0;
+static int aggregated_into_counters = 0;
+static char* service_name = 0;
+static int rssac002v3_yaml = 0;
+
+output_t rssm_output;
+
+#define MAX_SIZE_INDEX 4096
+#define MSG_SIZE_SHIFT 4
+#define MAX_TBL_ADDRS 2000000
+#define MAX_TBL_ADDRS2 200000
+#define MAX_RCODE (1 << 12)
+
+typedef struct {
+ hashtbl* tbl;
+ iaddr addrs[MAX_TBL_ADDRS];
+ uint64_t count[MAX_TBL_ADDRS];
+ unsigned int num_addrs;
+} my_hashtbl;
+
+typedef struct {
+ hashtbl* tbl;
+ iaddr addrs[MAX_TBL_ADDRS2];
+ uint64_t count[MAX_TBL_ADDRS2];
+ unsigned int num_addrs;
+} my_hashtbl2;
+
+struct {
+ uint64_t dns_udp_queries_received_ipv4;
+ uint64_t dns_udp_queries_received_ipv6;
+ uint64_t dns_tcp_queries_received_ipv4;
+ uint64_t dns_tcp_queries_received_ipv6;
+ uint64_t dns_udp_responses_sent_ipv4;
+ uint64_t dns_udp_responses_sent_ipv6;
+ uint64_t dns_tcp_responses_sent_ipv4;
+ uint64_t dns_tcp_responses_sent_ipv6;
+ uint64_t udp_query_size[MAX_SIZE_INDEX];
+ uint64_t tcp_query_size[MAX_SIZE_INDEX];
+ uint64_t udp_response_size[MAX_SIZE_INDEX];
+ uint64_t tcp_response_size[MAX_SIZE_INDEX];
+ uint64_t rcodes[MAX_RCODE];
+ my_hashtbl sources;
+ my_hashtbl2 aggregated;
+ uint64_t num_ipv4_sources;
+ uint64_t num_ipv6_sources;
+} counts;
+
+static unsigned int
+iaddr_hash(const void* key)
+{
+ const iaddr* ia = (const iaddr*)key;
+
+ if (AF_INET == ia->af)
+ return ia->u.a4.s_addr >> 8;
+ else if (AF_INET6 == ia->af) {
+ uint16_t* h = (uint16_t*)&ia->u;
+ return h[2] + h[3] + h[4];
+ } else
+ return 0;
+}
+
+static int
+iaddr_cmp(const void* _a, const void* _b)
+{
+ const iaddr *a = (const iaddr*)_a, *b = (const iaddr*)_b;
+
+ if (a->af == b->af) {
+ if (AF_INET == a->af)
+ return memcmp(&a->u.a4.s_addr, &b->u.a4.s_addr, sizeof(a->u.a4.s_addr));
+ if (AF_INET6 == a->af)
+ return memcmp(&a->u.a6.s6_addr, &b->u.a6.s6_addr, sizeof(a->u.a6.s6_addr));
+ return 0;
+ }
+ if (a->af < b->af)
+ return -1;
+ return 1;
+}
+
+ia_str_t ia_str = 0;
+
+void rssm_extension(int ext, void* arg)
+{
+ switch (ext) {
+ case DNSCAP_EXT_IA_STR:
+ ia_str = (ia_str_t)arg;
+ break;
+ }
+}
+
+void rssm_usage()
+{
+ fprintf(stderr,
+ "\nrssm.so options:\n"
+ "\t-? print these instructions and exit\n"
+ "\t-w <name> write basic counters to <name>.<timesec>.<timeusec>\n"
+ "\t-Y use RSSAC002v3 YAML format when writing counters, the\n"
+ "\t file will contain multiple YAML documents, one for each\n"
+ "\t RSSAC002v3 metric\n"
+ "\t Used with; -S adds custom metric \"dnscap-rssm-sources\"\n"
+ "\t and -A adds \"dnscap-rssm-aggregated-sources\"\n"
+ "\t-n <name> the service name to use in RSSAC002v3 YAML\n"
+ "\t-S write source IPs into counters file with the prefix\n"
+ "\t \"source\" or ...\n"
+ "\t-s <name> write source IPs to <name>.<timesec>.<timeusec>\n"
+ "\t-A write aggregated IPv6(/64) sources into counters file\n"
+ "\t with the prefix \"aggregated-source\" or ...\n"
+ "\t-a <name> write aggregated IPv6(/64) sources to\n"
+ "\t <name>.<timesec>.<timeusec>\n"
+ "\t-D don't fork on close\n");
+}
+
+void rssm_getopt(int* argc, char** argv[])
+{
+ int c;
+ while ((c = getopt(*argc, *argv, "?w:Yn:Ss:Aa:D")) != EOF) {
+ switch (c) {
+ case 'w':
+ if (counts_prefix)
+ free(counts_prefix);
+ counts_prefix = strdup(optarg);
+ break;
+ case 'Y':
+ rssac002v3_yaml = 1;
+ break;
+ case 'n':
+ if (service_name)
+ free(service_name);
+ service_name = strdup(optarg);
+ break;
+ case 'S':
+ sources_into_counters = 1;
+ break;
+ case 's':
+ if (sources_prefix)
+ free(sources_prefix);
+ sources_prefix = strdup(optarg);
+ break;
+ case 'A':
+ aggregated_into_counters = 1;
+ break;
+ case 'a':
+ if (aggregated_prefix)
+ free(aggregated_prefix);
+ aggregated_prefix = strdup(optarg);
+ break;
+ case 'D':
+ dont_fork_on_close = 1;
+ break;
+ case '?':
+ rssm_usage();
+ if (!optopt || optopt == '?') {
+ exit(0);
+ }
+ // fallthrough
+ default:
+ exit(1);
+ }
+ }
+ if (sources_into_counters && sources_prefix) {
+ fprintf(stderr, "rssm: -S and -s can not be used at the same time!\n");
+ rssm_usage();
+ exit(1);
+ }
+ if (aggregated_into_counters && aggregated_prefix) {
+ fprintf(stderr, "rssm: -A and -a can not be used at the same time!\n");
+ rssm_usage();
+ exit(1);
+ }
+ if (rssac002v3_yaml && !service_name) {
+ fprintf(stderr, "rssm: service name (-n) needed for RSSAC002v3 YAML (-Y) output!\n");
+ rssm_usage();
+ exit(1);
+ }
+}
+
+int rssm_start(logerr_t* a_logerr)
+{
+ logerr = a_logerr;
+ return 0;
+}
+
+void rssm_stop()
+{
+}
+
+int rssm_open(my_bpftimeval ts)
+{
+ open_ts = ts;
+ if (counts.sources.tbl)
+ hash_destroy(counts.sources.tbl);
+ if (counts.aggregated.tbl)
+ hash_destroy(counts.aggregated.tbl);
+ memset(&counts, 0, sizeof(counts));
+ if (!(counts.sources.tbl = hash_create(65536, iaddr_hash, iaddr_cmp, 0))) {
+ return -1;
+ }
+ if (!(counts.aggregated.tbl = hash_create(4096, iaddr_hash, iaddr_cmp, 0))) {
+ return -1;
+ }
+ return 0;
+}
+
+void rssm_save_counts(const char* sbuf)
+{
+ FILE* fp;
+ int i;
+ char* tbuf = 0;
+ i = asprintf(&tbuf, "%s.%s.%06lu", counts_prefix ? counts_prefix : COUNTS_PREFIX_DEFAULT, sbuf, (u_long)open_ts.tv_usec);
+ if (i < 1 || !tbuf) {
+ logerr("asprintf: out of memory");
+ return;
+ }
+ fprintf(stderr, "rssm: saving counts in %s\n", tbuf);
+ fp = fopen(tbuf, "w");
+ if (!fp) {
+ logerr("%s: %s", sbuf, strerror(errno));
+ free(tbuf);
+ return;
+ }
+ if (rssac002v3_yaml) {
+ char tz[21];
+ struct tm tm;
+
+ gmtime_r((time_t*)&open_ts.tv_sec, &tm);
+ if (!strftime(tz, sizeof(tz), "%Y-%m-%dT%H:%M:%SZ", &tm)) {
+ logerr("rssm: strftime failed");
+ fclose(fp);
+ free(tbuf);
+ return;
+ }
+
+ fprintf(fp, "---\nversion: rssac002v3\nservice: %s\nstart-period: %s\nmetric: traffic-volume\n", service_name, tz);
+ fprintf(fp, "dns-udp-queries-received-ipv4: %" PRIu64 "\n", counts.dns_udp_queries_received_ipv4);
+ fprintf(fp, "dns-udp-queries-received-ipv6: %" PRIu64 "\n", counts.dns_udp_queries_received_ipv6);
+ fprintf(fp, "dns-tcp-queries-received-ipv4: %" PRIu64 "\n", counts.dns_tcp_queries_received_ipv4);
+ fprintf(fp, "dns-tcp-queries-received-ipv6: %" PRIu64 "\n", counts.dns_tcp_queries_received_ipv6);
+ fprintf(fp, "dns-udp-responses-sent-ipv4: %" PRIu64 "\n", counts.dns_udp_responses_sent_ipv4);
+ fprintf(fp, "dns-udp-responses-sent-ipv6: %" PRIu64 "\n", counts.dns_udp_responses_sent_ipv6);
+ fprintf(fp, "dns-tcp-responses-sent-ipv4: %" PRIu64 "\n", counts.dns_tcp_responses_sent_ipv4);
+ fprintf(fp, "dns-tcp-responses-sent-ipv6: %" PRIu64 "\n", counts.dns_tcp_responses_sent_ipv6);
+
+ fprintf(fp, "\n---\nversion: rssac002v3\nservice: %s\nstart-period: %s\nmetric: traffic-sizes\n", service_name, tz);
+ i = 0;
+ for (; i < MAX_SIZE_INDEX; i++) {
+ if (counts.udp_query_size[i]) {
+ break;
+ }
+ }
+ if (i < MAX_SIZE_INDEX) {
+ fprintf(fp, "udp-request-sizes:\n");
+ for (; i < MAX_SIZE_INDEX; i++) {
+ if (counts.udp_query_size[i]) {
+ fprintf(fp, " %d-%d: %" PRIu64 "\n",
+ i << MSG_SIZE_SHIFT,
+ ((i + 1) << MSG_SIZE_SHIFT) - 1,
+ counts.udp_query_size[i]);
+ }
+ }
+ } else {
+ fprintf(fp, "udp-request-sizes: {}\n");
+ }
+ i = 0;
+ for (; i < MAX_SIZE_INDEX; i++) {
+ if (counts.udp_response_size[i]) {
+ break;
+ }
+ }
+ if (i < MAX_SIZE_INDEX) {
+ fprintf(fp, "udp-response-sizes:\n");
+ for (; i < MAX_SIZE_INDEX; i++) {
+ if (counts.udp_response_size[i]) {
+ fprintf(fp, " %d-%d: %" PRIu64 "\n",
+ i << MSG_SIZE_SHIFT,
+ ((i + 1) << MSG_SIZE_SHIFT) - 1,
+ counts.udp_response_size[i]);
+ }
+ }
+ } else {
+ fprintf(fp, "udp-response-sizes: {}\n");
+ }
+ i = 0;
+ for (; i < MAX_SIZE_INDEX; i++) {
+ if (counts.tcp_query_size[i]) {
+ break;
+ }
+ }
+ if (i < MAX_SIZE_INDEX) {
+ fprintf(fp, "tcp-request-sizes:\n");
+ for (; i < MAX_SIZE_INDEX; i++) {
+ if (counts.tcp_query_size[i]) {
+ fprintf(fp, " %d-%d: %" PRIu64 "\n",
+ i << MSG_SIZE_SHIFT,
+ ((i + 1) << MSG_SIZE_SHIFT) - 1,
+ counts.tcp_query_size[i]);
+ }
+ }
+ } else {
+ fprintf(fp, "tcp-request-sizes: {}\n");
+ }
+ i = 0;
+ for (; i < MAX_SIZE_INDEX; i++) {
+ if (counts.tcp_response_size[i]) {
+ break;
+ }
+ }
+ if (i < MAX_SIZE_INDEX) {
+ fprintf(fp, "tcp-response-sizes:\n");
+ for (; i < MAX_SIZE_INDEX; i++) {
+ if (counts.tcp_response_size[i]) {
+ fprintf(fp, " %d-%d: %" PRIu64 "\n",
+ i << MSG_SIZE_SHIFT,
+ ((i + 1) << MSG_SIZE_SHIFT) - 1,
+ counts.tcp_response_size[i]);
+ }
+ }
+ } else {
+ fprintf(fp, "tcp-response-sizes: {}\n");
+ }
+
+ fprintf(fp, "\n---\nversion: rssac002v3\nservice: %s\nstart-period: %s\nmetric: rcode-volume\n", service_name, tz);
+ for (i = 0; i < MAX_RCODE; i++) {
+ if (counts.rcodes[i]) {
+ fprintf(fp, "%d: %" PRIu64 "\n", i, counts.rcodes[i]);
+ }
+ }
+
+ fprintf(fp, "\n---\nversion: rssac002v3\nservice: %s\nstart-period: %s\nmetric: unique-sources\n", service_name, tz);
+ fprintf(fp, "num-sources-ipv4: %" PRIu64 "\n", counts.num_ipv4_sources);
+ fprintf(fp, "num-sources-ipv6: %" PRIu64 "\n", counts.num_ipv6_sources);
+ fprintf(fp, "num-sources-ipv6-aggregate: %u\n", counts.aggregated.num_addrs);
+
+ if (sources_into_counters) {
+ fprintf(fp, "\n---\nversion: rssac002v3\nservice: %s\nstart-period: %s\nmetric: dnscap-rssm-sources\n", service_name, tz);
+ if (counts.sources.num_addrs) {
+ fprintf(fp, "sources:\n");
+ for (i = 0; i < counts.sources.num_addrs; i++) {
+ fprintf(fp, " %s: %" PRIu64 "\n", ia_str(counts.sources.addrs[i]), counts.sources.count[i]);
+ }
+ } else {
+ fprintf(fp, "sources: {}\n");
+ }
+ }
+
+ if (aggregated_into_counters) {
+ fprintf(fp, "\n---\nversion: rssac002v3\nservice: %s\nstart-period: %s\nmetric: dnscap-rssm-aggregated-sources\n", service_name, tz);
+ if (counts.aggregated.num_addrs) {
+ fprintf(fp, "aggregated-sources:\n");
+ for (i = 0; i < counts.aggregated.num_addrs; i++) {
+ fprintf(fp, " %s: %" PRIu64 "\n", ia_str(counts.aggregated.addrs[i]), counts.aggregated.count[i]);
+ }
+ } else {
+ fprintf(fp, "aggregated-sources: {}\n");
+ }
+ }
+ } else {
+ fprintf(fp, "first-packet-time %ld\n", (long)open_ts.tv_sec);
+ fprintf(fp, "last-packet-time %ld\n", (long)close_ts.tv_sec);
+ fprintf(fp, "dns-udp-queries-received-ipv4 %" PRIu64 "\n", counts.dns_udp_queries_received_ipv4);
+ fprintf(fp, "dns-udp-queries-received-ipv6 %" PRIu64 "\n", counts.dns_udp_queries_received_ipv6);
+ fprintf(fp, "dns-tcp-queries-received-ipv4 %" PRIu64 "\n", counts.dns_tcp_queries_received_ipv4);
+ fprintf(fp, "dns-tcp-queries-received-ipv6 %" PRIu64 "\n", counts.dns_tcp_queries_received_ipv6);
+ fprintf(fp, "dns-udp-responses-sent-ipv4 %" PRIu64 "\n", counts.dns_udp_responses_sent_ipv4);
+ fprintf(fp, "dns-udp-responses-sent-ipv6 %" PRIu64 "\n", counts.dns_udp_responses_sent_ipv6);
+ fprintf(fp, "dns-tcp-responses-sent-ipv4 %" PRIu64 "\n", counts.dns_tcp_responses_sent_ipv4);
+ fprintf(fp, "dns-tcp-responses-sent-ipv6 %" PRIu64 "\n", counts.dns_tcp_responses_sent_ipv6);
+ for (i = 0; i < MAX_SIZE_INDEX; i++)
+ if (counts.udp_query_size[i])
+ fprintf(fp, "dns-udp-query-size %d-%d %" PRIu64 "\n",
+ i << MSG_SIZE_SHIFT,
+ ((i + 1) << MSG_SIZE_SHIFT) - 1,
+ counts.udp_query_size[i]);
+ for (i = 0; i < MAX_SIZE_INDEX; i++)
+ if (counts.tcp_query_size[i])
+ fprintf(fp, "dns-tcp-query-size %d-%d %" PRIu64 "\n",
+ i << MSG_SIZE_SHIFT,
+ ((i + 1) << MSG_SIZE_SHIFT) - 1,
+ counts.tcp_query_size[i]);
+ for (i = 0; i < MAX_SIZE_INDEX; i++)
+ if (counts.udp_response_size[i])
+ fprintf(fp, "dns-udp-response-size %d-%d %" PRIu64 "\n",
+ i << MSG_SIZE_SHIFT,
+ ((i + 1) << MSG_SIZE_SHIFT) - 1,
+ counts.udp_response_size[i]);
+ for (i = 0; i < MAX_SIZE_INDEX; i++)
+ if (counts.tcp_response_size[i])
+ fprintf(fp, "dns-tcp-response-size %d-%d %" PRIu64 "\n",
+ i << MSG_SIZE_SHIFT,
+ ((i + 1) << MSG_SIZE_SHIFT) - 1,
+ counts.tcp_response_size[i]);
+ for (i = 0; i < MAX_RCODE; i++)
+ if (counts.rcodes[i])
+ fprintf(fp, "dns-rcode %d %" PRIu64 "\n",
+ i, counts.rcodes[i]);
+ fprintf(fp, "num-sources %u\n", counts.sources.num_addrs);
+ if (sources_into_counters) {
+ for (i = 0; i < counts.sources.num_addrs; i++) {
+ fprintf(fp, "source %s %" PRIu64 "\n", ia_str(counts.sources.addrs[i]), counts.sources.count[i]);
+ }
+ }
+ if (aggregated_into_counters) {
+ for (i = 0; i < counts.aggregated.num_addrs; i++) {
+ fprintf(fp, "aggregated-source %s %" PRIu64 "\n", ia_str(counts.aggregated.addrs[i]), counts.aggregated.count[i]);
+ }
+ }
+ }
+ fclose(fp);
+ fprintf(stderr, "rssm: done\n");
+ free(tbuf);
+}
+
+void rssm_save_sources(const char* sbuf)
+{
+ FILE* fp;
+ char* tbuf = 0;
+ int i;
+ i = asprintf(&tbuf, "%s.%s.%06lu", sources_prefix, sbuf, (u_long)open_ts.tv_usec);
+ if (i < 1 || !tbuf) {
+ logerr("asprintf: out of memory");
+ return;
+ }
+ fprintf(stderr, "rssm: saving %u sources in %s\n", counts.sources.num_addrs, tbuf);
+ fp = fopen(tbuf, "w");
+ if (!fp) {
+ logerr("%s: %s", tbuf, strerror(errno));
+ free(tbuf);
+ return;
+ }
+ for (i = 0; i < counts.sources.num_addrs; i++) {
+ fprintf(fp, "%s %" PRIu64 "\n", ia_str(counts.sources.addrs[i]), counts.sources.count[i]);
+ }
+ fclose(fp);
+ fprintf(stderr, "rssm: done\n");
+ free(tbuf);
+}
+
+void rssm_save_aggregated(const char* sbuf)
+{
+ FILE* fp;
+ char* tbuf = 0;
+ int i;
+ i = asprintf(&tbuf, "%s.%s.%06lu", aggregated_prefix, sbuf, (u_long)open_ts.tv_usec);
+ if (i < 1 || !tbuf) {
+ logerr("asprintf: out of memory");
+ return;
+ }
+ fprintf(stderr, "rssm: saving %u aggregated in %s\n", counts.aggregated.num_addrs, tbuf);
+ fp = fopen(tbuf, "w");
+ if (!fp) {
+ logerr("%s: %s", tbuf, strerror(errno));
+ free(tbuf);
+ return;
+ }
+ for (i = 0; i < counts.aggregated.num_addrs; i++) {
+ fprintf(fp, "%s %" PRIu64 "\n", ia_str(counts.aggregated.addrs[i]), counts.aggregated.count[i]);
+ }
+ fclose(fp);
+ fprintf(stderr, "rssm: done\n");
+ free(tbuf);
+}
+
+/*
+ * Fork a separate process so that we don't block the main dnscap. Use double-fork
+ * to avoid zombies for the main dnscap process.
+ */
+int rssm_close(my_bpftimeval ts)
+{
+ char sbuf[265];
+ pid_t pid;
+ struct tm tm;
+
+ if (dont_fork_on_close) {
+ struct tm tm;
+ gmtime_r((time_t*)&open_ts.tv_sec, &tm);
+ strftime(sbuf, sizeof(sbuf), "%Y%m%d.%H%M%S", &tm);
+ close_ts = ts;
+ rssm_save_counts(sbuf);
+ if (sources_prefix)
+ rssm_save_sources(sbuf);
+ if (aggregated_prefix)
+ rssm_save_aggregated(sbuf);
+ return 0;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ logerr("rssm.so: fork: %s", strerror(errno));
+ return 1;
+ } else if (pid) {
+ /* parent */
+ waitpid(pid, NULL, 0);
+ return 0;
+ }
+ /* 1st gen child continues */
+ pid = fork();
+ if (pid < 0) {
+ logerr("rssm.so: fork: %s", strerror(errno));
+ return 1;
+ } else if (pid) {
+ /* 1st gen child exits */
+ exit(0);
+ }
+ /* grandchild (2nd gen) continues */
+ gmtime_r((time_t*)&open_ts.tv_sec, &tm);
+ strftime(sbuf, sizeof(sbuf), "%Y%m%d.%H%M%S", &tm);
+ close_ts = ts;
+ rssm_save_counts(sbuf);
+ if (sources_prefix)
+ rssm_save_sources(sbuf);
+ if (aggregated_prefix)
+ rssm_save_aggregated(sbuf);
+ exit(0);
+}
+
+static void
+find_or_add(iaddr ia)
+{
+ uint64_t* c = hash_find(&ia, counts.sources.tbl);
+ if (c) {
+ (*c)++;
+ } else {
+ if (counts.sources.num_addrs == MAX_TBL_ADDRS)
+ return;
+ counts.sources.addrs[counts.sources.num_addrs] = ia;
+ if (hash_add(&counts.sources.addrs[counts.sources.num_addrs], &counts.sources.count[counts.sources.num_addrs], counts.sources.tbl)) {
+ logerr("rssm.so: unable to add address to hash");
+ return;
+ }
+ counts.sources.count[counts.sources.num_addrs]++;
+ counts.sources.num_addrs++;
+ if (ia.af == AF_INET) {
+ counts.num_ipv4_sources++;
+ } else {
+ counts.num_ipv6_sources++;
+ }
+ }
+
+ if (ia.af == AF_INET6) {
+ iaddr v6agg = ia;
+
+ memset(((uint8_t*)&v6agg.u.a6) + 8, 0, 8);
+ c = hash_find(&v6agg, counts.aggregated.tbl);
+ if (c) {
+ (*c)++;
+ } else {
+ if (counts.aggregated.num_addrs == MAX_TBL_ADDRS2)
+ return;
+ counts.aggregated.addrs[counts.aggregated.num_addrs] = v6agg;
+ if (hash_add(&counts.aggregated.addrs[counts.aggregated.num_addrs], &counts.aggregated.count[counts.aggregated.num_addrs], counts.aggregated.tbl)) {
+ logerr("rssm.so: unable to add aggregated address to hash");
+ return;
+ }
+ counts.aggregated.count[counts.aggregated.num_addrs]++;
+ counts.aggregated.num_addrs++;
+ }
+ }
+}
+
+void rssm_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, const unsigned olen,
+ const u_char* payload, const unsigned payloadlen)
+{
+ unsigned dnslen;
+ ldns_pkt* pkt = 0;
+
+ if (!(flags & DNSCAP_OUTPUT_ISDNS))
+ return;
+
+ if (ldns_wire2pkt(&pkt, payload, payloadlen) != LDNS_STATUS_OK) {
+ return;
+ }
+
+ dnslen = payloadlen >> MSG_SIZE_SHIFT;
+ if (dnslen >= MAX_SIZE_INDEX)
+ dnslen = MAX_SIZE_INDEX - 1;
+
+ if (!ldns_pkt_qr(pkt)) {
+ find_or_add(from);
+ if (IPPROTO_UDP == proto) {
+ counts.udp_query_size[dnslen]++;
+ } else if (IPPROTO_TCP == proto) {
+ counts.tcp_query_size[dnslen]++;
+ }
+ if (AF_INET == from.af) {
+ if (IPPROTO_UDP == proto) {
+ counts.dns_udp_queries_received_ipv4++;
+ } else if (IPPROTO_TCP == proto) {
+ counts.dns_tcp_queries_received_ipv4++;
+ }
+ } else if (AF_INET6 == from.af) {
+ if (IPPROTO_UDP == proto) {
+ counts.dns_udp_queries_received_ipv6++;
+ } else if (IPPROTO_TCP == proto) {
+ counts.dns_tcp_queries_received_ipv6++;
+ }
+ }
+ } else {
+ uint16_t rcode = ldns_pkt_get_rcode(pkt);
+ if (IPPROTO_UDP == proto) {
+ counts.udp_response_size[dnslen]++;
+ } else if (IPPROTO_TCP == proto) {
+ counts.tcp_response_size[dnslen]++;
+ }
+ if (AF_INET == from.af) {
+ if (IPPROTO_UDP == proto) {
+ counts.dns_udp_responses_sent_ipv4++;
+ } else if (IPPROTO_TCP == proto) {
+ counts.dns_tcp_responses_sent_ipv4++;
+ }
+ } else if (AF_INET6 == from.af) {
+ if (IPPROTO_UDP == proto) {
+ counts.dns_udp_responses_sent_ipv6++;
+ } else if (IPPROTO_TCP == proto) {
+ counts.dns_tcp_responses_sent_ipv6++;
+ }
+ }
+ if (ldns_pkt_arcount(pkt)) {
+ rcode |= ((uint16_t)ldns_pkt_edns_extended_rcode(pkt) << 4);
+ }
+ counts.rcodes[rcode]++;
+ }
+
+ ldns_pkt_free(pkt);
+}
diff --git a/plugins/rssm/test1.gold b/plugins/rssm/test1.gold
new file mode 100644
index 0000000..86957d0
--- /dev/null
+++ b/plugins/rssm/test1.gold
@@ -0,0 +1,58 @@
+---
+version: rssac002v3
+service: test1
+start-period: 2016-10-20T15:23:01Z
+metric: traffic-volume
+dns-udp-queries-received-ipv4: 41
+dns-udp-queries-received-ipv6: 0
+dns-tcp-queries-received-ipv4: 0
+dns-tcp-queries-received-ipv6: 0
+dns-udp-responses-sent-ipv4: 41
+dns-udp-responses-sent-ipv6: 0
+dns-tcp-responses-sent-ipv4: 0
+dns-tcp-responses-sent-ipv6: 0
+
+---
+version: rssac002v3
+service: test1
+start-period: 2016-10-20T15:23:01Z
+metric: traffic-sizes
+udp-request-sizes:
+ 16-31: 24
+ 32-47: 17
+udp-response-sizes:
+ 176-191: 24
+ 256-271: 17
+tcp-request-sizes: {}
+tcp-response-sizes: {}
+
+---
+version: rssac002v3
+service: test1
+start-period: 2016-10-20T15:23:01Z
+metric: rcode-volume
+0: 41
+
+---
+version: rssac002v3
+service: test1
+start-period: 2016-10-20T15:23:01Z
+metric: unique-sources
+num-sources-ipv4: 1
+num-sources-ipv6: 0
+num-sources-ipv6-aggregate: 0
+
+---
+version: rssac002v3
+service: test1
+start-period: 2016-10-20T15:23:01Z
+metric: dnscap-rssm-sources
+sources:
+ 172.17.0.10: 41
+
+---
+version: rssac002v3
+service: test1
+start-period: 2016-10-20T15:23:01Z
+metric: dnscap-rssm-aggregated-sources
+aggregated-sources: {}
diff --git a/plugins/rssm/test1.sh b/plugins/rssm/test1.sh
new file mode 100755
index 0000000..d43b947
--- /dev/null
+++ b/plugins/rssm/test1.sh
@@ -0,0 +1,11 @@
+#!/bin/sh -xe
+
+plugin=`find . -name 'rssm.so' | head -n 1`
+if [ -z "$plugin" ]; then
+ echo "Unable to find the RSSM plugin"
+ exit 1
+fi
+
+../../src/dnscap -N -T -r "$srcdir/../../src/test/dns.pcap" -P "$plugin" -w test1 -Y -n test1 -A -S -D
+
+diff test1.20161020.152301.075993 "$srcdir/test1.gold"
diff --git a/plugins/rssm/test2.gold b/plugins/rssm/test2.gold
new file mode 100644
index 0000000..85625c0
--- /dev/null
+++ b/plugins/rssm/test2.gold
@@ -0,0 +1,43 @@
+---
+0: 123
+metric: rcode-volume
+service: test1
+start-period: 2016-10-20T15:23:01Z
+version: rssac002v3
+
+---
+metric: traffic-sizes
+service: test1
+start-period: 2016-10-20T15:23:01Z
+tcp-request-sizes: {}
+tcp-response-sizes: {}
+udp-request-sizes:
+ 16-31: 72
+ 32-47: 51
+udp-response-sizes:
+ 176-191: 72
+ 256-271: 51
+version: rssac002v3
+
+---
+dns-tcp-queries-received-ipv4: 0
+dns-tcp-queries-received-ipv6: 0
+dns-tcp-responses-sent-ipv4: 0
+dns-tcp-responses-sent-ipv6: 0
+dns-udp-queries-received-ipv4: 123
+dns-udp-queries-received-ipv6: 0
+dns-udp-responses-sent-ipv4: 123
+dns-udp-responses-sent-ipv6: 0
+metric: traffic-volume
+service: test1
+start-period: 2016-10-20T15:23:01Z
+version: rssac002v3
+
+---
+metric: unique-sources
+num-sources-ipv4: 1
+num-sources-ipv6: 0
+num-sources-ipv6-aggregate: 0
+service: test1
+start-period: 2016-10-20T15:23:01Z
+version: rssac002v3
diff --git a/plugins/rssm/test2.sh b/plugins/rssm/test2.sh
new file mode 100755
index 0000000..11f44af
--- /dev/null
+++ b/plugins/rssm/test2.sh
@@ -0,0 +1,5 @@
+#!/bin/sh -xe
+
+"$srcdir"/dnscap-rssm-rssac002 --sort "$srcdir/test1.gold" "$srcdir/test1.gold" "$srcdir/test1.gold" > test2.out
+
+diff test2.out "$srcdir/test2.gold"
diff --git a/plugins/rssm/test3.gold b/plugins/rssm/test3.gold
new file mode 100644
index 0000000..237af89
--- /dev/null
+++ b/plugins/rssm/test3.gold
@@ -0,0 +1,57 @@
+---
+version: rssac002v3
+service: test3
+start-period: 2018-11-27T15:52:00Z
+metric: traffic-volume
+dns-udp-queries-received-ipv4: 0
+dns-udp-queries-received-ipv6: 1
+dns-tcp-queries-received-ipv4: 0
+dns-tcp-queries-received-ipv6: 0
+dns-udp-responses-sent-ipv4: 0
+dns-udp-responses-sent-ipv6: 1
+dns-tcp-responses-sent-ipv4: 0
+dns-tcp-responses-sent-ipv6: 0
+
+---
+version: rssac002v3
+service: test3
+start-period: 2018-11-27T15:52:00Z
+metric: traffic-sizes
+udp-request-sizes:
+ 32-47: 1
+udp-response-sizes:
+ 48-63: 1
+tcp-request-sizes: {}
+tcp-response-sizes: {}
+
+---
+version: rssac002v3
+service: test3
+start-period: 2018-11-27T15:52:00Z
+metric: rcode-volume
+0: 1
+
+---
+version: rssac002v3
+service: test3
+start-period: 2018-11-27T15:52:00Z
+metric: unique-sources
+num-sources-ipv4: 0
+num-sources-ipv6: 1
+num-sources-ipv6-aggregate: 1
+
+---
+version: rssac002v3
+service: test3
+start-period: 2018-11-27T15:52:00Z
+metric: dnscap-rssm-sources
+sources:
+ 2a01:3f0:0:57::245: 1
+
+---
+version: rssac002v3
+service: test3
+start-period: 2018-11-27T15:52:00Z
+metric: dnscap-rssm-aggregated-sources
+aggregated-sources:
+ 2a01:3f0:0:57::: 1
diff --git a/plugins/rssm/test3.sh b/plugins/rssm/test3.sh
new file mode 100755
index 0000000..60b2e8a
--- /dev/null
+++ b/plugins/rssm/test3.sh
@@ -0,0 +1,11 @@
+#!/bin/sh -xe
+
+plugin=`find . -name 'rssm.so' | head -n 1`
+if [ -z "$plugin" ]; then
+ echo "Unable to find the RSSM plugin"
+ exit 1
+fi
+
+../../src/dnscap -N -T -r "$srcdir/../../src/test/dns6.pcap" -P "$plugin" -w test3 -Y -n test3 -A -S -D
+
+diff test3.20181127.155200.414188 "$srcdir/test3.gold"
diff --git a/plugins/rssm/test4.sh b/plugins/rssm/test4.sh
new file mode 100755
index 0000000..3c28711
--- /dev/null
+++ b/plugins/rssm/test4.sh
@@ -0,0 +1,14 @@
+#!/bin/sh -xe
+
+plugin=`find . -name 'rssm.so' | head -n 1`
+if [ -z "$plugin" ]; then
+ echo "Unable to find the RSSM plugin"
+ exit 1
+fi
+
+../../src/dnscap -r "$srcdir/../../src/test/dns.pcap" -P "$plugin" -?
+! ../../src/dnscap -r "$srcdir/../../src/test/dns.pcap" -P "$plugin" -X
+! ../../src/dnscap -r "$srcdir/../../src/test/dns.pcap" -P "$plugin" -s s -s s -S
+! ../../src/dnscap -r "$srcdir/../../src/test/dns.pcap" -P "$plugin" -a a -a a -A
+! ../../src/dnscap -r "$srcdir/../../src/test/dns.pcap" -P "$plugin" -Y
+../../src/dnscap -r "$srcdir/../../src/test/dns.pcap" -P "$plugin" -D -w test4 -w test4 -n n -n n -s test4.src -a test4.agg
diff --git a/plugins/rssm/test5.gold b/plugins/rssm/test5.gold
new file mode 100644
index 0000000..acaa7cd
--- /dev/null
+++ b/plugins/rssm/test5.gold
@@ -0,0 +1,58 @@
+---
+version: rssac002v3
+service: test5
+start-period: 2018-01-10T11:22:41Z
+metric: traffic-volume
+dns-udp-queries-received-ipv4: 0
+dns-udp-queries-received-ipv6: 0
+dns-tcp-queries-received-ipv4: 41
+dns-tcp-queries-received-ipv6: 0
+dns-udp-responses-sent-ipv4: 0
+dns-udp-responses-sent-ipv6: 0
+dns-tcp-responses-sent-ipv4: 41
+dns-tcp-responses-sent-ipv6: 0
+
+---
+version: rssac002v3
+service: test5
+start-period: 2018-01-10T11:22:41Z
+metric: traffic-sizes
+udp-request-sizes: {}
+udp-response-sizes: {}
+tcp-request-sizes:
+ 16-31: 24
+ 32-47: 17
+tcp-response-sizes:
+ 32-47: 24
+ 128-143: 17
+
+---
+version: rssac002v3
+service: test5
+start-period: 2018-01-10T11:22:41Z
+metric: rcode-volume
+0: 41
+
+---
+version: rssac002v3
+service: test5
+start-period: 2018-01-10T11:22:41Z
+metric: unique-sources
+num-sources-ipv4: 1
+num-sources-ipv6: 0
+num-sources-ipv6-aggregate: 0
+
+---
+version: rssac002v3
+service: test5
+start-period: 2018-01-10T11:22:41Z
+metric: dnscap-rssm-sources
+sources:
+ 172.17.0.8: 41
+
+---
+version: rssac002v3
+service: test5
+start-period: 2018-01-10T11:22:41Z
+metric: dnscap-rssm-aggregated-sources
+aggregated-sources: {}
diff --git a/plugins/rssm/test5.sh b/plugins/rssm/test5.sh
new file mode 100755
index 0000000..4b93df5
--- /dev/null
+++ b/plugins/rssm/test5.sh
@@ -0,0 +1,11 @@
+#!/bin/sh -xe
+
+plugin=`find . -name 'rssm.so' | head -n 1`
+if [ -z "$plugin" ]; then
+ echo "Unable to find the RSSM plugin"
+ exit 1
+fi
+
+../../src/dnscap -N -T -r "$srcdir/../../src/test/dnso1tcp.pcap" -P "$plugin" -w test5 -Y -n test5 -A -S -D
+
+diff test5.20180110.112241.543825 "$srcdir/test5.gold"