1# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
2#
3# SPDX-License-Identifier: MPL-2.0
4#
5# This Source Code Form is subject to the terms of the Mozilla Public
6# License, v. 2.0.  If a copy of the MPL was not distributed with this
7# file, you can obtain one at https://mozilla.org/MPL/2.0/.
8#
9# See the COPYRIGHT file distributed with this work for additional
10# information regarding copyright ownership.
11
12# test response rate limiting
13
14SYSTEMTESTTOP=..
15. $SYSTEMTESTTOP/conf.sh
16
17RNDCCMD="$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p ${CONTROLPORT} -s"
18
19#set -x
20
21ns1=10.53.0.1			    # root, defining the others
22ns2=10.53.0.2			    # test server
23ns3=10.53.0.3			    # secondary test server
24ns4=10.53.0.4			    # log-only test server
25ns7=10.53.0.7			    # whitelisted client
26
27USAGE="$0: [-x]"
28while getopts "x" c; do
29    case $c in
30	x) set -x;;
31	*) echo "$USAGE" 1>&2; exit 1;;
32    esac
33done
34shift `expr $OPTIND - 1 || true`
35if test "$#" -ne 0; then
36    echo "$USAGE" 1>&2
37    exit 1
38fi
39# really quit on control-C
40trap 'exit 1' 1 2 15
41
42
43ret=0
44setret () {
45    ret=1
46    echo_i "$*"
47}
48
49
50# Wait until soon after the start of a second to make results consistent.
51#   The start of a second credits a rate limit.
52#   This would be far easier in C or by assuming a modern version of perl.
53sec_start () {
54    START=`date`
55    while true; do
56	NOW=`date`
57	if test "$START" != "$NOW"; then
58	    return
59	fi
60	$PERL -e 'select(undef, undef, undef, 0.05)' || true
61    done
62}
63
64
65# turn off ${HOME}/.digrc
66HOME=/dev/null; export HOME
67
68#   $1=number of tests  $2=target domain  $3=dig options
69QNUM=1
70burst () {
71    BURST_LIMIT=$1; shift
72    BURST_DOM_BASE="$1"; shift
73
74    XCNT=$CNT
75    CNT='XXX'
76    eval FILENAME="mdig.out-$BURST_DOM_BASE"
77    CNT=$XCNT
78
79    DOMS=""
80    CNTS=`$PERL -e 'for ( $i = 0; $i < '$BURST_LIMIT'; $i++) { printf "%03d\n", '$QNUM' + $i; }'`
81    for CNT in $CNTS
82    do
83        eval BURST_DOM="$BURST_DOM_BASE"
84        DOMS="$DOMS $BURST_DOM"
85    done
86    ARGS="+burst +nocookie +continue +time=1 +tries=1 -p ${PORT} $* @$ns2 $DOMS"
87    $MDIG $ARGS 2>&1 |                                                  \
88        tr -d '\r' |                                                    \
89        tee -a full-$FILENAME |                                         \
90        sed -n -e '/^;; AUTHORITY/,/^$/d'			        \
91		-e '/^;; ADDITIONAL/,/^$/d'				\
92		-e 's/^[^;].*	\([^	 ]\{1,\}\)$/\1/p'		\
93		-e 's/;; flags.* tc .*/TC/p'				\
94		-e 's/;; .* status: NXDOMAIN.*/NXDOMAIN/p'		\
95		-e 's/;; .* status: NOERROR.*/NOERROR/p'		\
96		-e 's/;; .* status: SERVFAIL.*/SERVFAIL/p'		\
97		-e 's/response failed with timed out.*/drop/p'		\
98		-e 's/;; communications error to.*/drop/p' >> $FILENAME &
99    QNUM=`expr $QNUM + $BURST_LIMIT`
100}
101
102# compare integers $1 and $2; ensure the difference is no more than $3
103range () {
104    $PERL -e 'if (abs(int($ARGV[0]) - int($ARGV[1])) > int($ARGV[2])) { exit(1) }' $1 $2 $3
105}
106
107#   $1=domain  $2=IP address  $3=# of IP addresses  $4=TC  $5=drop
108#	$6=NXDOMAIN  $7=SERVFAIL or other errors
109ck_result() {
110    # wait to the background mdig calls to complete.
111    wait
112    BAD=no
113    ADDRS=`egrep "^$2$" mdig.out-$1				2>/dev/null | wc -l`
114    # count simple truncated and truncated NXDOMAIN as TC
115    TC=`egrep "^TC|NXDOMAINTC$" mdig.out-$1			2>/dev/null | wc -l`
116    DROP=`egrep "^drop$" mdig.out-$1				2>/dev/null | wc -l`
117    # count NXDOMAIN and truncated NXDOMAIN as NXDOMAIN
118    NXDOMAIN=`egrep "^NXDOMAIN|NXDOMAINTC$" mdig.out-$1		2>/dev/null | wc -l`
119    SERVFAIL=`egrep "^SERVFAIL$" mdig.out-$1			2>/dev/null | wc -l`
120    NOERROR=`egrep "^NOERROR$" mdig.out-$1			2>/dev/null | wc -l`
121
122    range $ADDRS "$3" 1 ||
123    setret "$ADDRS instead of $3 '$2' responses for $1" &&
124    BAD=yes
125
126    range $TC "$4" 1 ||
127    setret "$TC instead of $4 truncation responses for $1" &&
128    BAD=yes
129
130    range $DROP "$5" 1 ||
131    setret "$DROP instead of $5 dropped responses for $1" &&
132    BAD=yes
133
134    range $NXDOMAIN "$6" 1 ||
135    setret "$NXDOMAIN instead of $6 NXDOMAIN responses for $1" &&
136    BAD=yes
137
138    range $SERVFAIL "$7" 1 ||
139    setret "$SERVFAIL instead of $7 error responses for $1" &&
140    BAD=yes
141
142    range $NOERROR "$8" 1 ||
143    setret "$NOERROR instead of $8 NOERROR responses for $1" &&
144    BAD=yes
145
146    if test -z "$BAD"; then
147	rm -f mdig.out-$1
148    fi
149}
150
151
152ckstats () {
153    LABEL="$1"; shift
154    TYPE="$1"; shift
155    EXPECTED="$1"; shift
156    C=`tr -d '\r' < ns2/named.stats |
157        sed -n -e "s/[	 ]*\([0-9]*\).responses $TYPE for rate limits.*/\1/p" |
158        tail -1`
159    C=`expr 0$C + 0`
160
161    range "$C" $EXPECTED 1 ||
162    setret "wrong $LABEL $TYPE statistics of $C instead of $EXPECTED"
163}
164
165
166#########
167sec_start
168
169# Tests of referrals to "." must be done before the hints are loaded
170#   or with "additional-from-cache no"
171burst 5 a1.tld3 +norec
172# basic rate limiting
173burst 3 a1.tld2
174# delay allows an additional response.
175sleep 1
176burst 10 a1.tld2
177# Request 30 different qnames to try a wildcard.
178burst 30 'x$CNT.a2.tld2'
179# These should be counted and limited but are not.  See RT33138.
180burst 10 'y.x$CNT.a2.tld2'
181
182#					IP      TC      drop  NXDOMAIN SERVFAIL NOERROR
183# referrals to "."
184ck_result   a1.tld3	x		0	1	2	0	0	2
185# check 13 results including 1 second delay that allows an additional response
186ck_result   a1.tld2	192.0.2.1	3	4	6	0	0	8
187
188# Check the wild card answers.
189# The parent name of the 30 requests is counted.
190ck_result 'x*.a2.tld2'	192.0.2.2	2	10	18	0	0	12
191
192# These should be limited but are not.  See RT33138.
193ck_result 'y.x*.a2.tld2' 192.0.2.2	10	0	0	0	0	10
194
195#########
196sec_start
197
198burst 10 'x.a3.tld3'
199burst 10 'y$CNT.a3.tld3'
200burst 10 'z$CNT.a4.tld2'
201
202# 10 identical recursive responses are limited
203ck_result 'x.a3.tld3'	192.0.3.3	2	3	5	0	0	5
204
205# 10 different recursive responses are not limited
206ck_result 'y*.a3.tld3'	192.0.3.3	10	0	0	0	0	10
207
208# 10 different NXDOMAIN responses are limited based on the parent name.
209#   We count 13 responses because we count truncated NXDOMAIN responses
210#   as both truncated and NXDOMAIN.
211ck_result 'z*.a4.tld2'	x		0	3	5	5	0	0
212
213$RNDCCMD $ns2 stats
214ckstats first dropped 36
215ckstats first truncated 21
216
217
218#########
219sec_start
220
221burst 10 a5.tld2 +tcp
222burst 10 a6.tld2 -b $ns7
223burst 10 a7.tld4
224burst 2 a8.tld2 -t AAAA
225burst 2 a8.tld2 -t TXT
226burst 2 a8.tld2 -t SPF
227
228#					IP      TC      drop  NXDOMAIN SERVFAIL NOERROR
229# TCP responses are not rate limited
230ck_result a5.tld2	192.0.2.5	10	0	0	0	0	10
231
232# whitelisted client is not rate limited
233ck_result a6.tld2	192.0.2.6	10	0	0	0	0	10
234
235# Errors such as SERVFAIL are rate limited.
236ck_result a7.tld4	x		0	0	8	0	2	0
237
238# NODATA responses are counted as the same regardless of qtype.
239ck_result a8.tld2	x		0	2	2	0	0	4
240
241$RNDCCMD $ns2 stats
242ckstats second dropped 46
243ckstats second truncated 23
244
245
246#########
247sec_start
248
249#					IP      TC      drop  NXDOMAIN SERVFAIL NOERROR
250# all-per-second
251#   The qnames are all unique but the client IP address is constant.
252QNUM=101
253burst 60 'all$CNT.a9.tld2'
254
255ck_result 'a*.a9.tld2'	192.0.2.8	50	0	10	0	0	50
256
257$RNDCCMD $ns2 stats
258ckstats final dropped 56
259ckstats final truncated 23
260
261#########
262sec_start
263
264DIGOPTS="+nocookie +nosearch +time=1 +tries=1 +ignore -p ${PORT}"
265$DIG $DIGOPTS @$ns4 A a7.tld4 > /dev/null 2>&1
266$DIG $DIGOPTS @$ns4 A a7.tld4 > /dev/null 2>&1
267$DIG $DIGOPTS @$ns4 A a7.tld4 > /dev/null 2>&1
268$DIG $DIGOPTS @$ns4 A a7.tld4 > /dev/null 2>&1
269$DIG $DIGOPTS @$ns4 A a7.tld4 > /dev/null 2>&1
270$DIG $DIGOPTS @$ns4 A a7.tld4 > /dev/null 2>&1
271$DIG $DIGOPTS @$ns4 A a7.tld4 > /dev/null 2>&1
272$DIG $DIGOPTS @$ns4 A a7.tld4 > /dev/null 2>&1
273$DIG $DIGOPTS @$ns4 A a7.tld4 > /dev/null 2>&1
274$DIG $DIGOPTS @$ns4 A a7.tld4 > /dev/null 2>&1
275$DIG $DIGOPTS @$ns4 A a7.tld4 > /dev/null 2>&1
276
277# regression test for GL #2839
278DIGOPTS="+bufsize=4096 +ignore -p ${PORT}"
279$DIG $DIGOPTS @$ns4 TXT big.tld4 > /dev/null 2>&1
280
281grep "would limit" ns4/named.run >/dev/null 2>&1 ||
282setret "\"would limit\" not found in log file."
283
284$NAMED -D rrl-ns5 -gc broken.conf > broken.out 2>&1 &
285sleep 2
286grep "min-table-size 1" broken.out > /dev/null || setret "min-table-size 0 was not changed to 1"
287
288if [ -f named.pid ]; then
289    $KILL `cat named.pid`
290    setret "named should not have started, but did"
291fi
292
293echo_i "exit status: $ret"
294[ $ret -eq 0 ] || exit 1
295