xref: /netbsd-src/external/mpl/bind/dist/bin/tests/system/statschannel/tests.sh (revision 9689912e6b171cbda866ec33f15ae94a04e2c02d)
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
14set -e
15
16# shellcheck source=conf.sh
17. ../conf.sh
18
19DIGCMD="$DIG @10.53.0.2 -p ${PORT}"
20RNDCCMD="$RNDC -c ../_common/rndc.conf -p ${CONTROLPORT} -s"
21NS_PARAMS="-m record -c named.conf -d 99 -g -T maxcachesize=2097152"
22
23if ! $FEATURETEST --have-json-c; then
24  unset PERL_JSON
25  echo_i "JSON was not configured; skipping" >&2
26elif $PERL -e 'use JSON;' 2>/dev/null; then
27  PERL_JSON=1
28else
29  unset PERL_JSON
30  echo_i "JSON tests require JSON library; skipping" >&2
31fi
32
33if ! $FEATURETEST --have-libxml2; then
34  unset PERL_XML
35  echo_i "XML was not configured; skipping" >&2
36elif $PERL -e 'use XML::Simple;' 2>/dev/null; then
37  PERL_XML=1
38else
39  unset PERL_XML
40  echo_i "XML tests require XML::Simple; skipping" >&2
41fi
42
43if [ ! "$PERL_JSON" ] && [ ! "$PERL_XML" ]; then
44  echo_i "skipping all tests"
45  exit 0
46fi
47
48retry_quiet_fast() {
49  __retries="${1}"
50  shift
51
52  while :; do
53    if "$@"; then
54      return 0
55    fi
56    __retries=$((__retries - 1))
57    if [ "${__retries}" -gt 0 ]; then
58      # sleep for 0.1 seconds
59      perl -e 'select(undef, undef, undef, .1)'
60    else
61      return 1
62    fi
63  done
64}
65
66wait_for_log_fast() (
67  timeout="$1"
68  msg="$2"
69  file="$3"
70  retry_quiet_fast "$timeout" _search_log "$msg" "$file" && return 0
71  echo_i "exceeded time limit waiting for literal '$msg' in $file"
72  return 1
73)
74
75getzones() {
76  sleep 1
77  echo_i "... using $1"
78  case $1 in
79    xml) path='xml/v3/zones' ;;
80    json) path='json/v1/zones' ;;
81    *) return 1 ;;
82  esac
83  file=$($PERL fetch.pl -p ${EXTRAPORT1} $path)
84  cp $file $file.$1.$3
85  {
86    $PERL zones-${1}.pl $file $2 2>/dev/null | sort >zones.out.$3
87    result=$?
88  } || true
89  return $result
90}
91
92getxfrins() {
93  echo_i "... using $1"
94  case $1 in
95    xml) path='xml/v3/xfrins' ;;
96    json) path='json/v1/xfrins' ;;
97    *) return 1 ;;
98  esac
99  file=$($PERL fetch.pl -s 10.53.0.3 -p ${EXTRAPORT1} $path)
100  cp $file $file.$1.$2
101  result=$?
102  return $result
103}
104
105# TODO: Move loadkeys_on to conf.sh.common
106loadkeys_on() {
107  nsidx=$1
108  zone=$2
109  nextpart ns${nsidx}/named.run >/dev/null
110  $RNDCCMD 10.53.0.${nsidx} loadkeys ${zone} | sed "s/^/ns${nsidx} /" | cat_i
111  wait_for_log 20 "next key event" ns${nsidx}/named.run
112}
113
114# verify that the http server dropped the connection without replying
115check_http_dropped() {
116  if [ -x "${NC}" ]; then
117    "${NC}" 10.53.0.3 "${EXTRAPORT1}" >nc.out$n || ret=1
118    if test -s nc.out$n; then
119      ret=1
120    fi
121  else
122    echo_i "skipping test as nc not found"
123  fi
124}
125
126status=0
127n=1
128
129echo_i "check content-length parse error ($n)"
130ret=0
131check_http_dropped <<EOF
132POST /xml/v3/status HTTP/1.0
133Content-Length: nah
134
135EOF
136if [ $ret != 0 ]; then echo_i "failed"; fi
137status=$((status + ret))
138n=$((n + 1))
139
140echo_i "check negative content-length ($n)"
141ret=0
142check_http_dropped <<EOF
143POST /xml/v3/status HTTP/1.0
144Content-Length: -50
145
146EOF
147if [ $ret != 0 ]; then echo_i "failed"; fi
148status=$((status + ret))
149n=$((n + 1))
150
151echo_i "check content-length 32-bit overflow ($n)"
152check_http_dropped <<EOF
153POST /xml/v3/status HTTP/1.0
154Content-Length: 4294967239
155
156EOF
157if [ $ret != 0 ]; then echo_i "failed"; fi
158status=$((status + ret))
159n=$((n + 1))
160
161echo_i "check content-length 64-bit overflow ($n)"
162check_http_dropped <<EOF
163POST /xml/v3/status HTTP/1.0
164Content-Length: 18446744073709551549
165
166EOF
167
168if [ $ret != 0 ]; then echo_i "failed"; fi
169status=$((status + ret))
170n=$((n + 1))
171
172echo_i "Prepare for if-modified-since test ($n)"
173ret=0
174i=0
175if $FEATURETEST --have-libxml2 && [ -x "${CURL}" ]; then
176  URL="http://10.53.0.3:${EXTRAPORT1}/bind9.xsl"
177  ${CURL} --silent --show-error --fail --output bind9.xsl.1 $URL
178  ret=$?
179else
180  echo_i "skipping test: requires libxml2 and curl"
181fi
182if [ $ret != 0 ]; then echo_i "failed"; fi
183status=$((status + ret))
184n=$((n + 1))
185
186echo_i "checking consistency between named.stats and xml/json ($n)"
187ret=0
188rm -f ns2/named.stats
189$DIGCMD +tcp example ns >dig.out.$n || ret=1
190$RNDCCMD 10.53.0.2 stats 2>&1 | sed 's/^/I:ns1 /'
191query_count=$(awk '/QUERY/ {print $1}' ns2/named.stats)
192txt_count=$(awk '/TXT/ {print $1}' ns2/named.stats)
193noerror_count=$(awk '/NOERROR/ {print $1}' ns2/named.stats)
194if [ "$PERL_XML" ]; then
195  file=$($PERL fetch.pl -p ${EXTRAPORT1} xml/v3/server)
196  mv $file xml.stats
197  $PERL server-xml.pl >xml.fmtstats 2>/dev/null
198  xml_query_count=$(awk '/opcode QUERY/ { print $NF }' xml.fmtstats)
199  xml_query_count=${xml_query_count:-0}
200  [ "$query_count" -eq "$xml_query_count" ] || ret=1
201  xml_txt_count=$(awk '/qtype TXT/ { print $NF }' xml.fmtstats)
202  xml_txt_count=${xml_txt_count:-0}
203  [ "$txt_count" -eq "$xml_txt_count" ] || ret=1
204  xml_noerror_count=$(awk '/rcode NOERROR/ { print $NF }' xml.fmtstats)
205  xml_noerror_count=${xml_noerror_count:-0}
206  [ "$noerror_count" -eq "$xml_noerror_count" ] || ret=1
207fi
208if [ "$PERL_JSON" ]; then
209  file=$($PERL fetch.pl -p ${EXTRAPORT1} json/v1/server)
210  mv $file json.stats
211  $PERL server-json.pl >json.fmtstats 2>/dev/null
212  json_query_count=$(awk '/opcode QUERY/ { print $NF }' json.fmtstats)
213  json_query_count=${json_query_count:-0}
214  [ "$query_count" -eq "$json_query_count" ] || ret=1
215  json_txt_count=$(awk '/qtype TXT/ { print $NF }' json.fmtstats)
216  json_txt_count=${json_txt_count:-0}
217  [ "$txt_count" -eq "$json_txt_count" ] || ret=1
218  json_noerror_count=$(awk '/rcode NOERROR/ { print $NF }' json.fmtstats)
219  json_noerror_count=${json_noerror_count:-0}
220  [ "$noerror_count" -eq "$json_noerror_count" ] || ret=1
221fi
222if [ $ret != 0 ]; then echo_i "failed"; fi
223status=$((status + ret))
224n=$((n + 1))
225
226ret=0
227echo_i "checking malloced memory statistics xml/json ($n)"
228if [ "$PERL_XML" ]; then
229  file=$($PERL fetch.pl -p ${EXTRAPORT1} xml/v3/mem)
230  mv $file xml.mem
231  $PERL mem-xml.pl $file >xml.fmtmem
232  grep "'InUse' => '[0-9][0-9]*'" xml.fmtmem >/dev/null || ret=1
233  grep "'inuse' => '[0-9][0-9]*'" xml.fmtmem >/dev/null || ret=1
234fi
235if [ "$PERL_JSON" ]; then
236  file=$($PERL fetch.pl -p ${EXTRAPORT1} json/v1/mem)
237  mv $file json.mem
238  grep '"inuse":[0-9][0-9]*,' json.mem >/dev/null || ret=1
239  grep '"InUse":[0-9][0-9]*,' json.mem >/dev/null || ret=1
240fi
241if [ $ret != 0 ]; then echo_i "failed"; fi
242status=$((status + ret))
243n=$((n + 1))
244
245echo_i "checking consistency between regular and compressed output ($n)"
246ret=0
247if [ -x "${CURL}" ]; then
248  for i in 1 2 3 4 5; do
249    ret=0
250    if $FEATURETEST --have-libxml2; then
251      URL="http://10.53.0.2:${EXTRAPORT1}/xml/v3/server"
252      filter_str='s#<current-time>.*</current-time>##g'
253    else
254      URL="http://10.53.0.2:${EXTRAPORT1}/json/v1/server"
255      filter_str='s#"current-time.*",##g'
256    fi
257    "${CURL}" -D regular.headers "$URL" 2>/dev/null \
258      | sed -e "$filter_str" >regular.out || ret=1
259    "${CURL}" -D compressed.headers --compressed "$URL" 2>/dev/null \
260      | sed -e "$filter_str" >compressed.out || ret=1
261    diff regular.out compressed.out >/dev/null || ret=1
262    if [ $ret != 0 ]; then
263      echo_i "failed on try $i, probably a timing issue, trying again"
264      sleep 1
265    else
266      break
267    fi
268  done
269else
270  echo_i "skipping test as curl not found"
271fi
272status=$((status + ret))
273n=$((n + 1))
274
275ret=0
276echo_i "checking if compressed output is really compressed ($n)"
277if $FEATURETEST --with-zlib; then
278  REGSIZE=$(cat regular.headers \
279    | grep -i Content-Length | sed -e "s/.*: \([0-9]*\).*/\1/")
280  COMPSIZE=$(cat compressed.headers \
281    | grep -i Content-Length | sed -e "s/.*: \([0-9]*\).*/\1/")
282  if [ ! $((REGSIZE / COMPSIZE)) -gt 2 ]; then
283    ret=1
284  fi
285else
286  echo_i "skipped"
287fi
288if [ $ret != 0 ]; then echo_i "failed"; fi
289status=$((status + ret))
290n=$((n + 1))
291
292# Test dnssec sign statistics.
293zone="dnssec"
294sign_prefix="dnssec-sign operations"
295refresh_prefix="dnssec-refresh operations"
296ksk_id=$(cat ns2/$zone.ksk.id)
297zsk_id=$(cat ns2/$zone.zsk.id)
298
299# Test sign operations for scheduled resigning.
300ret=0
301# The dnssec zone has 10 RRsets to sign (including NSEC) with the ZSK and one
302# RRset (DNSKEY) with the KSK. So starting named with signatures that expire
303# almost right away, this should trigger 10 zsk and 1 ksk sign operations.
304echo "${refresh_prefix} ${zsk_id}: 10" >zones.expect
305echo "${refresh_prefix} ${ksk_id}: 1" >>zones.expect
306echo "${sign_prefix} ${zsk_id}: 10" >>zones.expect
307echo "${sign_prefix} ${ksk_id}: 1" >>zones.expect
308cat zones.expect | sort >zones.expect.$n
309rm -f zones.expect
310# Fetch and check the dnssec sign statistics.
311echo_i "fetching zone '$zone' stats data after zone maintenance at startup ($n)"
312if [ "$PERL_XML" ]; then
313  getzones xml $zone x$n || ret=1
314  cmp zones.out.x$n zones.expect.$n || ret=1
315fi
316if [ "$PERL_JSON" ]; then
317  getzones json 0 j$n || ret=1
318  cmp zones.out.j$n zones.expect.$n || ret=1
319fi
320if [ $ret != 0 ]; then echo_i "failed"; fi
321status=$((status + ret))
322n=$((n + 1))
323
324# Test sign operations after dynamic update.
325ret=0
326(
327  # Update dnssec zone to trigger signature creation.
328  echo zone $zone
329  echo server 10.53.0.2 "$PORT"
330  echo update add $zone. 300 in txt "nsupdate added me"
331  echo send
332) | $NSUPDATE
333# This should trigger the resign of SOA, TXT and NSEC (+3 zsk).
334echo "${refresh_prefix} ${zsk_id}: 10" >zones.expect
335echo "${refresh_prefix} ${ksk_id}: 1" >>zones.expect
336echo "${sign_prefix} ${zsk_id}: 13" >>zones.expect
337echo "${sign_prefix} ${ksk_id}: 1" >>zones.expect
338cat zones.expect | sort >zones.expect.$n
339rm -f zones.expect
340# Fetch and check the dnssec sign statistics.
341echo_i "fetching zone '$zone' stats data after dynamic update ($n)"
342if [ "$PERL_XML" ]; then
343  getzones xml $zone x$n || ret=1
344  cmp zones.out.x$n zones.expect.$n || ret=1
345fi
346if [ "$PERL_JSON" ]; then
347  getzones json 0 j$n || ret=1
348  cmp zones.out.j$n zones.expect.$n || ret=1
349fi
350if [ $ret != 0 ]; then echo_i "failed"; fi
351status=$((status + ret))
352n=$((n + 1))
353
354# Test sign operations of KSK.
355ret=0
356echo_i "fetch zone '$zone' stats data after updating DNSKEY RRset ($n)"
357id=$(echo "${zsk_id}" | cut -d+ -f2 -)
358# Add a DNSKEY, this triggers resigning the DNSKEY RRset.
359zsk=$("$KEYGEN" -L 3600 -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "$zone")
360(
361  echo zone $zone
362  echo server 10.53.0.2 "$PORT"
363  echo update add $(cat "${zsk}.key" | grep -v ";.*")
364  echo send
365) | $NSUPDATE
366# This should trigger the resign of SOA (+1 zsk) and DNSKEY (+1 ksk).
367echo "${refresh_prefix} ${zsk_id}: 10" >zones.expect
368echo "${refresh_prefix} ${ksk_id}: 1" >>zones.expect
369echo "${sign_prefix} ${zsk_id}: 14" >>zones.expect
370echo "${sign_prefix} ${ksk_id}: 2" >>zones.expect
371cat zones.expect | sort >zones.expect.$n
372rm -f zones.expect
373# Fetch and check the dnssec sign statistics.
374if [ "$PERL_XML" ]; then
375  getzones xml $zone x$n || ret=1
376  cmp zones.out.x$n zones.expect.$n || ret=1
377fi
378if [ "$PERL_JSON" ]; then
379  getzones json 0 j$n || ret=1
380  cmp zones.out.j$n zones.expect.$n || ret=1
381fi
382if [ $ret != 0 ]; then echo_i "failed"; fi
383status=$((status + ret))
384n=$((n + 1))
385
386# Test sign operations for scheduled resigning (many keys).
387ret=0
388zone="manykeys"
389ksk8_id=$(cat ns2/$zone.ksk8.id)
390zsk8_id=$(cat ns2/$zone.zsk8.id)
391ksk13_id=$(cat ns2/$zone.ksk13.id)
392zsk13_id=$(cat ns2/$zone.zsk13.id)
393ksk14_id=$(cat ns2/$zone.ksk14.id)
394zsk14_id=$(cat ns2/$zone.zsk14.id)
395# The dnssec zone has 10 RRsets to sign (including NSEC) with the ZSKs and one
396# RRset (DNSKEY) with the KSKs. So starting named with signatures that expire
397# almost right away, this should trigger 10 zsk and 1 ksk sign operations per
398# key.
399echo "${refresh_prefix} ${zsk8_id}: 10" >zones.expect
400echo "${refresh_prefix} ${zsk13_id}: 10" >>zones.expect
401echo "${refresh_prefix} ${zsk14_id}: 10" >>zones.expect
402echo "${refresh_prefix} ${ksk8_id}: 1" >>zones.expect
403echo "${refresh_prefix} ${ksk13_id}: 1" >>zones.expect
404echo "${refresh_prefix} ${ksk14_id}: 1" >>zones.expect
405echo "${sign_prefix} ${zsk8_id}: 10" >>zones.expect
406echo "${sign_prefix} ${zsk13_id}: 10" >>zones.expect
407echo "${sign_prefix} ${zsk14_id}: 10" >>zones.expect
408echo "${sign_prefix} ${ksk8_id}: 1" >>zones.expect
409echo "${sign_prefix} ${ksk13_id}: 1" >>zones.expect
410echo "${sign_prefix} ${ksk14_id}: 1" >>zones.expect
411cat zones.expect | sort >zones.expect.$n
412rm -f zones.expect
413# Fetch and check the dnssec sign statistics.
414echo_i "fetching zone '$zone' stats data after zone maintenance at startup ($n)"
415if [ "$PERL_XML" ]; then
416  getzones xml $zone x$n || ret=1
417  cmp zones.out.x$n zones.expect.$n || ret=1
418fi
419if [ "$PERL_JSON" ]; then
420  getzones json 2 j$n || ret=1
421  cmp zones.out.j$n zones.expect.$n || ret=1
422fi
423if [ $ret != 0 ]; then echo_i "failed"; fi
424status=$((status + ret))
425n=$((n + 1))
426
427# Test sign operations after dynamic update (many keys).
428ret=0
429(
430  # Update dnssec zone to trigger signature creation.
431  echo zone $zone
432  echo server 10.53.0.2 "$PORT"
433  echo update add $zone. 300 in txt "nsupdate added me"
434  echo send
435) | $NSUPDATE
436# This should trigger the resign of SOA, TXT and NSEC (+3 zsk).
437echo "${refresh_prefix} ${zsk8_id}: 10" >zones.expect
438echo "${refresh_prefix} ${zsk13_id}: 10" >>zones.expect
439echo "${refresh_prefix} ${zsk14_id}: 10" >>zones.expect
440echo "${refresh_prefix} ${ksk8_id}: 1" >>zones.expect
441echo "${refresh_prefix} ${ksk13_id}: 1" >>zones.expect
442echo "${refresh_prefix} ${ksk14_id}: 1" >>zones.expect
443echo "${sign_prefix} ${zsk8_id}: 13" >>zones.expect
444echo "${sign_prefix} ${zsk13_id}: 13" >>zones.expect
445echo "${sign_prefix} ${zsk14_id}: 13" >>zones.expect
446echo "${sign_prefix} ${ksk8_id}: 1" >>zones.expect
447echo "${sign_prefix} ${ksk13_id}: 1" >>zones.expect
448echo "${sign_prefix} ${ksk14_id}: 1" >>zones.expect
449cat zones.expect | sort >zones.expect.$n
450rm -f zones.expect
451# Fetch and check the dnssec sign statistics.
452echo_i "fetching zone '$zone' stats data after dynamic update ($n)"
453if [ "$PERL_XML" ]; then
454  getzones xml $zone x$n || ret=1
455  cmp zones.out.x$n zones.expect.$n || ret=1
456fi
457if [ "$PERL_JSON" ]; then
458  getzones json 2 j$n || ret=1
459  cmp zones.out.j$n zones.expect.$n || ret=1
460fi
461if [ $ret != 0 ]; then echo_i "failed"; fi
462status=$((status + ret))
463n=$((n + 1))
464
465# Test sign operations after dnssec-policy change (removing keys).
466ret=0
467copy_setports ns2/named2.conf.in ns2/named.conf
468$RNDCCMD 10.53.0.2 reload 2>&1 | sed 's/^/I:ns2 /'
469# This should trigger the resign of DNSKEY (+1 ksk), and SOA, NSEC,
470# TYPE65534 (+3 zsk). The dnssec-sign statistics for the removed keys should
471# be cleared and thus no longer visible. But NSEC and SOA are (mistakenly)
472# counted double, one time because of zone_resigninc and one time because of
473# zone_nsec3chain. So +5 zsk in total.
474echo "${refresh_prefix} ${zsk8_id}: 15" >zones.expect
475echo "${refresh_prefix} ${ksk8_id}: 2" >>zones.expect
476echo "${sign_prefix} ${zsk8_id}: 18" >>zones.expect
477echo "${sign_prefix} ${ksk8_id}: 2" >>zones.expect
478cat zones.expect | sort >zones.expect.$n
479rm -f zones.expect
480# Fetch and check the dnssec sign statistics.
481echo_i "fetching zone '$zone' stats data after dnssec-policy change ($n)"
482if [ "$PERL_XML" ]; then
483  getzones xml $zone x$n || ret=1
484  cmp zones.out.x$n zones.expect.$n || ret=1
485fi
486if [ "$PERL_JSON" ]; then
487  getzones json 2 j$n || ret=1
488  cmp zones.out.j$n zones.expect.$n || ret=1
489fi
490if [ $ret != 0 ]; then echo_i "failed"; fi
491status=$((status + ret))
492n=$((n + 1))
493
494echo_i "Check HTTP/1.1 client-side pipelined requests are handled (GET) ($n)"
495ret=0
496if [ -x "${NC}" ]; then
497  "${NC}" 10.53.0.3 "${EXTRAPORT1}" <<EOF >nc.out$n || ret=1
498GET /xml/v3/status HTTP/1.1
499Host: 10.53.0.3:${EXTRAPORT1}
500
501GET /xml/v3/status HTTP/1.1
502Host: 10.53.0.3:${EXTRAPORT1}
503Connection: close
504
505EOF
506  lines=$(grep -c "^<statistics version" nc.out$n)
507  test "$lines" = 2 || ret=1
508  # keep-alive not needed in HTTP/1.1, second response has close
509  lines=$(grep -c "^Connection: Keep-Alive" nc.out$n || true)
510  test "$lines" = 0 || ret=1
511  lines=$(grep -c "^Connection: close" nc.out$n)
512  test "$lines" = 1 || ret=1
513else
514  echo_i "skipping test as nc not found"
515fi
516if [ $ret != 0 ]; then echo_i "failed"; fi
517status=$((status + ret))
518n=$((n + 1))
519
520echo_i "Check HTTP/1.1 client-side pipelined requests are handled (POST) ($n)"
521ret=0
522if [ -x "${NC}" ]; then
523  "${NC}" 10.53.0.3 "${EXTRAPORT1}" <<EOF >nc.out$n || ret=1
524POST /xml/v3/status HTTP/1.1
525Host: 10.53.0.3:${EXTRAPORT1}
526Content-Type: application/json
527Content-Length: 3
528
529{}
530POST /xml/v3/status HTTP/1.1
531Host: 10.53.0.3:${EXTRAPORT1}
532Content-Type: application/json
533Content-Length: 3
534Connection: close
535
536{}
537EOF
538  lines=$(grep -c "^<statistics version" nc.out$n)
539  test "$lines" = 2 || ret=1
540  # keep-alive not needed in HTTP/1.1, second response has close
541  lines=$(grep -c "^Connection: Keep-Alive" nc.out$n || true)
542  test "$lines" = 0 || ret=1
543  lines=$(grep -c "^Connection: close" nc.out$n)
544  test "$lines" = 1 || ret=1
545else
546  echo_i "skipping test as nc not found"
547fi
548if [ $ret != 0 ]; then echo_i "failed"; fi
549status=$((status + ret))
550n=$((n + 1))
551
552echo_i "Check HTTP/1.0 keep-alive ($n)"
553ret=0
554if [ -x "${NC}" ]; then
555  "${NC}" 10.53.0.3 "${EXTRAPORT1}" <<EOF >nc.out$n || ret=1
556GET /xml/v3/status HTTP/1.0
557Connection: keep-alive
558
559GET /xml/v3/status HTTP/1.0
560
561EOF
562  # should be two responses
563  lines=$(grep -c "^<statistics version" nc.out$n)
564  test "$lines" = 2 || ret=1
565  # first response has keep-alive, second has close
566  lines=$(grep -c "^Connection: Keep-Alive" nc.out$n || true)
567  test "$lines" = 1 || ret=1
568  lines=$(grep -c "^Connection: close" nc.out$n)
569  test "$lines" = 1 || ret=1
570else
571  echo_i "skipping test as nc not found"
572fi
573if [ $ret != 0 ]; then echo_i "failed"; fi
574status=$((status + ret))
575n=$((n + 1))
576
577echo_i "Check inconsistent Connection: headers ($n)"
578ret=0
579if [ -x "${NC}" ]; then
580  "${NC}" 10.53.0.3 "${EXTRAPORT1}" <<EOF >nc.out$n || ret=1
581GET /xml/v3/status HTTP/1.0
582Connection: keep-alive
583Connection: close
584
585GET /xml/v3/status HTTP/1.0
586
587EOF
588  # should be one response (second is ignored)
589  lines=$(grep -c "^<statistics version" nc.out$n)
590  test "$lines" = 1 || ret=1
591  # no keep-alive, one close
592  lines=$(grep -c "^Connection: Keep-Alive" nc.out$n || true)
593  test "$lines" = 0 || ret=1
594  lines=$(grep -c "^Connection: close" nc.out$n)
595  test "$lines" = 1 || ret=1
596else
597  echo_i "skipping test as nc not found"
598fi
599if [ $ret != 0 ]; then echo_i "failed"; fi
600status=$((status + ret))
601n=$((n + 1))
602
603if [ -x "${CURL}" ] && ! ("${CURL}" --next 2>&1 | grep 'option --next: is unknown'); then
604  CURL_NEXT="${CURL}"
605fi
606
607echo_i "Check HTTP with more than 100 headers ($n)"
608ret=0
609i=0
610if [ -x "${CURL_NEXT}" ]; then
611  # build input stream.
612  : >header.in$n
613  while test $i -lt 101; do
614    printf 'X-Bloat%d: VGhlIG1vc3QgY29tbW9uIHJlYXNvbiBmb3IgYmxvYXRpbmcgaXMgaGF2aW5nIGEgbG90IG9mIGdhcyBpbiB5b3VyIGd1dC4gCg==\r\n' $i >>header.in$n
615    i=$((i + 1))
616  done
617  printf '\r\n' >>header.in$n
618
619  # send the requests then wait for named to close the socket.
620  URL="http://10.53.0.3:${EXTRAPORT1}/xml/v3/status"
621  "${CURL}" --silent --include --get "$URL" --next --get --header @header.in$n "$URL" >curl.out$n && ret=1
622  # we expect 1 request to be processed.
623  lines=$(grep -c "^<statistics version" curl.out$n)
624  test "$lines" = 1 || ret=1
625else
626  echo_i "skipping test as curl with --next support not found"
627fi
628if [ $ret != 0 ]; then echo_i "failed"; fi
629status=$((status + ret))
630n=$((n + 1))
631
632echo_i "Check HTTP/1.1 keep-alive with truncated stream ($n)"
633ret=0
634i=0
635if [ -x "${CURL_NEXT}" ]; then
636  # build input stream.
637  printf 'X-Bloat: ' >header.in$n
638  while test $i -lt 5000; do
639    printf '%s' "VGhlIG1vc3QgY29tbW9uIHJlYXNvbiBmb3IgYmxvYXRpbmcgaXMgaGF2aW5nIGEgbG90IG9mIGdhcyBpbiB5b3VyIGd1dC4gCg==" >>header.in$n
640    i=$((i + 1))
641  done
642  printf '\r\n' >>header.in$n
643
644  # send the requests then wait for named to close the socket.
645  URL="http://10.53.0.3:${EXTRAPORT1}/xml/v3/status"
646  "${CURL}" --silent --include --get "$URL" --next --get --header @header.in$n "$URL" >curl.out$n && ret=1
647  # we expect 1 request to be processed.
648  lines=$(grep -c "^<statistics version" curl.out$n)
649  test "$lines" = 1 || ret=1
650else
651  echo_i "skipping test as curl with --next support not found"
652fi
653if [ $ret != 0 ]; then echo_i "failed"; fi
654status=$((status + ret))
655n=$((n + 1))
656
657echo_i "Check that consequtive responses do not grow excessively ($n)"
658ret=0
659i=0
660if [ -x "${CURL}" ]; then
661  URL="http://10.53.0.3:${EXTRAPORT1}/json/v1"
662  "${CURL}" --silent --include --header "Accept-Encoding: deflate, gzip, br, zstd" "$URL" "$URL" "$URL" "$URL" "$URL" "$URL" "$URL" "$URL" "$URL" "$URL" >curl.out$n || ret=1
663  grep -a Content-Length curl.out$n | awk 'BEGIN { prev=0; } { if (prev != 0 && $2 - prev > 100) { exit(1); } prev = $2; }' || ret=1
664else
665  echo_i "skipping test as curl not found"
666fi
667if [ $ret != 0 ]; then echo_i "failed"; fi
668status=$((status + ret))
669n=$((n + 1))
670
671echo_i "Check if-modified-since works ($n)"
672ret=0
673if $FEATURETEST --have-libxml2 && [ -x "${CURL}" ]; then
674  URL="http://10.53.0.3:${EXTRAPORT1}/bind9.xsl"
675  # ensure over-long time stamps are ignored
676  ${CURL} --silent --show-error --fail --output bind9.xsl.2 $URL \
677    --header 'If-Modified-Since: 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789'
678  if ! [ bind9.xsl.2 -nt bind9.xsl.1 ] \
679    || ! ${CURL} --silent --show-error --fail \
680      --output bind9.xsl.3 $URL \
681      --time-cond bind9.xsl.1 \
682    || [ -f bind9.xsl.3 ]; then
683    ret=1
684  fi
685else
686  echo_i "skipping test: requires libxml2 and curl"
687fi
688if [ $ret != 0 ]; then echo_i "failed"; fi
689status=$((status + ret))
690n=$((n + 1))
691
692echo_i "Checking that there are no 'first refresh' zones in ns3 ($n)"
693ret=0
694$RNDCCMD 10.53.0.3 status | grep -E '^xfers first refresh: 0$' >/dev/null || ret=1
695if [ $ret != 0 ]; then echo_i "failed"; fi
696status=$((status + ret))
697n=$((n + 1))
698
699echo_i "Transfering zones from ns1 to ns3 in slow mode ($n)"
700ret=0
701i=0
702# Restart ns1 with '-T transferslowly' to see the xfrins information in ns3's statschannel while it's ongoing
703stop_server ns1
704start_server --noclean --restart --port ${PORT} ns1 -- "-D statschannel-ns1 $NS_PARAMS -T transferslowly"
705# Request a retransfer of the secondary zones
706nextpart ns3/named.run >/dev/null
707$RNDCCMD 10.53.0.3 retransfer example | sed "s/^/ns3 /" | cat_i
708$RNDCCMD 10.53.0.3 retransfer example-tcp | sed "s/^/ns3 /" | cat_i
709$RNDCCMD 10.53.0.3 retransfer example-tls | sed "s/^/ns3 /" | cat_i
710$RNDCCMD 10.53.0.3 addzone 'example-new { type secondary; primaries { 10.53.0.1; }; file "example-new.db"; };' 2>&1 | sed "s/^/ns3 /" | cat_i
711wait_for_log_fast 200 "zone example/IN: Transfer started" ns3/named.run || ret=1
712if [ $ret != 0 ]; then echo_i "failed"; fi
713status=$((status + ret))
714n=$((n + 1))
715
716_wait_for_transfers() {
717  if [ "$PERL_XML" ]; then
718    getxfrins xml x$n || return 1
719
720    # XML is encoded in one line, use awk to separate each transfer
721    # with a newline
722
723    # We expect 4 transfers
724    count=$(awk '{ gsub("<xfrin ", "\n<xfrin ") } 1' xfrins.xml.x$n | grep -c -E '<state>(Zone Transfer Request|First Data|Receiving AXFR Data)</state>')
725    if [ $count != 4 ]; then return 1; fi
726
727    # We expect 3 of 4 to be retransfers
728    count=$(awk '{ gsub("<xfrin ", "\n<xfrin ") } 1' xfrins.xml.x$n | grep -c -F '<firstrefresh>No</firstrefresh>')
729    if [ $count != 3 ]; then return 1; fi
730
731    # We expect 1 of 4 to be a new transfer
732    count=$(awk '{ gsub("<xfrin ", "\n<xfrin ") } 1' xfrins.xml.x$n | grep -c -F '<firstrefresh>Yes</firstrefresh>')
733    if [ $count != 1 ]; then return 1; fi
734  fi
735
736  if [ "$PERL_JSON" ]; then
737    getxfrins json j$n || return 1
738
739    # We expect 4 transfers
740    count=$(grep -c -E '"state":"(Zone Transfer Request|First Data|Receiving AXFR Data)"' xfrins.json.j$n)
741    if [ $count != 4 ]; then return 1; fi
742
743    # We expect 3 of 4 to be retransfers
744    count=$(grep -c -F '"firstrefresh":"No"' xfrins.json.j$n)
745    if [ $count != 3 ]; then return 1; fi
746
747    # We expect 1 of 4 to be a new transfer
748    count=$(grep -c -F '"firstrefresh":"Yes"' xfrins.json.j$n)
749    if [ $count != 1 ]; then return 1; fi
750  fi
751}
752
753# We have now less than one second to catch the zone transfers in progress
754echo_i "Checking zone transfer information in the statistics channel ($n)"
755ret=0
756retry_quiet_fast 200 _wait_for_transfers || ret=1
757if [ $ret != 0 ]; then echo_i "failed"; fi
758status=$((status + ret))
759n=$((n + 1))
760
761echo_i "Checking that there is one 'first refresh' zone in ns3 ($n)"
762ret=0
763$RNDCCMD 10.53.0.3 status | grep -E '^xfers first refresh: 1$' >/dev/null || ret=1
764if [ $ret != 0 ]; then echo_i "failed"; fi
765status=$((status + ret))
766n=$((n + 1))
767
768if [ "$PERL_JSON" ]; then
769  echo_i "Checking zone transfer transports ($n)"
770  ret=0
771  cp xfrins.json.j$((n - 2)) xfrins.json.j$n
772  $PERL xfrins-json.pl xfrins.json.j$n example >xfrins.example.format$n
773  echo "soatransport: UDP" >xfrins.example.expect$n
774  echo "transport: TCP" >>xfrins.example.expect$n
775  cmp xfrins.example.format$n xfrins.example.expect$n || ret=1
776  $PERL xfrins-json.pl xfrins.json.j$n example-tcp >xfrins.example-tcp.format$n
777  echo "soatransport: TCP" >xfrins.example-tcp.expect$n
778  echo "transport: TCP" >>xfrins.example-tcp.expect$n
779  cmp xfrins.example-tcp.format$n xfrins.example-tcp.expect$n || ret=1
780  $PERL xfrins-json.pl xfrins.json.j$n example-tls >xfrins.example-tls.format$n
781  echo "soatransport: TLS" >xfrins.example-tls.expect$n
782  echo "transport: TLS" >>xfrins.example-tls.expect$n
783  cmp xfrins.example-tls.format$n xfrins.example-tls.expect$n || ret=1
784  if [ $ret != 0 ]; then echo_i "failed"; fi
785  status=$((status + ret))
786  n=$((n + 1))
787fi
788
789echo_i "Wait for slow zone transfer to complete ($n)"
790ret=0
791wait_for_log 20 "zone example/IN: zone transfer finished: success" ns3/named.run || ret=1
792if [ $ret != 0 ]; then echo_i "failed"; fi
793status=$((status + ret))
794n=$((n + 1))
795
796echo_i "exit status: $status"
797[ $status -eq 0 ] || exit 1
798