#!/usr/local/bin/perl
#
# Quick script to see that the DNS server is doing
# Fri Oct 30 16:34:29 PDT 2020, created, rudy
#
# requirements:
# pkg install p5-Term-ReadKey
# pkg install p5-Term-ANCIScreen
# ----------------------------------------------------------------------
use Term::ReadKey;
use Socket;
use POSIX qw(ceil floor);
use Term::ANSIScreen qw/:color :cursor :screen :keyboard :constants/;
use strict;
our (%h, %r, %lh, %lr); # hits, requests, and last for those two
our (%dns, %stats); # DNS cache, and RDNC output
our ($width, $height); # terminal size
our $hostname = `hostname`;
chomp($hostname);
# set 'network' to your DNS server's ip block
# example, your DNS server is 10.8.2.3 or 10.8.2.4 then use
our $network = '10.8.1.0/28'; # DNS server Range (used in TCPDUMP)
our $port = 53; # not sure this will ever change
our $countPerCycle = 1000; # count flag for TCPDUMP
# ----------------------------------------------------------------------
print "Welcome, starting TCPdump to gather snapshot of current DNS requests.\nStandby ......\n\n";
while (1) {
&tcpdump;
my ($lastwidth, $lastheight) = ($width, $height);
($width, $height) = GetTerminalSize ();
print cls() if ($height != $lastheight || $width != $lastwidth); #wipe screen on resize
locate 1, 1; # move cursor to top left
&getStats;
&header;
&top10;
&footer;
}
sub tcpdump {
%lr = %r;
%lh = %h;
open TCPDUMP, "tcpdump -n -c $countPerCycle dst port $port and dst net $network 2> /dev/null |" or die "No tcpdump\n";
while (<TCPDUMP>) {
# 13:14:18.664904 IP 52.119.118.12.42467 > 208.69.40.3.53: 26522+ A? l3.dca0.com. (29)
# this could probably use refinements... version 0.1
my @a = split(' ');
my $ip = $a[2];
$ip =~ s/(\.\d+)$//; #remote port;
$h{$ip}++; # easy to spoof...
if (/Flags/) {
$r{'Flags'}++;
} elsif ($a[6] eq '[1au]') {
$r{"$a[7] $a[8]"}++;
} else {
$r{"$a[6] $a[7]"}++;
}
}
close TCPDUMP;
}
sub top10 {
# started out as top10, but let's just max out to match terminal size.
my $top = floor($height/2) - 3;
# print top IPs doing requests
print colored ['black on white'], sprintf("%5s %17s %45s\n","Hits", "IP","Hostname");
my $count = 0;
foreach my $ip (sort {$h{$b} - $lh{$b} <=> $h{$a} - $lh{$a} } keys %h) {
if ($top > $count++) {
print " "x$width . "\r"; # clear line
print colored ['bold white'], sprintf("%5i ",$h{$ip}),
colored ['bold green'], sprintf("+%-4i",$h{$ip} - $lh{$ip}),
colored ['red'], sprintf("%17s",$ip),
colored ['yellow'], sprintf(" %45s",&hostname($ip)),
"\n";
} elsif ($count > 1000) {
delete($h{$ip});
}
}
# Print out the 'most popular' lookups
$count = 0;
my $half = floor($width/2);
my $halfminus1 = $half-1;
print colored ['bold black on white'], sprintf("%-${halfminus1}s\n", "Total Hits Request");
foreach my $req (sort {$r{$b} <=> $r{$a}} keys %r) {
if ($top > $count++) {
print " "x$width . "\r";
print colored ['bold white'], sprintf("%5i ",$r{$req}),
colored ['bold green'], sprintf("+%-4i",$r{$req} - $lr{$req});
#$req =~ s/(.{$half}).*/$1/,
print colored ['bold blue'], $req, "\n";
} elsif ($count > 1000) {
delete($r{$req});
}
}
return if ($width < 70);
$count = 0;
# Print out the 'most popular' lookups for this last data cycle
print up($top+1), right($half);
print colored ['bold black on white'], sprintf("%-${half}s\n", "New Hits Request");
foreach my $req (sort {$r{$b} - $lr{$b} <=> $r{$a}- $lr{$a} } keys %r) {
if ($top > $count++) {
print right($half),
colored ['bold green'], sprintf("+%-4i",$r{$req} - $lr{$req});
my $max_string_size = $half-5;
$req =~ s/(.{$max_string_size}).*/$1/,
print colored ['bold blue'], $req, "\n";
}
}
%lr = {};
}
sub hostname {
my $ip = shift;
$dns{$ip} ||= gethostbyaddr(inet_aton($ip), AF_INET) || 'No.rDNS';
return $dns{$ip};
}
sub getStats {
# get clients from 'rndc' ouput and use 'ps' to get CPU usuage of bind
open RNDC, "rndc status |" or return undef;
local $/ = undef;
$_ = <RNDC>;
close RNDC;
$stats{cpu} = `ps axo %cpu,comm | grep named`;
$stats{cpu} =~ s/ named.*/%/s;
/version: BIND (\S+)/s || return undef;
$stats{version} = $1;
if (m,(recursive clients: \d+/\d+/\d+),s) {
$stats{clients} = $1;
}
}
sub header {
# stuffed this in a subroutine so you can change it to what you want!
my $now_string = localtime;
print colored ['black on yellow'], sprintf("%-${width}s","$hostname $now_string");
}
sub footer {
print " "x$width . "\r"; # clear line
my $size = $width - length("CPU: $stats{cpu}, BIND $stats{version}") - 1;
my $now_string = localtime;
print colored ['black on yellow'], sprintf("CPU: $stats{cpu}, BIND $stats{version} %${size}s\n",$stats{clients});
}