Understanding Perl Net::DNS resolving and patching check_rbl to support a specific nameserver

Written by - 0 comments

Published on - Listed in Perl Coding Monitoring DNS Mail


In the previous article I wrote about issues with the Spamhaus DNSBL, when public DNS resolvers are used. As mentioned at the end of that post, not only mail servers (such as Postfix) are running into potential issues, also monitoring is affected.

check_rbl alerts: Blacklisted on Spamhaus!

If you are managing any mail server which sends out e-mails, you should regularly check whether these servers are listed on a RBL/DNSBL (Real-time Block Lists / DNS Block Lists). Of course this is an annoying task and is easily forgotten, however very important. And here we come to the great thing, which open source monitoring is: There's a plugin for everything.

check_rbl is a monitoring plugin, maintained by Matteo Corti, which can query one or douzens of DNSBLs (defined in a ini config file). But since Spamhaus changed their policy to respond with a bogus A record "127.255.255.254" as response when public DNS resolvers were used, the plugin now mistakes the 127.255.255.254 record as being listed on the Spamhaus block lists.

root@icinga2:/usr/lib/nagios/plugins# ./check_rbl -H 212.103.71.210 --extra-opts "rbl@rbl.ini" -t 60
CHECK_RBL CRITICAL - 212.103.71.210 BLACKLISTED on 6 servers of 82 (cbl.abuseat.org (127.255.255.254) pbl.spamhaus.org (127.255.255.254) sbl.spamhaus.org (127.255.255.254) sbl-xbl.spamhaus.org (127.255.255.254) zen.spamhaus.org (127.255.255.254) xbl.spamhaus.org (127.255.255.254)) | servers=6;0;0 time=0s;;

How DNS lookups work in Perl

The plugin, written in Perl, uses the Net::DNS module to do DNS lookups/queries. To run a DNS query, a DNS resolver needs to be initiated (here using $res):

use Net::DNS;
my $res = Net::DNS::Resolver->new;

To launch a full DNS query on a domain, the query function can be used:

my $response = $res->query("www.claudiokuenzler.com");

Note: check_rbl uses another function to run multiple queries in parallel, but this query function is used for demonstration.

But $response cannot be used in combination with print, it would just show garbage data:

print $response;
-> Net::DNS::Packet=HASH(0x55d9e9b22e28)

This is because $response contains a hash as data value (see related article From strings to array to hashes and how to view the values in Perl), so we need to parse this correctly. To see how the hash looks like we can use the Data::Dumper module:

ck@mintp ~ $ cat /tmp/simpledns.pl
#!/usr/bin/perl
use Net::DNS;
use Data::Dumper qw(Dumper);
my $res = Net::DNS::Resolver->new;
my $response = $res->query("www.claudiokuenzler.com");
print Dumper \$response;

ck@mintp ~ $ perl /tmp/simpledns.pl
$VAR1 = \bless( {
                   'xedns' => bless( {
                                       'owner' => bless( {
                                                           'label' => []
                                                         }, 'Net::DNS::DomainName1035' ),
                                       'type' => 41
                                     }, 'Net::DNS::RR::OPT' ),
                   'id' => 59494,
                   'additional' => [],
                   'answer' => [
                                 bless( {
                                          'owner' => bless( {
                                                              'label' => [],
                                                              'origin' => bless( {
                                                                                   'label' => [
                                                                                                'www',
                                                                                                'claudiokuenzler',
                                                                                                'com'
                                                                                              ]
                                                                                 }, 'Net::DNS::DomainName' )
                                                            }, 'Net::DNS::DomainName1035' ),
                                          'address' => '?gG?',
                                          'type' => 1,
                                          'rdlength' => 4,
                                          'class' => 1,
                                          'ttl' => 930
                                        }, 'Net::DNS::RR::A' )
                               ],
                   'status' => 33152,
                   'question' => [
                                   bless( {
                                            'qname' => bless( {
                                                                'label' => [
                                                                             'www',
                                                                             'claudiokuenzler',
                                                                             'com'
                                                                           ]
                                                              }, 'Net::DNS::DomainName1035' ),
                                            'qclass' => 1,
                                            'qtype' => 1
                                          }, 'Net::DNS::Question' )
                                 ],
                   'replysize' => 57,
                   'count' => [
                                1,
                                1,
                                0,
                                0
                              ],
                   'replyfrom' => '127.0.0.53',
                   'authority' => []
                 }, 'Net::DNS::Packet' );

Looks overwhelming, doesn't it? However the part we're interested in is nested inside the answer array. Inside answer there's the "address" key - which right now still holds some weird value. To show the actual value, this needs to be run through the array (using foreach):

ck@mintp ~ $ cat /tmp/simpledns.pl
#!/usr/bin/perl
use Net::DNS;
my $res = Net::DNS::Resolver->new;
my $response = $res->query("www.claudiokuenzler.com");
foreach $record ($response->answer) {
    print $record->type, " record: ";
    print $record->address, "\n";
}


ck@mintp ~ $ perl /tmp/simpledns.pl
A record: 212.103.71.210

Here we go, we got the A record for www.claudiokuenzler.com!

Which resolver is being used by Net::DNS?

But where did we get this DNS response from? This can be seen above in the DataDumper output:

                   'replyfrom' => '127.0.0.53',

We can also list the nameserver(s) by retrieving a list using the nameservers function:

ck@mintp ~ $ cat /tmp/simpledns.pl
#!/usr/bin/perl
use Net::DNS;
my $res = Net::DNS::Resolver->new;
my @nameserver = $res->nameservers();
my $response = $res->query("www.claudiokuenzler.com");
foreach $record ($response->answer) {
    print $record->type, " record: ";
    print $record->address, "\n";
}
print "We used this resolver: @nameserver\n";

ck@mintp ~ $ perl /tmp/simpledns.pl
A record: 212.103.71.210
We used this resolver: 127.0.0.53

We can see that 127.0.0.53 is used as nameserver. This IP is defined in the systems /etc/resolv.conf (here using systemd-resolved in the background):

ck@mintp ~ $ cat /etc/resolv.conf
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
# 127.0.0.53 is the systemd-resolved stub resolver.
# run "systemd-resolve --status" to see details about the actual nameservers.

nameserver 127.0.0.53

This means that Perl's Net::DNS uses the system's DNS nameserver, usually defined in /etc/resolv.conf.

Which nameserver is used by check_rbl?

With this information in mind, we can verify which nameservers are used on the Icinga server, running the check_rbl plugin.

 root@icinga2:/usr/lib/nagios/plugins# ./check_rbl -H 212.103.71.210 --extra-opts "rbl@rbl.ini" -t 60
CHECK_RBL CRITICAL - 212.103.71.210 BLACKLISTED on 6 servers of 82 (cbl.abuseat.org (127.255.255.254) pbl.spamhaus.org (127.255.255.254) sbl.spamhaus.org (127.255.255.254) sbl-xbl.spamhaus.org (127.255.255.254) zen.spamhaus.org (127.255.255.254) xbl.spamhaus.org (127.255.255.254)) | servers=6;0;0 time=0s;;

root@icinga2:/usr/lib/nagios/plugins# cat /etc/resolv.conf
nameserver 1.1.1.1
nameserver 8.8.8.8

We're using the Cloudflare public DNS resolver 1.1.1.1 and Google's 8.8.8.8 as fallback. Surprise? Not really. That exactly explains why check_rbl shows the Spamhaus block lists as critical.

Telling check_rbl to use another nameserver

Of course we could tell this Icinga server to use different nameservers. But there's a better solution: We can patch the check_rbl plugin and allow a specific nameserver to be used as DNS resolver!

The Net::DNS module allows to set a specific nameserver during initiation (above with the $res variable). This can be defined by using the nameservers function once again (but not saved as list this time):

ck@mintp ~ $ cat /tmp/simpledns.pl
#!/usr/bin/perl
use Net::DNS;
use Data::Dumper qw(Dumper);
my $res = Net::DNS::Resolver->new;
$res->nameservers("208.67.222.222");
my @nameserver = $res->nameservers();
my $response = $res->query("www.claudiokuenzler.com");
#print Dumper \$response;
foreach $record ($response->answer) {
    print $record->type, " record: ";
    print $record->address, "\n";
}
print "We used this resolver: @nameserver\n";

ck@mintp ~ $ perl /tmp/simpledns.pl
A record: 212.103.71.210
We used this resolver: 208.67.222.222

A specific nameserver (208.67.222.222, a public DNS resolver from Cisco) was defined to be used as resolver. Using this knowledge, we can adjust the check_rbl plugin.

Of course we need to make this a config option as additional parameter (-n / --nameserver):

     $options->arg(
        spec     => 'nameserver|n=s',
        help     => 'Use this nameserver IP as DNS resolver (only one IP supported)',
        required => 0,
    );

During the plugin's DNS resolver initiation (which happens in the plugin's init_dns_resolver function), an additional if condition checks whether or not the nameserver parameter was used. If yes, the given input is used as nameserver:

sub init_dns_resolver {

    my $retries = shift;

    my $res = Net::DNS::Resolver->new();
    if ( $res->can('force_v4') ) {
        $res->force_v4(1);
    }

    if ($retries) {
        $res->retry($retries);
    }

    if (defined $options->nameserver) {
        $res->nameservers( $options->nameserver );
    }

    my @nameservers = $res->nameservers();
    debug("Using DNS Resolver: @nameservers");

    return $res;
}

With this patched version of check_rbl, the plugin can now be run against a different nameserver instead of the fixed nameservers defined in the OS:

root@icinga2:/usr/lib/nagios/plugins# ./check_rbl -H 212.103.71.210 --extra-opts "rbl@rbl.ini" -t 60 -n 208.67.222.222
CHECK_RBL OK - 212.103.71.210 (vip210.infiniroot.net) BLACKLISTED on 0 servers of 82 | servers=0;0;0 time=2s;;

And voilĂ ! We didn't run into the Spamhaus bogus response anymore and the plugin works again!

Code contribution in upstream project

Of course this patch was sent as a pull request to the upstream project, check_rbl. It's very likely that the next version (1.6.5) of check_rbl will contain this feature.



Add a comment

Show form to leave a comment

Comments (newest first)

No comments yet.

RSS feed

Blog Tags:

  AWS   Android   Ansible   Apache   Apple   Atlassian   BSD   Backup   Bash   Bluecoat   CMS   Chef   Cloud   Coding   Consul   Containers   CouchDB   DB   DNS   Database   Databases   Docker   ELK   Elasticsearch   Filebeat   FreeBSD   Galera   Git   GlusterFS   Grafana   Graphics   HAProxy   HTML   Hacks   Hardware   Icinga   Influx   Internet   Java   KVM   Kibana   Kodi   Kubernetes   LVM   LXC   Linux   Logstash   Mac   Macintosh   Mail   MariaDB   Minio   MongoDB   Monitoring   Multimedia   MySQL   NFS   Nagios   Network   Nginx   OSSEC   OTRS   Office   PGSQL   PHP   Perl   Personal   PostgreSQL   Postgres   PowerDNS   Proxmox   Proxy   Python   Rancher   Rant   Redis   Roundcube   SSL   Samba   Seafile   Security   Shell   SmartOS   Solaris   Surveillance   Systemd   TLS   Tomcat   Ubuntu   Unix   VMWare   VMware   Varnish   Virtualization   Windows   Wireless   Wordpress   Wyse   ZFS   Zoneminder