Commit 11db6399 authored by Matthijs Mekking's avatar Matthijs Mekking 🏡
Browse files

Merge branch '513-matthijs-dnssec-sign-stats' into 'master'

Resolve "New metrics to report DNSSEC signing operations and IXFRs [ISC-support #13431]"

Closes #513

See merge request !2067
parents e317a675 3a3f40e3
Pipeline #16322 passed with stages
in 1 minute
5254. [func] Collect metrics to report to the statistics-channel
DNSSEC signing operations (dnssec-sign) and refresh
operations (dnssec-refresh) per zone and per keytag.
[GL #513]
5253. [port] Support platforms that don't define ULLONG_MAX.
[GL #1098]
......
......@@ -1444,6 +1444,60 @@ rcodestat_dump(dns_rcode_t code, uint64_t val, void *arg) {
#endif
}
static void
dnssecsignstat_dump(dns_keytag_t tag, uint64_t val, void *arg) {
FILE *fp;
char tagbuf[64];
stats_dumparg_t *dumparg = arg;
#ifdef HAVE_LIBXML2
xmlTextWriterPtr writer;
int xmlrc;
#endif
#ifdef HAVE_JSON_C
json_object *zoneobj, *obj;
#endif
snprintf(tagbuf, sizeof(tagbuf), "%u", tag);
switch (dumparg->type) {
case isc_statsformat_file:
fp = dumparg->arg;
fprintf(fp, "%20" PRIu64 " %s\n", val, tagbuf);
break;
case isc_statsformat_xml:
#ifdef HAVE_LIBXML2
writer = dumparg->arg;
TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counter"));
TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "name",
ISC_XMLCHAR tagbuf ));
TRY0(xmlTextWriterWriteFormatString(writer,
"%" PRIu64,
val));
TRY0(xmlTextWriterEndElement(writer)); /* counter */
#endif
break;
case isc_statsformat_json:
#ifdef HAVE_JSON_C
zoneobj = (json_object *) dumparg->arg;
obj = json_object_new_int64(val);
if (obj == NULL) {
return;
}
json_object_object_add(zoneobj, tagbuf, obj);
#endif
break;
}
return;
#ifdef HAVE_LIBXML2
error:
isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL,
NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR,
"failed at dnssecsignstat_dump()");
dumparg->result = ISC_R_FAILURE;
return;
#endif
}
#ifdef HAVE_LIBXML2
/*
* Which statistics to include when rendering to XML
......@@ -1506,6 +1560,8 @@ zone_xmlrender(dns_zone_t *zone, void *arg) {
isc_stats_t *zonestats;
isc_stats_t *gluecachestats;
dns_stats_t *rcvquerystats;
dns_stats_t *dnssecsignstats;
dns_stats_t *dnssecrefreshstats;
uint64_t nsstat_values[ns_statscounter_max];
uint64_t gluecachestats_values[dns_gluecachestatscounter_max];
......@@ -1533,8 +1589,8 @@ zone_xmlrender(dns_zone_t *zone, void *arg) {
TRY0(xmlTextWriterStartElement(writer,
ISC_XMLCHAR "counters"));
TRY0(xmlTextWriterWriteAttribute(writer,
ISC_XMLCHAR "type",
ISC_XMLCHAR "gluecache"));
ISC_XMLCHAR "type",
ISC_XMLCHAR "gluecache"));
result = dump_counters(gluecachestats,
isc_statsformat_xml,
......@@ -1567,6 +1623,46 @@ zone_xmlrender(dns_zone_t *zone, void *arg) {
/* counters type="qtype"*/
TRY0(xmlTextWriterEndElement(writer));
}
dnssecsignstats = dns_zone_getdnssecsignstats(zone);
if (dnssecsignstats != NULL) {
TRY0(xmlTextWriterStartElement(writer,
ISC_XMLCHAR "counters"));
TRY0(xmlTextWriterWriteAttribute(writer,
ISC_XMLCHAR "type",
ISC_XMLCHAR "dnssec-sign"));
dumparg.result = ISC_R_SUCCESS;
dns_dnssecsignstats_dump(dnssecsignstats,
dnssecsignstat_dump,
&dumparg, 0);
if(dumparg.result != ISC_R_SUCCESS) {
goto error;
}
/* counters type="dnssec-sign"*/
TRY0(xmlTextWriterEndElement(writer));
}
dnssecrefreshstats = dns_zone_getdnssecrefreshstats(zone);
if (dnssecrefreshstats != NULL) {
TRY0(xmlTextWriterStartElement(writer,
ISC_XMLCHAR "counters"));
TRY0(xmlTextWriterWriteAttribute(writer,
ISC_XMLCHAR "type",
ISC_XMLCHAR "dnssec-refresh"));
dumparg.result = ISC_R_SUCCESS;
dns_dnssecsignstats_dump(dnssecrefreshstats,
dnssecsignstat_dump,
&dumparg, 0);
if(dumparg.result != ISC_R_SUCCESS) {
goto error;
}
/* counters type="dnssec-refresh"*/
TRY0(xmlTextWriterEndElement(writer));
}
}
TRY0(xmlTextWriterEndElement(writer)); /* zone */
......@@ -2287,6 +2383,8 @@ zone_jsonrender(dns_zone_t *zone, void *arg) {
isc_stats_t *zonestats;
isc_stats_t *gluecachestats;
dns_stats_t *rcvquerystats;
dns_stats_t *dnssecsignstats;
dns_stats_t *dnssecrefreshstats;
uint64_t nsstat_values[ns_statscounter_max];
uint64_t gluecachestats_values[dns_gluecachestatscounter_max];
......@@ -2364,6 +2462,58 @@ zone_jsonrender(dns_zone_t *zone, void *arg) {
else
json_object_put(counters);
}
dnssecsignstats = dns_zone_getdnssecsignstats(zone);
if (dnssecsignstats != NULL) {
stats_dumparg_t dumparg;
json_object *counters = json_object_new_object();
CHECKMEM(counters);
dumparg.type = isc_statsformat_json;
dumparg.arg = counters;
dumparg.result = ISC_R_SUCCESS;
dns_dnssecsignstats_dump(dnssecsignstats,
dnssecsignstat_dump,
&dumparg, 0);
if (dumparg.result != ISC_R_SUCCESS) {
json_object_put(counters);
goto error;
}
if (json_object_get_object(counters)->count != 0) {
json_object_object_add(zoneobj,
"dnssec-sign",
counters);
} else {
json_object_put(counters);
}
}
dnssecrefreshstats = dns_zone_getdnssecrefreshstats(zone);
if (dnssecrefreshstats != NULL) {
stats_dumparg_t dumparg;
json_object *counters = json_object_new_object();
CHECKMEM(counters);
dumparg.type = isc_statsformat_json;
dumparg.arg = counters;
dumparg.result = ISC_R_SUCCESS;
dns_dnssecsignstats_dump(dnssecrefreshstats,
dnssecsignstat_dump,
&dumparg, 0);
if (dumparg.result != ISC_R_SUCCESS) {
json_object_put(counters);
goto error;
}
if (json_object_get_object(counters)->count != 0) {
json_object_object_add(zoneobj,
"dnssec-refresh",
counters);
} else {
json_object_put(counters);
}
}
}
json_object_array_add(zonearray, zoneobj);
......
......@@ -904,6 +904,8 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
const dns_master_style_t *masterstyle = &dns_master_style_default;
isc_stats_t *zoneqrystats;
dns_stats_t *rcvquerystats;
dns_stats_t *dnssecsignstats;
dns_stats_t *dnssecrefreshstats;
dns_zonestat_level_t statlevel = dns_zonestat_none;
int seconds;
dns_zone_t *mayberaw = (raw != NULL) ? raw : zone;
......@@ -1188,14 +1190,19 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
zoneqrystats = NULL;
rcvquerystats = NULL;
dnssecsignstats = NULL;
dnssecrefreshstats = NULL;
if (statlevel == dns_zonestat_full) {
RETERR(isc_stats_create(mctx, &zoneqrystats,
ns_statscounter_max));
RETERR(dns_rdatatypestats_create(mctx,
&rcvquerystats));
RETERR(dns_rdatatypestats_create(mctx, &rcvquerystats));
RETERR(dns_dnssecsignstats_create(mctx, &dnssecsignstats));
RETERR(dns_dnssecsignstats_create(mctx, &dnssecrefreshstats));
}
dns_zone_setrequeststats(zone, zoneqrystats);
dns_zone_setrcvquerystats(zone, rcvquerystats);
dns_zone_setdnssecsignstats(zone, dnssecsignstats);
dns_zone_setdnssecrefreshstats(zone, dnssecrefreshstats);
if (zoneqrystats != NULL)
isc_stats_detach(&zoneqrystats);
......@@ -1203,6 +1210,14 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
if(rcvquerystats != NULL)
dns_stats_detach(&rcvquerystats);
if(dnssecsignstats != NULL) {
dns_stats_detach(&dnssecsignstats);
}
if(dnssecrefreshstats != NULL) {
dns_stats_detach(&dnssecrefreshstats);
}
/*
* Configure master functionality. This applies
* to primary masters (type "master") and slaves
......
......@@ -9,14 +9,18 @@
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
rm -f traffic traffic.out.*
rm -f traffic traffic.out.* traffic.json.* traffic.xml.*
rm -f zones zones.out.* zones.json.* zones.xml.* zones.expect.*
rm -f dig.out*
rm -f */named.memstats
rm -f */named.conf
rm -f */named.run
rm -f */named.run*
rm -f ns*/named.lock
rm -f ns*/named.stats
rm -f xml.*stats json.*stats
rm -f xml.*mem json.*mem
rm -f compressed.headers regular.headers compressed.out regular.out
rm -f ns*/managed-keys.bind*
rm -f ns2/Kdnssec* ns2/dnssec.*.id
rm -f ns2/dnssec.db.signed* ns2/dsset-dnssec.
rm -f ns2/core
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; 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 http://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
......@@ -39,3 +39,13 @@ zone "example" {
file "example.db";
allow-transfer { any; };
};
zone "dnssec" {
type master;
file "dnssec.db.signed";
auto-dnssec maintain;
allow-update { any; };
zone-statistics full;
dnssec-dnskey-kskonly yes;
update-check-ksk yes;
};
#!/bin/sh -e
#
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# 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 http://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" -S -x -O full -e "now"+1s -o "$zone" -f "$zonefile" "$infile" > /dev/null 2>&1
echo "$ksk" | sed -e 's/.*[+]//' -e 's/^0*//' > dnssec.ksk.id
echo "$zsk" | sed -e 's/.*[+]//' -e 's/^0*//' > dnssec.zsk.id
......@@ -9,9 +9,14 @@
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
SYSTEMTESTTOP=..
. $SYSTEMTESTTOP/conf.sh
# shellcheck source=conf.sh
. "$SYSTEMTESTTOP/conf.sh"
$SHELL clean.sh
copy_setports ns2/named.conf.in ns2/named.conf
(
cd ns2
$SHELL sign.sh
)
......@@ -9,8 +9,8 @@
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
SYSTEMTESTTOP=..
. $SYSTEMTESTTOP/conf.sh
# 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"
......@@ -54,12 +54,48 @@ gettraffic() {
*) return 1 ;;
esac
file=`$PERL fetch.pl -p ${EXTRAPORT1} $path`
cp $file $file.$1.$2
$PERL traffic-${1}.pl $file 2>/dev/null | sort > traffic.out.$2
result=$?
rm -f $file
return $result
}
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.$2
$PERL zones-${1}.pl $file 2>/dev/null | sort > zones.out.$2
result=$?
return $result
}
# TODO: Move wait_for_log and loadkeys_on to conf.sh.common
wait_for_log() {
msg=$1
file=$2
for i in 1 2 3 4 5 6 7 8 9 10; do
nextpart "$file" | grep "$msg" > /dev/null && return
sleep 1
done
echo_i "exceeded time limit waiting for '$msg' in $file"
ret=1
}
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 "next key event" ns${nsidx}/named.run
}
status=0
n=1
ret=0
......@@ -243,5 +279,101 @@ 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`
# 1. 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.
# However, the DNSSEC maintenance assumes when we see the SOA record we have
# walked the whole zone, since the SOA record should always have the most
# recent signature. This however is not always the case, for example when
# the signature expiration is the same, `dns_db_getsigningtime could return
# the SOA RRset before a competing RRset. This happens here and so the
# SOA RRset is updated and resigned twice at startup, that explains the
# additional zsk sign operation (11 instead of 10).
echo "${refresh_prefix} ${zsk_id}: 11" > zones.expect
echo "${refresh_prefix} ${ksk_id}: 1" >> zones.expect
echo "${sign_prefix} ${zsk_id}: 11" >> 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 stats data after zone maintenance at startup ($n)"
if [ $PERL_XML ]; then
getzones xml x$n || ret=1
cmp zones.out.x$n zones.expect.$n || ret=1
fi
if [ $PERL_JSON ]; then
getzones json 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`
# 2. 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}: 11" > zones.expect
echo "${refresh_prefix} ${ksk_id}: 1" >> zones.expect
echo "${sign_prefix} ${zsk_id}: 14" >> 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 stats data after dynamic update ($n)"
if [ $PERL_XML ]; then
getzones xml x$n || ret=1
cmp zones.out.x$n zones.expect.$n || ret=1
fi
if [ $PERL_JSON ]; then
getzones json 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`
# 3. Test sign operations of KSK.
ret=0
echo_i "fetch 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}: 12" > zones.expect
echo "${refresh_prefix} ${ksk_id}: 2" >> zones.expect
echo "${sign_prefix} ${zsk_id}: 15" >> 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 x$n || ret=1
cmp zones.out.x$n zones.expect.$n || ret=1
fi
if [ $PERL_JSON ]; then
getzones json 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
#!/usr/bin/perl
#
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# 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 http://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];
open(INPUT, "<$file");
my $text = do{local$/;<INPUT>};
close(INPUT);
my $ref = decode_json($text);
my $dnssecsign = $ref->{views}->{_default}->{zones}[0]->{"dnssec-sign"};
my $type = "dnssec-sign operations ";
foreach $key (keys %{$dnssecsign}) {
print $type . $key . ": ". $dnssecsign->{$key} ."\n";
}
my $dnssecrefresh = $ref->{views}->{_default}->{zones}[0]->{"dnssec-refresh"};
my $type = "dnssec-refresh operations ";
foreach $key (keys %{$dnssecrefresh}) {
print $type . $key . ": ". $dnssecrefresh->{$key} ."\n";
}
#!/usr/bin/perl
#
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# 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 http://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 $ref = XMLin($file);
my $counters = $ref->{views}->{view}->{_default}->{zones}->{zone}->{dnssec}->{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";
}
}
}
}
......@@ -5425,6 +5425,8 @@ options {
<command>zone-statistics terse</command> or
<command>zone-statistics none</command>
in the <command>zone</command> statement).
These include, for example, DNSSEC signing operations
and the number of authoritative answers per query type.
The default is <userinput>terse</userinput>, providing
minimal statistics on zones (including name and
current serial number, but not query type
......