#! /bin/bash

#$title$ DNS recursive query anomalies
#$check$ broken/compromised clients making excessive duplicate lookups
#$ref$ /var/log/named/queries
#$author$ Rafal Rzeczkowski

level_check short

V=0.50	# initial
V=0.51	# report target name of the duplicate query
V=0.52	# metadata header, help message
V=0.53	# debug output, score randomized hostnames higher

TMP_AWK=$(mktemp)
QUERY_LOG='/var/log/named/queries'

if test ! -s $QUERY_LOG
then
	fail warning "BIND9 query log $QUERY_LOG not found"
	helpmsg 'unexpected software configuration fault'
	exit
fi

if ! awk --re-interval --assign DEBUG=$DEBUG '
function errprint(msg) {
	if (DEBUG>2) {print msg > "/dev/stderr"}
}
#25-Apr-2013 15:41:00.127 client 64.30.74.191#15496: query: i.ytimg.com IN A + (216.195.0.226)
#17-Jun-2014 16:16:07.356 client 2604:1500:8000:80:408:54f9:c549:3408#65184: query: wtexmpungvkbkt.www.860kan.com IN A + (2604:1500:0:c8::26)
BEGIN {
	NR_MAX=100000;
	FS=" ";
	MAX_QUERIES_PER_SECOND=100;
	ANALYSIS_INTERVAL=10;
	time_change=-1;
}
{
	if (NR>NR_MAX) { exit };
	split($2,time_cur,".");
	if (time_cur[1]!=time_last) {
		time_change++;
		time_last=time_cur[1];
		errprint("timediff\t" time_change " last timestamp was " time_last);
	}
	if (time_change<1) { NR=0; next };
	if (time_change>ANALYSIS_INTERVAL) { exit };

	split($4,split4,"#");
	source=split4[1];
	name=$6;
	namespace=$7;
	type=$8;
	query=name "_" namespace "_" type;
	errprint("dnsquery\t" source " " query);
	if (query == last_query[source]) {
		if (query ~ /^[a-z]{12,}[.]/) {
			penalty=10;
		} else {
			penalty=1;
		}

		address[source]=address[source]+penalty;
		target[source]=name;
		errprint("update  \t" source " " address[source]);
	}
	last_query[source]=query;
}
END {
	for (a in address) {
		rate=int(address[a]/ANALYSIS_INTERVAL);
		errprint("total\t" a "\t" rate "\t" target[a]);
		if (rate>MAX_QUERIES_PER_SECOND) {
			print a "\t" rate "\t" target[a];
			exit 1
		}
	}
	print NR; exit 0;
}' $QUERY_LOG > $TMP_AWK
then
	RESULT=($(<$TMP_AWK))
	CLIENT=${RESULT[0]}
	RATE=${RESULT[1]}
	TARGET=${RESULT[2]}
	if GETENT=($(getent hosts $CLIENT))
	then
		CLIENT_HOSTNAME=${GETENT[1]}
	else
		CLIENT_HOSTNAME='UNKNOWN'
	fi

	fail warning "client $CLIENT[$CLIENT_HOSTNAME] is requesting duplicate lookups for $TARGET at $RATE query units/second"
	helpmsg "temporary mitigation: {ip route add blackhole $CLIENT} on all recursive servers; permanent solution: customer router firmware/configuration update"
else
	RESULT=($(<$TMP_AWK))
	echodebug "${RESULT[0]} lines from $QUERY_LOG were analyzed for excessive duplicate queries"
	ok
fi
rm $TMP_AWK
exit
