diff options
Diffstat (limited to 'bin/tests/system/statschannel')
31 files changed, 1718 insertions, 0 deletions
diff --git a/bin/tests/system/statschannel/clean.sh b/bin/tests/system/statschannel/clean.sh new file mode 100644 index 0000000..5ad2a2c --- /dev/null +++ b/bin/tests/system/statschannel/clean.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +rm -f compressed.headers regular.headers compressed.out regular.out +rm -f dig.out* +rm -f ns*/managed-keys.bind* +rm -f ns*/named.conf +rm -f ns*/named.lock +rm -f ns*/named.memstats +rm -f ns*/named.run* +rm -f ns*/named.stats +rm -f ns*/signzone.out.* +rm -f ns2/*.db.signed* ns2/dsset-*. ns2/*.jbk +rm -f ns2/Kdnssec* ns2/dnssec.*.id +rm -f ns2/Kmanykeys* ns2/manykeys.*.id +rm -f ns2/dnssec.db.signed* ns2/dsset-dnssec. +rm -f ns3/*.db +rm -f traffic traffic.out.* traffic.json.* traffic.xml.* +rm -f xml.*mem json.*mem +rm -f xml.*stats json.*stats +rm -f zones zones.out.* zones.json.* zones.xml.* zones.expect.* +rm -rf ./__pycache__ diff --git a/bin/tests/system/statschannel/conftest.py b/bin/tests/system/statschannel/conftest.py new file mode 100644 index 0000000..363dd7a --- /dev/null +++ b/bin/tests/system/statschannel/conftest.py @@ -0,0 +1,25 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +import os +import pytest + + +@pytest.fixture +def statsport(request): + # pylint: disable=unused-argument + env_port = os.getenv("EXTRAPORT1") + if env_port is None: + env_port = 5301 + else: + env_port = int(env_port) + + return env_port diff --git a/bin/tests/system/statschannel/fetch.pl b/bin/tests/system/statschannel/fetch.pl new file mode 100644 index 0000000..b09ed54 --- /dev/null +++ b/bin/tests/system/statschannel/fetch.pl @@ -0,0 +1,43 @@ +#!/usr/bin/perl + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# fetch.pl: +# Simple script to fetch HTTP content from the statistics channel +# of a BIND server. Fetches the full XML stats from 10.53.0.2 port +# 8853 by default; these can be overridden by command line arguments. + +use File::Fetch; +use Getopt::Std; + +sub usage { + print ("Usage: fetch.pl [-s address] [-p port] [path]\n"); + exit 1; +} + +my %options={}; +getopts("s:p:", \%options); + +my $addr = "10.53.0.2"; +$addr = $options{s} if defined $options{s}; + +my $path = 'xml/v3'; +if (@ARGV >= 1) { + $path = shift @ARGV; +} + +my $port = 8853; +$port = $options{p} if defined $options{p}; + +my $ff = File::Fetch->new(uri => "http://$addr:$port/$path"); +my $file = $ff->fetch() or die $ff->error; +print ("$file\n"); diff --git a/bin/tests/system/statschannel/generic.py b/bin/tests/system/statschannel/generic.py new file mode 100644 index 0000000..5ff09e2 --- /dev/null +++ b/bin/tests/system/statschannel/generic.py @@ -0,0 +1,106 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +from datetime import datetime, timedelta +import os + + +# ISO datetime format without msec +fmt = "%Y-%m-%dT%H:%M:%SZ" + +# The constants were taken from BIND 9 source code (lib/dns/zone.c) +max_refresh = timedelta(seconds=2419200) # 4 weeks +max_expires = timedelta(seconds=14515200) # 24 weeks +now = datetime.utcnow().replace(microsecond=0) +dayzero = datetime.utcfromtimestamp(0).replace(microsecond=0) + + +# Generic helper functions +def check_expires(expires, min_time, max_time): + assert expires >= min_time + assert expires <= max_time + + +def check_refresh(refresh, min_time, max_time): + assert refresh >= min_time + assert refresh <= max_time + + +def check_loaded(loaded, expected): + # Sanity check the zone timers values + assert loaded == expected + assert loaded < now + + +def check_zone_timers(loaded, expires, refresh, loaded_exp): + # Sanity checks the zone timers values + if expires is not None: + check_expires(expires, now, now + max_expires) + if refresh is not None: + check_refresh(refresh, now, now + max_refresh) + check_loaded(loaded, loaded_exp) + + +# +# The output is gibberish, but at least make sure it does not crash. +# +def check_manykeys(name, zone=None): + # pylint: disable=unused-argument + assert name == "manykeys" + + +def zone_mtime(zonedir, name): + try: + si = os.stat(os.path.join(zonedir, "{}.db".format(name))) + except FileNotFoundError: + return dayzero + + mtime = datetime.utcfromtimestamp(si.st_mtime).replace(microsecond=0) + + return mtime + + +def test_zone_timers_primary(fetch_zones, load_timers, **kwargs): + statsip = kwargs["statsip"] + statsport = kwargs["statsport"] + zonedir = kwargs["zonedir"] + + zones = fetch_zones(statsip, statsport) + + for zone in zones: + (name, loaded, expires, refresh) = load_timers(zone, True) + mtime = zone_mtime(zonedir, name) + check_zone_timers(loaded, expires, refresh, mtime) + + +def test_zone_timers_secondary(fetch_zones, load_timers, **kwargs): + statsip = kwargs["statsip"] + statsport = kwargs["statsport"] + zonedir = kwargs["zonedir"] + + zones = fetch_zones(statsip, statsport) + + for zone in zones: + (name, loaded, expires, refresh) = load_timers(zone, False) + mtime = zone_mtime(zonedir, name) + check_zone_timers(loaded, expires, refresh, mtime) + + +def test_zone_with_many_keys(fetch_zones, load_zone, **kwargs): + statsip = kwargs["statsip"] + statsport = kwargs["statsport"] + + zones = fetch_zones(statsip, statsport) + + for zone in zones: + name = load_zone(zone) + if name == "manykeys": + check_manykeys(name) diff --git a/bin/tests/system/statschannel/generic_dnspython.py b/bin/tests/system/statschannel/generic_dnspython.py new file mode 100644 index 0000000..34a0398 --- /dev/null +++ b/bin/tests/system/statschannel/generic_dnspython.py @@ -0,0 +1,128 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +from collections import defaultdict + +import dns.message +import dns.query +import dns.rcode + + +TIMEOUT = 10 + + +def create_msg(qname, qtype): + msg = dns.message.make_query( + qname, qtype, want_dnssec=True, use_edns=0, payload=4096 + ) + + return msg + + +def udp_query(ip, port, msg): + ans = dns.query.udp(msg, ip, TIMEOUT, port=port) + assert ans.rcode() == dns.rcode.NOERROR + + return ans + + +def tcp_query(ip, port, msg): + ans = dns.query.tcp(msg, ip, TIMEOUT, port=port) + assert ans.rcode() == dns.rcode.NOERROR + + return ans + + +def create_expected(data): + expected = { + "dns-tcp-requests-sizes-received-ipv4": defaultdict(int), + "dns-tcp-responses-sizes-sent-ipv4": defaultdict(int), + "dns-tcp-requests-sizes-received-ipv6": defaultdict(int), + "dns-tcp-responses-sizes-sent-ipv6": defaultdict(int), + "dns-udp-requests-sizes-received-ipv4": defaultdict(int), + "dns-udp-requests-sizes-received-ipv6": defaultdict(int), + "dns-udp-responses-sizes-sent-ipv4": defaultdict(int), + "dns-udp-responses-sizes-sent-ipv6": defaultdict(int), + } + + for k, v in data.items(): + for kk, vv in v.items(): + expected[k][kk] += vv + + return expected + + +def update_expected(expected, key, msg): + msg_len = len(msg.to_wire()) + bucket_num = (msg_len // 16) * 16 + bucket = "{}-{}".format(bucket_num, bucket_num + 15) + + expected[key][bucket] += 1 + + +def check_traffic(data, expected): + def ordered(obj): + if isinstance(obj, dict): + return sorted((k, ordered(v)) for k, v in obj.items()) + if isinstance(obj, list): + return sorted(ordered(x) for x in obj) + return obj + + ordered_data = ordered(data) + ordered_expected = ordered(expected) + + assert len(ordered_data) == 8 + assert len(ordered_expected) == 8 + assert len(data) == len(ordered_data) + assert len(expected) == len(ordered_expected) + + assert ordered_data == ordered_expected + + +def test_traffic(fetch_traffic, **kwargs): + statsip = kwargs["statsip"] + statsport = kwargs["statsport"] + port = kwargs["port"] + + data = fetch_traffic(statsip, statsport) + exp = create_expected(data) + + msg = create_msg("short.example.", "TXT") + update_expected(exp, "dns-udp-requests-sizes-received-ipv4", msg) + ans = udp_query(statsip, port, msg) + update_expected(exp, "dns-udp-responses-sizes-sent-ipv4", ans) + data = fetch_traffic(statsip, statsport) + + check_traffic(data, exp) + + msg = create_msg("long.example.", "TXT") + update_expected(exp, "dns-udp-requests-sizes-received-ipv4", msg) + ans = udp_query(statsip, port, msg) + update_expected(exp, "dns-udp-responses-sizes-sent-ipv4", ans) + data = fetch_traffic(statsip, statsport) + + check_traffic(data, exp) + + msg = create_msg("short.example.", "TXT") + update_expected(exp, "dns-tcp-requests-sizes-received-ipv4", msg) + ans = tcp_query(statsip, port, msg) + update_expected(exp, "dns-tcp-responses-sizes-sent-ipv4", ans) + data = fetch_traffic(statsip, statsport) + + check_traffic(data, exp) + + msg = create_msg("long.example.", "TXT") + update_expected(exp, "dns-tcp-requests-sizes-received-ipv4", msg) + ans = tcp_query(statsip, port, msg) + update_expected(exp, "dns-tcp-responses-sizes-sent-ipv4", ans) + data = fetch_traffic(statsip, statsport) + + check_traffic(data, exp) diff --git a/bin/tests/system/statschannel/mem-xml.pl b/bin/tests/system/statschannel/mem-xml.pl new file mode 100644 index 0000000..4483aae --- /dev/null +++ b/bin/tests/system/statschannel/mem-xml.pl @@ -0,0 +1,21 @@ +#!/usr/bin/perl + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# server-xml.pl: +# Parses the XML version of the server stats into a normalized format. + +use XML::Simple; +use Data::Dumper; + +my $ref = XMLin("xml.mem"); +print Dumper($ref); diff --git a/bin/tests/system/statschannel/ns1/example.db b/bin/tests/system/statschannel/ns1/example.db new file mode 100644 index 0000000..5c2635e --- /dev/null +++ b/bin/tests/system/statschannel/ns1/example.db @@ -0,0 +1,49 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$ORIGIN . +$TTL 300 ; 5 minutes +example IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) +example. NS ns2.example. +ns2.example. A 10.53.0.2 + +$ORIGIN example. +a A 10.0.0.1 + MX 10 mail.example. +short TXT "short text" +long TXT ( + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + ) + +mail A 10.0.0.2 diff --git a/bin/tests/system/statschannel/ns1/named.conf.in b/bin/tests/system/statschannel/ns1/named.conf.in new file mode 100644 index 0000000..04ead33 --- /dev/null +++ b/bin/tests/system/statschannel/ns1/named.conf.in @@ -0,0 +1,43 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +options { + query-source address 10.53.0.1; + notify-source 10.53.0.1; + transfer-source 10.53.0.1; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.1; }; + listen-on-v6 { none; }; + recursion no; + notify explicit; + minimal-responses no; + version none; // make statistics independent of the version number +}; + +statistics-channels { inet 10.53.0.1 port @EXTRAPORT1@ allow { localhost; }; }; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-sha256; +}; + +controls { + inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "example" { + type primary; + file "example.db"; + allow-transfer { any; }; +}; diff --git a/bin/tests/system/statschannel/ns2/dnssec.db.in b/bin/tests/system/statschannel/ns2/dnssec.db.in new file mode 100644 index 0000000..90ae166 --- /dev/null +++ b/bin/tests/system/statschannel/ns2/dnssec.db.in @@ -0,0 +1,28 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$ORIGIN . +$TTL 300 + +dnssec. IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) +dnssec. NS ns2.dnssec. +ns2.dnssec. A 10.53.0.2 + +$ORIGIN dnssec. +a A 10.0.0.1 + MX 10 mail.dnssec. +mail A 10.0.0.2 diff --git a/bin/tests/system/statschannel/ns2/example.db b/bin/tests/system/statschannel/ns2/example.db new file mode 100644 index 0000000..5c2635e --- /dev/null +++ b/bin/tests/system/statschannel/ns2/example.db @@ -0,0 +1,49 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$ORIGIN . +$TTL 300 ; 5 minutes +example IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) +example. NS ns2.example. +ns2.example. A 10.53.0.2 + +$ORIGIN example. +a A 10.0.0.1 + MX 10 mail.example. +short TXT "short text" +long TXT ( + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + ) + +mail A 10.0.0.2 diff --git a/bin/tests/system/statschannel/ns2/manykeys.db.in b/bin/tests/system/statschannel/ns2/manykeys.db.in new file mode 100644 index 0000000..3281a39 --- /dev/null +++ b/bin/tests/system/statschannel/ns2/manykeys.db.in @@ -0,0 +1,28 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$ORIGIN . +$TTL 300 + +manykeys. IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) +manykeys. NS ns2.manykeys. +ns2.manykeys. A 10.53.0.2 + +$ORIGIN manykeys. +a A 10.0.0.1 + MX 10 mail.manykeys. +mail A 10.0.0.2 diff --git a/bin/tests/system/statschannel/ns2/named.conf.in b/bin/tests/system/statschannel/ns2/named.conf.in new file mode 100644 index 0000000..fd25fff --- /dev/null +++ b/bin/tests/system/statschannel/ns2/named.conf.in @@ -0,0 +1,72 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +options { + query-source address 10.53.0.2; + notify-source 10.53.0.2; + transfer-source 10.53.0.2; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.2; }; + listen-on-v6 { none; }; + recursion no; + notify no; + minimal-responses no; + version none; // make statistics independent of the version number +}; + +statistics-channels { inet 10.53.0.2 port @EXTRAPORT1@ allow { localhost; }; }; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-sha256; +}; + +controls { + inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +dnssec-policy "manykeys" { + keys { + ksk lifetime unlimited algorithm 8; + zsk lifetime unlimited algorithm 8; + ksk lifetime unlimited algorithm 13; + zsk lifetime unlimited algorithm 13; + ksk lifetime unlimited algorithm 14; + zsk lifetime unlimited algorithm 14; + }; +}; + +zone "example" { + type primary; + file "example.db"; + allow-transfer { any; }; +}; + +zone "dnssec" { + type primary; + file "dnssec.db.signed"; + auto-dnssec maintain; + allow-update { any; }; + zone-statistics full; + dnssec-dnskey-kskonly yes; + update-check-ksk yes; +}; + +zone "manykeys" { + type primary; + file "manykeys.db.signed"; + allow-update { any; }; + zone-statistics full; + dnssec-policy "manykeys"; +}; diff --git a/bin/tests/system/statschannel/ns2/named2.conf.in b/bin/tests/system/statschannel/ns2/named2.conf.in new file mode 100644 index 0000000..d45f9f5 --- /dev/null +++ b/bin/tests/system/statschannel/ns2/named2.conf.in @@ -0,0 +1,68 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +options { + query-source address 10.53.0.2; + notify-source 10.53.0.2; + transfer-source 10.53.0.2; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.2; }; + listen-on-v6 { none; }; + recursion no; + notify no; + minimal-responses no; + version none; // make statistics independent of the version number +}; + +statistics-channels { inet 10.53.0.2 port @EXTRAPORT1@ allow { localhost; }; }; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-sha256; +}; + +controls { + inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +dnssec-policy "manykeys" { + keys { + ksk lifetime unlimited algorithm 8; + zsk lifetime unlimited algorithm 8; + }; +}; + +zone "example" { + type primary; + file "example.db"; + allow-transfer { any; }; +}; + +zone "dnssec" { + type primary; + file "dnssec.db.signed"; + auto-dnssec maintain; + allow-update { any; }; + zone-statistics full; + dnssec-dnskey-kskonly yes; + update-check-ksk yes; +}; + +zone "manykeys" { + type primary; + file "manykeys.db.signed"; + allow-update { any; }; + zone-statistics full; + dnssec-policy "manykeys"; +}; diff --git a/bin/tests/system/statschannel/ns2/sign.sh b/bin/tests/system/statschannel/ns2/sign.sh new file mode 100644 index 0000000..ab23550 --- /dev/null +++ b/bin/tests/system/statschannel/ns2/sign.sh @@ -0,0 +1,45 @@ +#!/bin/sh -e + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. "$SYSTEMTESTTOP/conf.sh" + +set -e + +zone=dnssec. +infile=dnssec.db.in +zonefile=dnssec.db.signed +ksk=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -f KSK "$zone") +zsk=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "$zone") +# Sign deliberately with a very short expiration date. +"$SIGNER" -P -S -x -O full -e "now"+1s -o "$zone" -f "$zonefile" "$infile" > "signzone.out.$zone" 2>&1 +keyfile_to_key_id "$ksk" > dnssec.ksk.id +keyfile_to_key_id "$zsk" > dnssec.zsk.id + +zone=manykeys. +infile=manykeys.db.in +zonefile=manykeys.db.signed +ksk8=$("$KEYGEN" -q -a RSASHA256 -b 2048 -f KSK "$zone") +zsk8=$("$KEYGEN" -q -a RSASHA256 -b 2048 "$zone") +ksk13=$("$KEYGEN" -q -a ECDSAP256SHA256 -b 256 -f KSK "$zone") +zsk13=$("$KEYGEN" -q -a ECDSAP256SHA256 -b 256 "$zone") +ksk14=$("$KEYGEN" -q -a ECDSAP384SHA384 -b 384 -f KSK "$zone") +zsk14=$("$KEYGEN" -q -a ECDSAP384SHA384 -b 384 "$zone") +# Sign deliberately with a very short expiration date. +"$SIGNER" -S -x -O full -e "now"+1s -o "$zone" -f "$zonefile" "$infile" > "signzone.out.$zone" 2>&1 +keyfile_to_key_id "$ksk8" > manykeys.ksk8.id +keyfile_to_key_id "$zsk8" > manykeys.zsk8.id +keyfile_to_key_id "$ksk13" > manykeys.ksk13.id +keyfile_to_key_id "$zsk13" > manykeys.zsk13.id +keyfile_to_key_id "$ksk14" > manykeys.ksk14.id +keyfile_to_key_id "$zsk14" > manykeys.zsk14.id diff --git a/bin/tests/system/statschannel/ns3/named.conf.in b/bin/tests/system/statschannel/ns3/named.conf.in new file mode 100644 index 0000000..5f08c3f --- /dev/null +++ b/bin/tests/system/statschannel/ns3/named.conf.in @@ -0,0 +1,43 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +options { + query-source address 10.53.0.3; + notify-source 10.53.0.3; + transfer-source 10.53.0.3; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.3; }; + listen-on-v6 { none; }; + recursion no; + notify no; + minimal-responses no; + version none; // make statistics independent of the version number +}; + +statistics-channels { inet 10.53.0.3 port @EXTRAPORT1@ allow { localhost; }; }; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-sha256; +}; + +controls { + inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "example" { + type secondary; + file "example.db"; + primaries { 10.53.0.1; }; +}; diff --git a/bin/tests/system/statschannel/prereq.sh b/bin/tests/system/statschannel/prereq.sh new file mode 100644 index 0000000..4f8a444 --- /dev/null +++ b/bin/tests/system/statschannel/prereq.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh + +fail=0 + +if $PERL -e 'use File::Fetch;' 2>/dev/null +then + : +else + echo_i "This test requires the File::Fetch library." >&2 + fail=1 +fi + +exit $fail diff --git a/bin/tests/system/statschannel/server-json.pl b/bin/tests/system/statschannel/server-json.pl new file mode 100644 index 0000000..3715318 --- /dev/null +++ b/bin/tests/system/statschannel/server-json.pl @@ -0,0 +1,35 @@ +#!/usr/bin/perl + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# server-json.pl: +# Parses the JSON version of the server stats into a normalized format. + +use JSON; + +open(INPUT, "<json.stats"); +my $text = do{local$/;<INPUT>}; +close(INPUT); + +my $ref = decode_json($text); +foreach $key (keys %{$ref->{opcodes}}) { + print "opcode " . $key . ": " . $ref->{opcodes}->{$key} . "\n"; +} +foreach $key (keys %{$ref->{rcodes}}) { + print "rcode " . $key . ": " . $ref->{rcodes}->{$key} . "\n"; +} +foreach $key (keys %{$ref->{qtypes}}) { + print "qtype " . $key . ": " . $ref->{qtypes}->{$key} . "\n"; +} +foreach $key (keys %{$ref->{nsstats}}) { + print "nsstat " . $key . ": " . $ref->{nsstats}->{$key} . "\n"; +} diff --git a/bin/tests/system/statschannel/server-xml.pl b/bin/tests/system/statschannel/server-xml.pl new file mode 100644 index 0000000..5f76360 --- /dev/null +++ b/bin/tests/system/statschannel/server-xml.pl @@ -0,0 +1,25 @@ +#!/usr/bin/perl + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# server-xml.pl: +# Parses the XML version of the server stats into a normalized format. + +use XML::Simple; + +my $ref = XMLin("xml.stats"); +my $counters = $ref->{server}->{counters}; +foreach $group (@$counters) { + foreach $key (keys %{$group->{counter}}) { + print $group->{type} . " " . $key . ": ". $group->{counter}->{$key}->{content} . "\n"; + } +} diff --git a/bin/tests/system/statschannel/setup.sh b/bin/tests/system/statschannel/setup.sh new file mode 100644 index 0000000..4ebc39b --- /dev/null +++ b/bin/tests/system/statschannel/setup.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. "$SYSTEMTESTTOP/conf.sh" + +for conf in ns*/named.conf.in; do + copy_setports "$conf" "$(dirname "$conf")/$(basename "$conf" .in)" +done + +(cd ns2 && $SHELL sign.sh) diff --git a/bin/tests/system/statschannel/tests.sh b/bin/tests/system/statschannel/tests.sh new file mode 100644 index 0000000..0480b01 --- /dev/null +++ b/bin/tests/system/statschannel/tests.sh @@ -0,0 +1,392 @@ +#!/bin/sh + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +SYSTEMTESTTOP=.. +# shellcheck source=conf.sh +. "$SYSTEMTESTTOP/conf.sh" + +DIGCMD="$DIG @10.53.0.2 -p ${PORT}" +RNDCCMD="$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p ${CONTROLPORT} -s" + +if ! $FEATURETEST --have-json-c +then + unset PERL_JSON + echo_i "JSON was not configured; skipping" >&2 +elif $PERL -e 'use JSON;' 2>/dev/null +then + PERL_JSON=1 +else + unset PERL_JSON + echo_i "JSON tests require JSON library; skipping" >&2 +fi + +if ! $FEATURETEST --have-libxml2 +then + unset PERL_XML + echo_i "XML was not configured; skipping" >&2 +elif $PERL -e 'use XML::Simple;' 2>/dev/null +then + PERL_XML=1 +else + unset PERL_XML + echo_i "XML tests require XML::Simple; skipping" >&2 +fi + +if [ ! "$PERL_JSON" -a ! "$PERL_XML" ]; then + echo_i "skipping all tests" + exit 0 +fi + + +getzones() { + sleep 1 + echo_i "... using $1" + case $1 in + xml) path='xml/v3/zones' ;; + json) path='json/v1/zones' ;; + *) return 1 ;; + esac + file=`$PERL fetch.pl -p ${EXTRAPORT1} $path` + cp $file $file.$1.$3 + $PERL zones-${1}.pl $file $2 2>/dev/null | sort > zones.out.$3 + result=$? + return $result +} + +# TODO: Move loadkeys_on to conf.sh.common +loadkeys_on() { + nsidx=$1 + zone=$2 + nextpart ns${nsidx}/named.run > /dev/null + $RNDCCMD 10.53.0.${nsidx} loadkeys ${zone} | sed "s/^/ns${nsidx} /" | cat_i + wait_for_log 20 "next key event" ns${nsidx}/named.run +} + +status=0 +n=1 +ret=0 +echo_i "checking consistency between named.stats and xml/json ($n)" +rm -f ns2/named.stats +$DIGCMD +tcp example ns > dig.out.$n || ret=1 +$RNDCCMD 10.53.0.2 stats 2>&1 | sed 's/^/I:ns1 /' +query_count=`awk '/QUERY/ {print $1}' ns2/named.stats` +txt_count=`awk '/TXT/ {print $1}' ns2/named.stats` +noerror_count=`awk '/NOERROR/ {print $1}' ns2/named.stats` +if [ $PERL_XML ]; then + file=`$PERL fetch.pl -p ${EXTRAPORT1} xml/v3/server` + mv $file xml.stats + $PERL server-xml.pl > xml.fmtstats 2> /dev/null + xml_query_count=`awk '/opcode QUERY/ { print $NF }' xml.fmtstats` + xml_query_count=${xml_query_count:-0} + [ "$query_count" -eq "$xml_query_count" ] || ret=1 + xml_txt_count=`awk '/qtype TXT/ { print $NF }' xml.fmtstats` + xml_txt_count=${xml_txt_count:-0} + [ "$txt_count" -eq "$xml_txt_count" ] || ret=1 + xml_noerror_count=`awk '/rcode NOERROR/ { print $NF }' xml.fmtstats` + xml_noerror_count=${xml_noerror_count:-0} + [ "$noerror_count" -eq "$xml_noerror_count" ] || ret=1 +fi +if [ $PERL_JSON ]; then + file=`$PERL fetch.pl -p ${EXTRAPORT1} json/v1/server` + mv $file json.stats + $PERL server-json.pl > json.fmtstats 2> /dev/null + json_query_count=`awk '/opcode QUERY/ { print $NF }' json.fmtstats` + json_query_count=${json_query_count:-0} + [ "$query_count" -eq "$json_query_count" ] || ret=1 + json_txt_count=`awk '/qtype TXT/ { print $NF }' json.fmtstats` + json_txt_count=${json_txt_count:-0} + [ "$txt_count" -eq "$json_txt_count" ] || ret=1 + json_noerror_count=`awk '/rcode NOERROR/ { print $NF }' json.fmtstats` + json_noerror_count=${json_noerror_count:-0} + [ "$noerror_count" -eq "$json_noerror_count" ] || ret=1 +fi +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` +n=`expr $n + 1` + +ret=0 +echo_i "checking malloced memory statistics xml/json ($n)" +if [ $PERL_XML ]; then + file=`$PERL fetch.pl -p ${EXTRAPORT1} xml/v3/mem` + mv $file xml.mem + $PERL mem-xml.pl $file > xml.fmtmem + grep "'Malloced' => '[0-9][0-9]*'" xml.fmtmem > /dev/null || ret=1 + grep "'malloced' => '[0-9][0-9]*'" xml.fmtmem > /dev/null || ret=1 + grep "'maxmalloced' => '[0-9][0-9]*'" xml.fmtmem > /dev/null || ret=1 +fi +if [ $PERL_JSON ]; then + file=`$PERL fetch.pl -p ${EXTRAPORT1} json/v1/mem` + mv $file json.mem + grep '"malloced":[0-9][0-9]*,' json.mem > /dev/null || ret=1 + grep '"maxmalloced":[0-9][0-9]*,' json.mem > /dev/null || ret=1 + grep '"Malloced":[0-9][0-9]*,' json.mem > /dev/null || ret=1 +fi +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` +n=`expr $n + 1` + +echo_i "checking consistency between regular and compressed output ($n)" +for i in 1 2 3 4 5; do + ret=0 + if $FEATURETEST --have-libxml2; + then + URL=http://10.53.0.2:${EXTRAPORT1}/xml/v3/server + filter_str='s#<current-time>.*</current-time>##g' + else + URL=http://10.53.0.2:${EXTRAPORT1}/json/v1/server + filter_str='s#"current-time.*",##g' + fi + $CURL -D regular.headers $URL 2>/dev/null | \ + sed -e "$filter_str" > regular.out + $CURL -D compressed.headers --compressed $URL 2>/dev/null | \ + sed -e "$filter_str" > compressed.out + diff regular.out compressed.out >/dev/null || ret=1 + if [ $ret != 0 ]; then + echo_i "failed on try $i, probably a timing issue, trying again" + sleep 1 + else + break + fi +done + +status=`expr $status + $ret` +n=`expr $n + 1` + +ret=0 +echo_i "checking if compressed output is really compressed ($n)" +if $FEATURETEST --with-zlib; +then + REGSIZE=`cat regular.headers | \ + grep -i Content-Length | sed -e "s/.*: \([0-9]*\).*/\1/"` + COMPSIZE=`cat compressed.headers | \ + grep -i Content-Length | sed -e "s/.*: \([0-9]*\).*/\1/"` + if [ ! `expr $REGSIZE / $COMPSIZE` -gt 2 ]; then + ret=1 + fi +else + echo_i "skipped" +fi +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` +n=`expr $n + 1` + +# Test dnssec sign statistics. +zone="dnssec" +sign_prefix="dnssec-sign operations" +refresh_prefix="dnssec-refresh operations" +ksk_id=`cat ns2/$zone.ksk.id` +zsk_id=`cat ns2/$zone.zsk.id` + +# Test sign operations for scheduled resigning. +ret=0 +# The dnssec zone has 10 RRsets to sign (including NSEC) with the ZSK and one +# RRset (DNSKEY) with the KSK. So starting named with signatures that expire +# almost right away, this should trigger 10 zsk and 1 ksk sign operations. +echo "${refresh_prefix} ${zsk_id}: 10" > zones.expect +echo "${refresh_prefix} ${ksk_id}: 1" >> zones.expect +echo "${sign_prefix} ${zsk_id}: 10" >> zones.expect +echo "${sign_prefix} ${ksk_id}: 1" >> zones.expect +cat zones.expect | sort > zones.expect.$n +rm -f zones.expect +# Fetch and check the dnssec sign statistics. +echo_i "fetching zone '$zone' stats data after zone maintenance at startup ($n)" +if [ $PERL_XML ]; then + getzones xml $zone x$n || ret=1 + cmp zones.out.x$n zones.expect.$n || ret=1 +fi +if [ $PERL_JSON ]; then + getzones json 0 j$n || ret=1 + cmp zones.out.j$n zones.expect.$n || ret=1 +fi +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` +n=`expr $n + 1` + +# Test sign operations after dynamic update. +ret=0 +( +# Update dnssec zone to trigger signature creation. +echo zone $zone +echo server 10.53.0.2 "$PORT" +echo update add $zone. 300 in txt "nsupdate added me" +echo send +) | $NSUPDATE +# This should trigger the resign of SOA, TXT and NSEC (+3 zsk). +echo "${refresh_prefix} ${zsk_id}: 10" > zones.expect +echo "${refresh_prefix} ${ksk_id}: 1" >> zones.expect +echo "${sign_prefix} ${zsk_id}: 13" >> zones.expect +echo "${sign_prefix} ${ksk_id}: 1" >> zones.expect +cat zones.expect | sort > zones.expect.$n +rm -f zones.expect +# Fetch and check the dnssec sign statistics. +echo_i "fetching zone '$zone' stats data after dynamic update ($n)" +if [ $PERL_XML ]; then + getzones xml $zone x$n || ret=1 + cmp zones.out.x$n zones.expect.$n || ret=1 +fi +if [ $PERL_JSON ]; then + getzones json 0 j$n || ret=1 + cmp zones.out.j$n zones.expect.$n || ret=1 +fi +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` +n=`expr $n + 1` + +# Test sign operations of KSK. +ret=0 +echo_i "fetch zone '$zone' stats data after updating DNSKEY RRset ($n)" +# Add a standby DNSKEY, this triggers resigning the DNSKEY RRset. +zsk=$("$KEYGEN" -K ns2 -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "$zone") +$SETTIME -K ns2 -P now -A never $zsk.key > /dev/null +loadkeys_on 2 $zone || ret=1 +# This should trigger the resign of SOA (+1 zsk) and DNSKEY (+1 ksk). +echo "${refresh_prefix} ${zsk_id}: 11" > zones.expect +echo "${refresh_prefix} ${ksk_id}: 2" >> zones.expect +echo "${sign_prefix} ${zsk_id}: 14" >> zones.expect +echo "${sign_prefix} ${ksk_id}: 2" >> zones.expect +cat zones.expect | sort > zones.expect.$n +rm -f zones.expect +# Fetch and check the dnssec sign statistics. +if [ $PERL_XML ]; then + getzones xml $zone x$n || ret=1 + cmp zones.out.x$n zones.expect.$n || ret=1 +fi +if [ $PERL_JSON ]; then + getzones json 0 j$n || ret=1 + cmp zones.out.j$n zones.expect.$n || ret=1 +fi +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` +n=`expr $n + 1` + +# Test sign operations for scheduled resigning (many keys). +ret=0 +zone="manykeys" +ksk8_id=`cat ns2/$zone.ksk8.id` +zsk8_id=`cat ns2/$zone.zsk8.id` +ksk13_id=`cat ns2/$zone.ksk13.id` +zsk13_id=`cat ns2/$zone.zsk13.id` +ksk14_id=`cat ns2/$zone.ksk14.id` +zsk14_id=`cat ns2/$zone.zsk14.id` +num_ids=$( (echo $ksk8_id; echo $zsk8_id; echo $ksk13_id; echo $zsk13_id; echo $ksk14_id; echo $zsk14_id;) | sort -u | wc -l) +# The dnssec zone has 10 RRsets to sign (including NSEC) with the ZSKs and one +# RRset (DNSKEY) with the KSKs. So starting named with signatures that expire +# almost right away, this should trigger 10 zsk and 1 ksk sign operations per +# key. +echo "${refresh_prefix} ${zsk8_id}: 10" > zones.expect +echo "${refresh_prefix} ${zsk13_id}: 10" >> zones.expect +echo "${refresh_prefix} ${zsk14_id}: 10" >> zones.expect +echo "${refresh_prefix} ${ksk8_id}: 1" >> zones.expect +echo "${refresh_prefix} ${ksk13_id}: 1" >> zones.expect +echo "${refresh_prefix} ${ksk14_id}: 1" >> zones.expect +echo "${sign_prefix} ${zsk8_id}: 10" >> zones.expect +echo "${sign_prefix} ${zsk13_id}: 10" >> zones.expect +echo "${sign_prefix} ${zsk14_id}: 10" >> zones.expect +echo "${sign_prefix} ${ksk8_id}: 1" >> zones.expect +echo "${sign_prefix} ${ksk13_id}: 1" >> zones.expect +echo "${sign_prefix} ${ksk14_id}: 1" >> zones.expect +cat zones.expect | sort > zones.expect.$n +rm -f zones.expect +# Fetch and check the dnssec sign statistics. +echo_i "fetching zone '$zone' stats data after zone maintenance at startup ($n)" +if test $num_ids -eq 6 +then + if [ $PERL_XML ]; then + getzones xml $zone x$n || ret=1 + cmp zones.out.x$n zones.expect.$n || ret=1 + fi + if [ $PERL_JSON ]; then + getzones json 2 j$n || ret=1 + cmp zones.out.j$n zones.expect.$n || ret=1 + fi + if [ $ret != 0 ]; then echo_i "failed"; fi +else + echo_i "skipped: duplicate key id detected (fixed in BIND 9.19)" +fi +status=`expr $status + $ret` +n=`expr $n + 1` + +# Test sign operations after dynamic update (many keys). +ret=0 +( +# Update dnssec zone to trigger signature creation. +echo zone $zone +echo server 10.53.0.2 "$PORT" +echo update add $zone. 300 in txt "nsupdate added me" +echo send +) | $NSUPDATE +# This should trigger the resign of SOA, TXT and NSEC (+3 zsk). +echo "${refresh_prefix} ${zsk8_id}: 10" > zones.expect +echo "${refresh_prefix} ${zsk13_id}: 10" >> zones.expect +echo "${refresh_prefix} ${zsk14_id}: 10" >> zones.expect +echo "${refresh_prefix} ${ksk8_id}: 1" >> zones.expect +echo "${refresh_prefix} ${ksk13_id}: 1" >> zones.expect +echo "${refresh_prefix} ${ksk14_id}: 1" >> zones.expect +echo "${sign_prefix} ${zsk8_id}: 13" >> zones.expect +echo "${sign_prefix} ${zsk13_id}: 13" >> zones.expect +echo "${sign_prefix} ${zsk14_id}: 13" >> zones.expect +echo "${sign_prefix} ${ksk8_id}: 1" >> zones.expect +echo "${sign_prefix} ${ksk13_id}: 1" >> zones.expect +echo "${sign_prefix} ${ksk14_id}: 1" >> zones.expect +cat zones.expect | sort > zones.expect.$n +rm -f zones.expect +# Fetch and check the dnssec sign statistics. +echo_i "fetching zone '$zone' stats data after dynamic update ($n)" +if test $num_ids -eq 6 +then + if [ $PERL_XML ]; then + getzones xml $zone x$n || ret=1 + cmp zones.out.x$n zones.expect.$n || ret=1 + fi + if [ $PERL_JSON ]; then + getzones json 2 j$n || ret=1 + cmp zones.out.j$n zones.expect.$n || ret=1 + fi + if [ $ret != 0 ]; then echo_i "failed"; fi +else + echo_i "skipped: duplicate key id detected (fixed in BIND 9.19)" +fi +status=`expr $status + $ret` +n=`expr $n + 1` + +# Test sign operations after dnssec-policy change (removing keys). +ret=0 +copy_setports ns2/named2.conf.in ns2/named.conf +$RNDCCMD 10.53.0.2 reload 2>&1 | sed 's/^/I:ns2 /' +# This should trigger the resign of DNSKEY (+1 ksk), and SOA, NSEC, +# TYPE65534 (+3 zsk). The dnssec-sign statistics for the removed keys should +# be cleared and thus no longer visible. But NSEC and SOA are (mistakenly) +# counted double, one time because of zone_resigninc and one time because of +# zone_nsec3chain. So +5 zsk in total. +echo "${refresh_prefix} ${zsk8_id}: 15" > zones.expect +echo "${refresh_prefix} ${ksk8_id}: 2" >> zones.expect +echo "${sign_prefix} ${zsk8_id}: 18" >> zones.expect +echo "${sign_prefix} ${ksk8_id}: 2" >> zones.expect +cat zones.expect | sort > zones.expect.$n +rm -f zones.expect +# Fetch and check the dnssec sign statistics. +echo_i "fetching zone '$zone' stats data after dnssec-policy change ($n)" +if [ $PERL_XML ]; then + getzones xml $zone x$n || ret=1 + cmp zones.out.x$n zones.expect.$n || ret=1 +fi +if [ $PERL_JSON ]; then + getzones json 2 j$n || ret=1 + cmp zones.out.j$n zones.expect.$n || ret=1 +fi +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` +n=`expr $n + 1` + +echo_i "exit status: $status" +[ $status -eq 0 ] || exit 1 diff --git a/bin/tests/system/statschannel/tests_json.py b/bin/tests/system/statschannel/tests_json.py new file mode 100755 index 0000000..c459925 --- /dev/null +++ b/bin/tests/system/statschannel/tests_json.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +from datetime import datetime + +import pytest + +import generic +import pytest_custom_markers + +pytestmark = pytest_custom_markers.have_json_c +requests = pytest.importorskip("requests") + + +# JSON helper functions +def fetch_zones_json(statsip, statsport): + r = requests.get( + "http://{}:{}/json/v1/zones".format(statsip, statsport), timeout=600 + ) + assert r.status_code == 200 + + data = r.json() + return data["views"]["_default"]["zones"] + + +def fetch_traffic_json(statsip, statsport): + r = requests.get( + "http://{}:{}/json/v1/traffic".format(statsip, statsport), timeout=600 + ) + assert r.status_code == 200 + + data = r.json() + + return data["traffic"] + + +def load_timers_json(zone, primary=True): + name = zone["name"] + + # Check if the primary zone timer exists + assert "loaded" in zone + loaded = datetime.strptime(zone["loaded"], generic.fmt) + + if primary: + # Check if the secondary zone timers does not exist + assert "expires" not in zone + assert "refresh" not in zone + expires = None + refresh = None + else: + assert "expires" in zone + assert "refresh" in zone + expires = datetime.strptime(zone["expires"], generic.fmt) + refresh = datetime.strptime(zone["refresh"], generic.fmt) + + return (name, loaded, expires, refresh) + + +def load_zone_json(zone): + name = zone["name"] + + return name + + +def test_zone_timers_primary_json(statsport): + generic.test_zone_timers_primary( + fetch_zones_json, + load_timers_json, + statsip="10.53.0.1", + statsport=statsport, + zonedir="ns1", + ) + + +def test_zone_timers_secondary_json(statsport): + generic.test_zone_timers_secondary( + fetch_zones_json, + load_timers_json, + statsip="10.53.0.3", + statsport=statsport, + zonedir="ns3", + ) + + +def test_zone_with_many_keys_json(statsport): + generic.test_zone_with_many_keys( + fetch_zones_json, load_zone_json, statsip="10.53.0.2", statsport=statsport + ) + + +def test_traffic_json(named_port, statsport): + generic_dnspython = pytest.importorskip("generic_dnspython") + generic_dnspython.test_traffic( + fetch_traffic_json, statsip="10.53.0.2", statsport=statsport, port=named_port + ) diff --git a/bin/tests/system/statschannel/tests_xml.py b/bin/tests/system/statschannel/tests_xml.py new file mode 100755 index 0000000..7f0b37e --- /dev/null +++ b/bin/tests/system/statschannel/tests_xml.py @@ -0,0 +1,135 @@ +#!/usr/bin/python3 + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +from datetime import datetime +import xml.etree.ElementTree as ET + +import pytest + +import generic +import pytest_custom_markers + +pytestmark = pytest_custom_markers.have_libxml2 +requests = pytest.importorskip("requests") + + +# XML helper functions +def fetch_zones_xml(statsip, statsport): + r = requests.get( + "http://{}:{}/xml/v3/zones".format(statsip, statsport), timeout=600 + ) + assert r.status_code == 200 + + root = ET.fromstring(r.text) + + default_view = None + for view in root.find("views").iter("view"): + if view.attrib["name"] == "_default": + default_view = view + break + assert default_view is not None + + return default_view.find("zones").findall("zone") + + +def fetch_traffic_xml(statsip, statsport): + def load_counters(data): + out = {} + for counter in data.findall("counter"): + out[counter.attrib["name"]] = int(counter.text) + + return out + + r = requests.get( + "http://{}:{}/xml/v3/traffic".format(statsip, statsport), timeout=600 + ) + assert r.status_code == 200 + + root = ET.fromstring(r.text) + + traffic = {} + for ip in ["ipv4", "ipv6"]: + for proto in ["udp", "tcp"]: + proto_root = root.find("traffic").find(ip).find(proto) + for counters in proto_root.findall("counters"): + if counters.attrib["type"] == "request-size": + key = "dns-{}-requests-sizes-received-{}".format(proto, ip) + else: + key = "dns-{}-responses-sizes-sent-{}".format(proto, ip) + + values = load_counters(counters) + traffic[key] = values + + return traffic + + +def load_timers_xml(zone, primary=True): + name = zone.attrib["name"] + + loaded_el = zone.find("loaded") + assert loaded_el is not None + loaded = datetime.strptime(loaded_el.text, generic.fmt) + + expires_el = zone.find("expires") + refresh_el = zone.find("refresh") + if primary: + assert expires_el is None + assert refresh_el is None + expires = None + refresh = None + else: + assert expires_el is not None + assert refresh_el is not None + expires = datetime.strptime(expires_el.text, generic.fmt) + refresh = datetime.strptime(refresh_el.text, generic.fmt) + + return (name, loaded, expires, refresh) + + +def load_zone_xml(zone): + name = zone.attrib["name"] + + return name + + +def test_zone_timers_primary_xml(statsport): + generic.test_zone_timers_primary( + fetch_zones_xml, + load_timers_xml, + statsip="10.53.0.1", + statsport=statsport, + zonedir="ns1", + ) + + +def test_zone_timers_secondary_xml(statsport): + generic.test_zone_timers_secondary( + fetch_zones_xml, + load_timers_xml, + statsip="10.53.0.3", + statsport=statsport, + zonedir="ns3", + ) + + +def test_zone_with_many_keys_xml(statsport): + generic.test_zone_with_many_keys( + fetch_zones_xml, load_zone_xml, statsip="10.53.0.2", statsport=statsport + ) + + +def test_traffic_xml(named_port, statsport): + generic_dnspython = pytest.importorskip("generic_dnspython") + generic_dnspython.test_traffic( + fetch_traffic_xml, statsip="10.53.0.2", statsport=statsport, port=named_port + ) diff --git a/bin/tests/system/statschannel/traffic-json.pl b/bin/tests/system/statschannel/traffic-json.pl new file mode 100644 index 0000000..353d6c7 --- /dev/null +++ b/bin/tests/system/statschannel/traffic-json.pl @@ -0,0 +1,49 @@ +#!/usr/bin/perl + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# traffic-json.pl: +# Parses the JSON version of the RSSAC002 traffic stats into a +# normalized format. + +use JSON; + +my $file = $ARGV[0]; +open(INPUT, "<$file"); +my $text = do{local$/;<INPUT>}; +close(INPUT); + +my $ref = decode_json($text); + +my $tcprcvd = $ref->{traffic}->{"dns-tcp-requests-sizes-received-ipv4"}; +my $type = "tcp request-size "; +foreach $key (keys %{$tcprcvd}) { + print $type . $key . ": ". $tcprcvd->{$key} ."\n"; +} + +my $tcpsent = $ref->{traffic}->{"dns-tcp-responses-sizes-sent-ipv4"}; +my $type = "tcp response-size "; +foreach $key (keys %{$tcpsent}) { + print $type . $key . ": ". $tcpsent->{$key} ."\n"; +} + +my $udprcvd = $ref->{traffic}->{"dns-udp-requests-sizes-received-ipv4"}; +my $type = "udp request-size "; +foreach $key (keys %{$udprcvd}) { + print $type . $key . ": ". $udprcvd->{$key} ."\n"; +} + +my $udpsent = $ref->{traffic}->{"dns-udp-responses-sizes-sent-ipv4"}; +my $type = "udp response-size "; +foreach $key (keys %{$udpsent}) { + print $type . $key . ": ". $udpsent->{$key} ."\n"; +} diff --git a/bin/tests/system/statschannel/traffic-xml.pl b/bin/tests/system/statschannel/traffic-xml.pl new file mode 100644 index 0000000..5552cc5 --- /dev/null +++ b/bin/tests/system/statschannel/traffic-xml.pl @@ -0,0 +1,46 @@ +#!/usr/bin/perl + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# traffic-xml.pl: +# Parses the XML version of the RSSAC002 traffic stats into a +# normalized format. + +use XML::Simple; + +my $file = $ARGV[0]; + +my $ref = XMLin($file); + +my $udp = $ref->{traffic}->{ipv4}->{udp}->{counters}; +foreach $group (@$udp) { + my $type = "udp " . $group->{type} . " "; + if (exists $group->{counter}->{name}) { + print $type . $group->{counter}->{name} . ": " . $group->{counter}->{content} . "\n"; + } else { + foreach $key (keys %{$group->{counter}}) { + print $type . $key . ": ". $group->{counter}->{$key}->{content} ."\n"; + } + } +} + +my $tcp = $ref->{traffic}->{ipv4}->{tcp}->{counters}; +foreach $group (@$tcp) { + my $type = "tcp " . $group->{type} . " "; + if (exists $group->{counter}->{name}) { + print $type . $group->{counter}->{name} . ": " . $group->{counter}->{content} . "\n"; + } else { + foreach $key (keys %{$group->{counter}}) { + print $type . $key . ": ". $group->{counter}->{$key}->{content} ."\n"; + } + } +} diff --git a/bin/tests/system/statschannel/traffic.expect.1 b/bin/tests/system/statschannel/traffic.expect.1 new file mode 100644 index 0000000..5938d5d --- /dev/null +++ b/bin/tests/system/statschannel/traffic.expect.1 @@ -0,0 +1,2 @@ +tcp request-size 16-31: 1 +tcp response-size 64-79: 1 diff --git a/bin/tests/system/statschannel/traffic.expect.2 b/bin/tests/system/statschannel/traffic.expect.2 new file mode 100644 index 0000000..6c9e25a --- /dev/null +++ b/bin/tests/system/statschannel/traffic.expect.2 @@ -0,0 +1,4 @@ +tcp request-size 16-31: 1 +tcp response-size 64-79: 1 +udp request-size 48-63: 1 +udp response-size 112-127: 1 diff --git a/bin/tests/system/statschannel/traffic.expect.4 b/bin/tests/system/statschannel/traffic.expect.4 new file mode 100644 index 0000000..3f892f5 --- /dev/null +++ b/bin/tests/system/statschannel/traffic.expect.4 @@ -0,0 +1,5 @@ +tcp request-size 16-31: 1 +tcp response-size 64-79: 1 +udp request-size 48-63: 2 +udp response-size 112-127: 1 +udp response-size 848-863: 1 diff --git a/bin/tests/system/statschannel/traffic.expect.5 b/bin/tests/system/statschannel/traffic.expect.5 new file mode 100644 index 0000000..15911b1 --- /dev/null +++ b/bin/tests/system/statschannel/traffic.expect.5 @@ -0,0 +1,7 @@ +tcp request-size 16-31: 1 +tcp request-size 48-63: 1 +tcp response-size 112-127: 1 +tcp response-size 64-79: 1 +udp request-size 48-63: 2 +udp response-size 112-127: 1 +udp response-size 848-863: 1 diff --git a/bin/tests/system/statschannel/traffic.expect.6 b/bin/tests/system/statschannel/traffic.expect.6 new file mode 100644 index 0000000..73fc8f1 --- /dev/null +++ b/bin/tests/system/statschannel/traffic.expect.6 @@ -0,0 +1,8 @@ +tcp request-size 16-31: 1 +tcp request-size 48-63: 2 +tcp response-size 112-127: 1 +tcp response-size 64-79: 1 +tcp response-size 848-863: 1 +udp request-size 48-63: 2 +udp response-size 112-127: 1 +udp response-size 848-863: 1 diff --git a/bin/tests/system/statschannel/zones-json.pl b/bin/tests/system/statschannel/zones-json.pl new file mode 100644 index 0000000..9eec9db --- /dev/null +++ b/bin/tests/system/statschannel/zones-json.pl @@ -0,0 +1,37 @@ +#!/usr/bin/perl + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# zones-json.pl: +# Parses the JSON version of the dnssec sign stats for the +# "dnssec" zone in the default view into a normalized format. + +use JSON; + +my $file = $ARGV[0]; +my $zone = $ARGV[1]; +open(INPUT, "<$file"); +my $text = do{local$/;<INPUT>}; +close(INPUT); + +my $ref = decode_json($text); + +my $dnssecsign = $ref->{views}->{_default}->{zones}[$zone]->{"dnssec-sign"}; +my $type = "dnssec-sign operations "; +foreach $key (keys %{$dnssecsign}) { + print $type . $key . ": ". $dnssecsign->{$key} ."\n"; +} +my $dnssecrefresh = $ref->{views}->{_default}->{zones}[$zone]->{"dnssec-refresh"}; +my $type = "dnssec-refresh operations "; +foreach $key (keys %{$dnssecrefresh}) { + print $type . $key . ": ". $dnssecrefresh->{$key} ."\n"; +} diff --git a/bin/tests/system/statschannel/zones-xml.pl b/bin/tests/system/statschannel/zones-xml.pl new file mode 100644 index 0000000..be86852 --- /dev/null +++ b/bin/tests/system/statschannel/zones-xml.pl @@ -0,0 +1,40 @@ +#!/usr/bin/perl + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# zones-xml.pl: +# Parses the XML version of the dnssec sign stats for the +# "dnssec" zone in the default view into a normalized format. + +use XML::Simple; + +my $file = $ARGV[0]; +my $zone = $ARGV[1]; + +my $ref = XMLin($file); + +my $counters = $ref->{views}->{view}->{_default}->{zones}->{zone}->{$zone}->{counters}; + +foreach $group (@$counters) { + + my $type = $group->{type}; + + if ($type eq "dnssec-sign" || $type eq "dnssec-refresh") { + if (exists $group->{counter}->{name}) { + print $type . " operations " . $group->{counter}->{name} . ": " . $group->{counter}->{content} . "\n"; + } else { + foreach $key (keys %{$group->{counter}}) { + print $type . " operations " . $key . ": ". $group->{counter}->{$key}->{content} ."\n"; + } + } + } +} |