1#!/bin/sh
2
3# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
4#
5# SPDX-License-Identifier: MPL-2.0
6#
7# This Source Code Form is subject to the terms of the Mozilla Public
8# License, v. 2.0.  If a copy of the MPL was not distributed with this
9# file, you can obtain one at https://mozilla.org/MPL/2.0/.
10#
11# See the COPYRIGHT file distributed with this work for additional
12# information regarding copyright ownership.
13
14SYSTEMTESTTOP=..
15# shellcheck source=conf.sh
16. "$SYSTEMTESTTOP/conf.sh"
17
18DIGCMD="$DIG @10.53.0.2 -p ${PORT}"
19RNDCCMD="$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p ${CONTROLPORT} -s"
20
21if [ ! "$HAVEJSONSTATS" ]
22then
23    unset PERL_JSON
24    echo_i "JSON was not configured; skipping" >&2
25elif $PERL -e 'use JSON;' 2>/dev/null
26then
27    PERL_JSON=1
28else
29    unset PERL_JSON
30    echo_i "JSON tests require JSON library; skipping" >&2
31fi
32
33if [ ! "$HAVEXMLSTATS" ]
34then
35    unset PERL_XML
36    echo_i "XML was not configured; skipping" >&2
37elif $PERL -e 'use XML::Simple;' 2>/dev/null
38then
39    PERL_XML=1
40else
41    unset PERL_XML
42    echo_i "XML tests require XML::Simple; skipping" >&2
43fi
44
45if [ ! "$PERL_JSON" -a ! "$PERL_XML" ]; then
46    echo_i "skipping all tests"
47    exit 0
48fi
49
50
51getzones() {
52    sleep 1
53    echo_i "... using $1"
54    case $1 in
55        xml) path='xml/v3/zones' ;;
56        json) path='json/v1/zones' ;;
57        *) return 1 ;;
58    esac
59    file=`$PERL fetch.pl -p ${EXTRAPORT1} $path`
60    cp $file $file.$1.$3
61    $PERL zones-${1}.pl $file $2 2>/dev/null | sort > zones.out.$3
62    result=$?
63    return $result
64}
65
66# TODO: Move loadkeys_on to conf.sh.common
67loadkeys_on() {
68    nsidx=$1
69    zone=$2
70    nextpart ns${nsidx}/named.run > /dev/null
71    $RNDCCMD 10.53.0.${nsidx} loadkeys ${zone} | sed "s/^/ns${nsidx} /" | cat_i
72    wait_for_log 20 "next key event" ns${nsidx}/named.run
73}
74
75status=0
76n=1
77ret=0
78echo_i "checking consistency between named.stats and xml/json ($n)"
79rm -f ns2/named.stats
80$DIGCMD +tcp example ns > dig.out.$n || ret=1
81$RNDCCMD 10.53.0.2 stats 2>&1 | sed 's/^/I:ns1 /'
82query_count=`awk '/QUERY/ {print $1}' ns2/named.stats`
83txt_count=`awk '/TXT/ {print $1}' ns2/named.stats`
84noerror_count=`awk '/NOERROR/ {print $1}' ns2/named.stats`
85if [ $PERL_XML ]; then
86    file=`$PERL fetch.pl -p ${EXTRAPORT1} xml/v3/server`
87    mv $file xml.stats
88    $PERL server-xml.pl > xml.fmtstats 2> /dev/null
89    xml_query_count=`awk '/opcode QUERY/ { print $NF }' xml.fmtstats`
90    xml_query_count=${xml_query_count:-0}
91    [ "$query_count" -eq "$xml_query_count" ] || ret=1
92    xml_txt_count=`awk '/qtype TXT/ { print $NF }' xml.fmtstats`
93    xml_txt_count=${xml_txt_count:-0}
94    [ "$txt_count" -eq "$xml_txt_count" ] || ret=1
95    xml_noerror_count=`awk '/rcode NOERROR/ { print $NF }' xml.fmtstats`
96    xml_noerror_count=${xml_noerror_count:-0}
97    [ "$noerror_count" -eq "$xml_noerror_count" ] || ret=1
98fi
99if [ $PERL_JSON ]; then
100    file=`$PERL fetch.pl -p ${EXTRAPORT1} json/v1/server`
101    mv $file json.stats
102    $PERL server-json.pl > json.fmtstats 2> /dev/null
103    json_query_count=`awk '/opcode QUERY/ { print $NF }' json.fmtstats`
104    json_query_count=${json_query_count:-0}
105    [ "$query_count" -eq "$json_query_count" ] || ret=1
106    json_txt_count=`awk '/qtype TXT/ { print $NF }' json.fmtstats`
107    json_txt_count=${json_txt_count:-0}
108    [ "$txt_count" -eq "$json_txt_count" ] || ret=1
109    json_noerror_count=`awk '/rcode NOERROR/ { print $NF }' json.fmtstats`
110    json_noerror_count=${json_noerror_count:-0}
111    [ "$noerror_count" -eq "$json_noerror_count" ] || ret=1
112fi
113if [ $ret != 0 ]; then echo_i "failed"; fi
114status=`expr $status + $ret`
115n=`expr $n + 1`
116
117ret=0
118echo_i "checking malloced memory statistics xml/json ($n)"
119if [ $PERL_XML ]; then
120    file=`$PERL fetch.pl -p ${EXTRAPORT1} xml/v3/mem`
121    mv $file xml.mem
122    $PERL mem-xml.pl $file > xml.fmtmem
123    grep "'Malloced' => '[0-9][0-9]*'" xml.fmtmem > /dev/null || ret=1
124    grep "'malloced' => '[0-9][0-9]*'" xml.fmtmem > /dev/null || ret=1
125    grep "'maxmalloced' => '[0-9][0-9]*'" xml.fmtmem > /dev/null || ret=1
126fi
127if [ $PERL_JSON ]; then
128    file=`$PERL fetch.pl -p ${EXTRAPORT1} json/v1/mem`
129    mv $file json.mem
130    grep '"malloced":[0-9][0-9]*,' json.mem > /dev/null || ret=1
131    grep '"maxmalloced":[0-9][0-9]*,' json.mem > /dev/null || ret=1
132    grep '"Malloced":[0-9][0-9]*,' json.mem > /dev/null || ret=1
133fi
134if [ $ret != 0 ]; then echo_i "failed"; fi
135status=`expr $status + $ret`
136n=`expr $n + 1`
137
138echo_i "checking consistency between regular and compressed output ($n)"
139for i in 1 2 3 4 5; do
140	ret=0
141	if [ "$HAVEXMLSTATS" ];
142	then
143		URL=http://10.53.0.2:${EXTRAPORT1}/xml/v3/server
144		filter_str='s#<current-time>.*</current-time>##g'
145	else
146		URL=http://10.53.0.2:${EXTRAPORT1}/json/v1/server
147		filter_str='s#"current-time.*",##g'
148	fi
149	$CURL -D regular.headers $URL 2>/dev/null | \
150		sed -e "$filter_str" > regular.out
151	$CURL -D compressed.headers --compressed $URL 2>/dev/null | \
152		sed -e "$filter_str" > compressed.out
153	diff regular.out compressed.out >/dev/null || ret=1
154	if [ $ret != 0 ]; then
155		echo_i "failed on try $i, probably a timing issue, trying again"
156		sleep 1
157	else
158		break
159	fi
160done
161
162status=`expr $status + $ret`
163n=`expr $n + 1`
164
165ret=0
166echo_i "checking if compressed output is really compressed ($n)"
167if [ "$HAVEZLIB" ];
168then
169    REGSIZE=`cat regular.headers | \
170	grep -i Content-Length | sed -e "s/.*: \([0-9]*\).*/\1/"`
171    COMPSIZE=`cat compressed.headers | \
172	grep -i Content-Length | sed -e "s/.*: \([0-9]*\).*/\1/"`
173    if [ ! `expr $REGSIZE / $COMPSIZE` -gt 2 ]; then
174	ret=1
175    fi
176else
177    echo_i "skipped"
178fi
179if [ $ret != 0 ]; then echo_i "failed"; fi
180status=`expr $status + $ret`
181n=`expr $n + 1`
182
183# Test dnssec sign statistics.
184zone="dnssec"
185sign_prefix="dnssec-sign operations"
186refresh_prefix="dnssec-refresh operations"
187ksk_id=`cat ns2/$zone.ksk.id`
188zsk_id=`cat ns2/$zone.zsk.id`
189
190# Test sign operations for scheduled resigning.
191ret=0
192# The dnssec zone has 10 RRsets to sign (including NSEC) with the ZSK and one
193# RRset (DNSKEY) with the KSK. So starting named with signatures that expire
194# almost right away, this should trigger 10 zsk and 1 ksk sign operations.
195echo "${refresh_prefix} ${zsk_id}: 10" > zones.expect
196echo "${refresh_prefix} ${ksk_id}: 1" >> zones.expect
197echo "${sign_prefix} ${zsk_id}: 10" >> zones.expect
198echo "${sign_prefix} ${ksk_id}: 1" >> zones.expect
199cat zones.expect | sort > zones.expect.$n
200rm -f zones.expect
201# Fetch and check the dnssec sign statistics.
202echo_i "fetching zone '$zone' stats data after zone maintenance at startup ($n)"
203if [ $PERL_XML ]; then
204    getzones xml $zone x$n || ret=1
205    cmp zones.out.x$n zones.expect.$n || ret=1
206fi
207if [ $PERL_JSON ]; then
208    getzones json 0 j$n || ret=1
209    cmp zones.out.j$n zones.expect.$n || ret=1
210fi
211if [ $ret != 0 ]; then echo_i "failed"; fi
212status=`expr $status + $ret`
213n=`expr $n + 1`
214
215# Test sign operations after dynamic update.
216ret=0
217(
218# Update dnssec zone to trigger signature creation.
219echo zone $zone
220echo server 10.53.0.2 "$PORT"
221echo update add $zone. 300 in txt "nsupdate added me"
222echo send
223) | $NSUPDATE
224# This should trigger the resign of SOA, TXT and NSEC (+3 zsk).
225echo "${refresh_prefix} ${zsk_id}: 10" > zones.expect
226echo "${refresh_prefix} ${ksk_id}: 1" >> zones.expect
227echo "${sign_prefix} ${zsk_id}: 13" >> zones.expect
228echo "${sign_prefix} ${ksk_id}: 1" >> zones.expect
229cat zones.expect | sort > zones.expect.$n
230rm -f zones.expect
231# Fetch and check the dnssec sign statistics.
232echo_i "fetching zone '$zone' stats data after dynamic update ($n)"
233if [ $PERL_XML ]; then
234    getzones xml $zone x$n || ret=1
235    cmp zones.out.x$n zones.expect.$n || ret=1
236fi
237if [ $PERL_JSON ]; then
238    getzones json 0 j$n || ret=1
239    cmp zones.out.j$n zones.expect.$n || ret=1
240fi
241if [ $ret != 0 ]; then echo_i "failed"; fi
242status=`expr $status + $ret`
243n=`expr $n + 1`
244
245# Test sign operations of KSK.
246ret=0
247echo_i "fetch zone '$zone' stats data after updating DNSKEY RRset ($n)"
248# Add a standby DNSKEY, this triggers resigning the DNSKEY RRset.
249zsk=$("$KEYGEN" -K ns2 -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "$zone")
250$SETTIME -K ns2 -P now -A never $zsk.key > /dev/null
251loadkeys_on 2 $zone || ret=1
252# This should trigger the resign of SOA (+1 zsk) and DNSKEY (+1 ksk).
253echo "${refresh_prefix} ${zsk_id}: 11" > zones.expect
254echo "${refresh_prefix} ${ksk_id}: 2" >> zones.expect
255echo "${sign_prefix} ${zsk_id}: 14" >> zones.expect
256echo "${sign_prefix} ${ksk_id}: 2" >> zones.expect
257cat zones.expect | sort > zones.expect.$n
258rm -f zones.expect
259# Fetch and check the dnssec sign statistics.
260if [ $PERL_XML ]; then
261    getzones xml $zone x$n || ret=1
262    cmp zones.out.x$n zones.expect.$n || ret=1
263fi
264if [ $PERL_JSON ]; then
265    getzones json 0 j$n || ret=1
266    cmp zones.out.j$n zones.expect.$n || ret=1
267fi
268if [ $ret != 0 ]; then echo_i "failed"; fi
269status=`expr $status + $ret`
270n=`expr $n + 1`
271
272# Test sign operations for scheduled resigning (many keys).
273ret=0
274zone="manykeys"
275ksk8_id=`cat ns2/$zone.ksk8.id`
276zsk8_id=`cat ns2/$zone.zsk8.id`
277ksk13_id=`cat ns2/$zone.ksk13.id`
278zsk13_id=`cat ns2/$zone.zsk13.id`
279ksk14_id=`cat ns2/$zone.ksk14.id`
280zsk14_id=`cat ns2/$zone.zsk14.id`
281# The dnssec zone has 10 RRsets to sign (including NSEC) with the ZSKs and one
282# RRset (DNSKEY) with the KSKs. So starting named with signatures that expire
283# almost right away, this should trigger 10 zsk and 1 ksk sign operations per
284# key.
285echo "${refresh_prefix} ${zsk8_id}: 10" > zones.expect
286echo "${refresh_prefix} ${zsk13_id}: 10" >> zones.expect
287echo "${refresh_prefix} ${zsk14_id}: 10" >> zones.expect
288echo "${refresh_prefix} ${ksk8_id}: 1" >> zones.expect
289echo "${refresh_prefix} ${ksk13_id}: 1" >> zones.expect
290echo "${refresh_prefix} ${ksk14_id}: 1" >> zones.expect
291echo "${sign_prefix} ${zsk8_id}: 10" >> zones.expect
292echo "${sign_prefix} ${zsk13_id}: 10" >> zones.expect
293echo "${sign_prefix} ${zsk14_id}: 10" >> zones.expect
294echo "${sign_prefix} ${ksk8_id}: 1" >> zones.expect
295echo "${sign_prefix} ${ksk13_id}: 1" >> zones.expect
296echo "${sign_prefix} ${ksk14_id}: 1" >> zones.expect
297cat zones.expect | sort > zones.expect.$n
298rm -f zones.expect
299# Fetch and check the dnssec sign statistics.
300echo_i "fetching zone '$zone' stats data after zone maintenance at startup ($n)"
301if [ $PERL_XML ]; then
302    getzones xml $zone x$n || ret=1
303    cmp zones.out.x$n zones.expect.$n || ret=1
304fi
305if [ $PERL_JSON ]; then
306    getzones json 2 j$n || ret=1
307    cmp zones.out.j$n zones.expect.$n || ret=1
308fi
309if [ $ret != 0 ]; then echo_i "failed"; fi
310status=`expr $status + $ret`
311n=`expr $n + 1`
312
313# Test sign operations after dynamic update (many keys).
314ret=0
315(
316# Update dnssec zone to trigger signature creation.
317echo zone $zone
318echo server 10.53.0.2 "$PORT"
319echo update add $zone. 300 in txt "nsupdate added me"
320echo send
321) | $NSUPDATE
322# This should trigger the resign of SOA, TXT and NSEC (+3 zsk).
323echo "${refresh_prefix} ${zsk8_id}: 10" > zones.expect
324echo "${refresh_prefix} ${zsk13_id}: 10" >> zones.expect
325echo "${refresh_prefix} ${zsk14_id}: 10" >> zones.expect
326echo "${refresh_prefix} ${ksk8_id}: 1" >> zones.expect
327echo "${refresh_prefix} ${ksk13_id}: 1" >> zones.expect
328echo "${refresh_prefix} ${ksk14_id}: 1" >> zones.expect
329echo "${sign_prefix} ${zsk8_id}: 13" >> zones.expect
330echo "${sign_prefix} ${zsk13_id}: 13" >> zones.expect
331echo "${sign_prefix} ${zsk14_id}: 13" >> zones.expect
332echo "${sign_prefix} ${ksk8_id}: 1" >> zones.expect
333echo "${sign_prefix} ${ksk13_id}: 1" >> zones.expect
334echo "${sign_prefix} ${ksk14_id}: 1" >> zones.expect
335cat zones.expect | sort > zones.expect.$n
336rm -f zones.expect
337# Fetch and check the dnssec sign statistics.
338echo_i "fetching zone '$zone' stats data after dynamic update ($n)"
339if [ $PERL_XML ]; then
340    getzones xml $zone x$n || ret=1
341    cmp zones.out.x$n zones.expect.$n || ret=1
342fi
343if [ $PERL_JSON ]; then
344    getzones json 2 j$n || ret=1
345    cmp zones.out.j$n zones.expect.$n || ret=1
346fi
347if [ $ret != 0 ]; then echo_i "failed"; fi
348status=`expr $status + $ret`
349n=`expr $n + 1`
350
351# Test sign operations after dnssec-policy change (removing keys).
352ret=0
353copy_setports ns2/named2.conf.in ns2/named.conf
354$RNDCCMD 10.53.0.2 reload 2>&1 | sed 's/^/I:ns2 /'
355# This should trigger the resign of DNSKEY (+1 ksk), and SOA, NSEC,
356# TYPE65534 (+3 zsk). The dnssec-sign statistics for the removed keys should
357# be cleared and thus no longer visible. But NSEC and SOA are (mistakenly)
358# counted double, one time because of zone_resigninc and one time because of
359# zone_nsec3chain. So +5 zsk in total.
360echo "${refresh_prefix} ${zsk8_id}: 15" > zones.expect
361echo "${refresh_prefix} ${ksk8_id}: 2" >> zones.expect
362echo "${sign_prefix} ${zsk8_id}: 18" >> zones.expect
363echo "${sign_prefix} ${ksk8_id}: 2" >> zones.expect
364cat zones.expect | sort > zones.expect.$n
365rm -f zones.expect
366# Fetch and check the dnssec sign statistics.
367echo_i "fetching zone '$zone' stats data after dnssec-policy change ($n)"
368if [ $PERL_XML ]; then
369    getzones xml $zone x$n || ret=1
370    cmp zones.out.x$n zones.expect.$n || ret=1
371fi
372if [ $PERL_JSON ]; then
373    getzones json 2 j$n || ret=1
374    cmp zones.out.j$n zones.expect.$n || ret=1
375fi
376if [ $ret != 0 ]; then echo_i "failed"; fi
377status=`expr $status + $ret`
378n=`expr $n + 1`
379
380echo_i "exit status: $status"
381[ $status -eq 0 ] || exit 1
382