dnc : a CLI tool to check domain names configuration and statistics

For a long time, I needed a simple and minimalistic tool showing a quick overview of my domain names configuration and statistics. I originally planned to implement it as a shellscript, but Node makes it so easy to write efficient CLI tools and distribute them via npm so it made more sense to go this way. The tool performs asynchronous DNS requests and as a bonus, displays a nice colored Unicode table, thanks to the great cli-tables module.

Here is the output of running dnc with two domains in the configuration file :

dnc showing a report for two domains

To install, simply invoke :

npm install -g dnc

Source code and documentation are on GitHub : https://github.com/fcambus/dnc

NXDOMAIN Hijacking : Dnsmasq to the rescue!

I’ve been playing a lot with OpenWrt lately (an embedded Linux distribution targeted at routers), which uses Dnsmasq to provide DNS forwarding and DHCP. After reading about Dnsmasq in detail (should you want to do the same, “Alternative DNS Servers“ has an entire chapter on it), I discovered a really interesting option : bogus-nxdomain.

To illustrate how it works, we are going to configure Dnsmasq to use OpenDNS resolvers, which perform NXDOMAIN hijacking by default.

Let’s add this directive to dnsmasq.conf, specifying where to look for resolvers :

resolv-file=/etc/resolv.conf.dnsmasq

We then create the resolv.conf.dnsmasq file, and add the hosts we want Dnsmasq to forward queries to :

nameserver 208.67.220.220
nameserver 208.67.222.222

Now, let’s attempt to resolve a non existant domain :

dig domain.nxdomain +short
67.215.65.132

Sure enough, instead of getting a NXDOMAIN response, we are redirected to an IP address belonging to OpenDNS.

We can verifiy that it’s indeed the case by performing a reverse lookup :

dig -x 67.215.65.132 +short
hit-nxdomain.opendns.com.

We now add the bogus-nxdomain directive to dnsmasq.conf :

bogus-nxdomain=67.215.65.132

And finally, after restarting Dnsmasq, querying a non existant domain returns NXDOMAIN as it should :

dig domain.nxdomain +noall +answer +comments

; <<>> DiG 9.8.4-rpz2+rl005.12-P1 <<>> domain.nxdomain +noall +answer +comments
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- 0="" 53036="" opcode:="" query,="" status:="" nxdomain,="" id:="" ;;="" flags:="" qr="" rd="" ra;="" query:="" 1,="" answer:="" 0,="" authority:="" additional:="" <="" pre="">

Storing ASCII art in the DNS

From time to time, I like to do some quick DNS related experiments, and storing ASCII art in the DNS had been on my idea list for quite a while.

The naive approach to this problem would be to simply use TXT records, but there are a couple of issues with this method. First, it’s not possible to specify the order of priority of TXT records, and results seem to vary depending on implementations. But moreover, there is no way to have several times the exact same line, as the records would be identical and only one would be taken into account when parsing the zone.

On the other hand, NAPTR records look like the perfect candidates for this purpose, as they can be ordered using the Order and Preference fields (see RFC 2915, for the NAPTR RR Format).

For this experiment, the first ASCII diagram from RFC 1035 will be used.

Here is the content of the zone :

ascii NAPTR 100 10 "" "              Local Host                        |  Foreign    " "" .
ascii NAPTR 110 10 "" "                                                |             " "" .
ascii NAPTR 120 10 "" " +---------+               +----------+         |  +--------+ " "" .
ascii NAPTR 130 10 "" " |         | user queries  |          |queries  |  |        | " "" .
ascii NAPTR 140 10 "" " |  User   |-------------->|          |---------|->|Foreign | " "" .
ascii NAPTR 150 10 "" " | Program |               | Resolver |         |  |  Name  | " "" .
ascii NAPTR 160 10 "" " |         |<--------------| 10="" 170="" 180="" 190="" 200="" 210="" 220="" 230="" 240="" |<--------|--|="" server="" |="" "="" ""="" .="" ascii="" naptr="" user="" responses|="" |responses|="" +---------+="" +----------+="" +--------+="" a="" cache="" additions="" references="" v="" <="" pre="">

And the result of querying the zone for NAPTR records :

dig ascii.statdns.org naptr +short
;; Truncated, retrying in TCP mode.
100 10 "" "              Local Host                        |  Foreign    " "" .
110 10 "" "                                                |             " "" .
120 10 "" " +---------+               +----------+         |  +--------+ " "" .
130 10 "" " |         | user queries  |          |queries  |  |        | " "" .
140 10 "" " |  User   |-------------->|          |---------|->|Foreign | " "" .
150 10 "" " | Program |               | Resolver |         |  |  Name  | " "" .
160 10 "" " |         |<--------------| 10="" 170="" 180="" 190="" 200="" 210="" 220="" 230="" 240="" |<--------|--|="" server="" |="" "="" ""="" .="" user="" responses|="" |responses|="" +---------+="" +----------+="" +--------+="" a="" cache="" additions="" references="" v="" <="" pre="">

Parsing JSON from command line using Python

With JSON becoming ubiquitous as an API response format, it is sometimes desirable to parse JSON data from command line. There are multiple tools available in order to do so, and I’m personally using jq, a lightweight and flexible command-line JSON processor. While jq is a fantastic tool, it introduces a dependency and might not be available in all environments.

Python, on the other end, comes preinstalled on most Linux distributions and supports JSON out of the box since version 2.6. It can be used to pretty print or parse data, here are a few examples :

Pretty printing JSON data :

curl -s http://www.telize.com/geoip/46.19.37.108 | python -mjson.tool
{
    "area_code": "0",
    "asn": "AS196752",
    "continent_code": "EU",
    "country": "Netherlands",
    "country_code": "NL",
    "country_code3": "NLD",
    "dma_code": "0",
    "ip": "46.19.37.108",
    "isp": "Tilaa V.O.F.",
    "latitude": 52.5,
    "longitude": 5.75,
    "timezone": "Europe/Amsterdam"
}

A one-liner for parsing simple JSON data :

curl -s http://www.telize.com/geoip/46.19.37.108 | python -c 'import sys, json; print json.load(sys.stdin)["country"]'
Netherlands

A one-liner for parsing JSON arrays :

curl -s http://api.statdns.com/x/46.19.37.108 | python -c 'import sys, json; print json.load(sys.stdin)["answer"][0]["rdata"]'
statdns.net.

Getting started with OpenWrt

My last encounter with embedded Linux distributions dates back to August 2005, leveraging Familiar Linux on an iPaq in order to port an Apache + PHP + MySQL application to a thttpd + PHP (using CGI) one to be usable directly on what was a widely available PDA at the time.

Clearly, it had been a while and I’m now doing some experiments with OpenWrt (an embedded Linux distribution targeted at residential gateways and routers) on a TP-LINK TL-MR3020 pocket router. The main reason behind this is that I’ve been focusing a lot on DNS during the last few years and have been wanting to do DNSSEC validation at the router level, which I will document in another article.

OpenWrt 12.09 MOTD

The TL-MR3020 embeds an Atheros AR7240 CPU (MIPS architecture) running at 400MHz, 32MiB of RAM, 4MiB of Flash, a 100 MBit Ethernet port, and most importantly an USB 2.0 port allowing to plug a flash drive and extend storage space.

Here is the result of running cat /proc/cpuinfo on this device :

root@OpenWrt:~# cat /proc/cpuinfo

system type             : Atheros AR9330 rev 1
machine                 : TP-LINK TL-MR3020
processor               : 0
cpu model               : MIPS 24Kc V7.4
BogoMIPS                : 265.42
wait instruction        : yes
microsecond timers      : yes
tlb_entries             : 16
extra interrupt vector  : yes
hardware watchpoint     : yes, count: 4, address/irw mask: [0x0000, 0x0008, 0x0008, 0x0730]
ASEs implemented        : mips16
shadow register sets    : 1
kscratch registers      : 0
core                    : 0
VCED exceptions         : not available
VCEI exceptions         : not available

LuCI web interface

One of the best part of using OpenWrt instead of stock firmware is the gorgeous interface, which is uncluttered and responsive.

Here are some captures :

OpenWrt - Overview

OpenWrt - Realtime Traffic

Installing packages

OpenWrt uses the opkg package manager (originally forked from ipkg) which makes it really easy to install software packages.

opkg must have one sub-command argument
usage: opkg [options...] sub-command [arguments...]
where sub-command is one of:

Package Manipulation:
    update                  Update list of available packages
    upgrade           Upgrade packages
    install           Install package(s)
    configure         Configure unpacked package(s)
    remove     Remove package(s)
    flag        Flag package(s)
    =hold|noprune|user|ok|installed|unpacked (one per invocation)

Using an USB flash drive to extend storage space

First, we need to install required packages and kernel modules in order to be able to use and mount USB external storage devices.

For ext4 support :

opkg install block-mount kmod-usb-storage kmod-fs-ext4

Then we can configure OpenWrt to use a root filesystem on external storage (extroot) by using one of the following methods : external root (pivot root), or external overlay (pivot overlay).

Swap

This device has 32Mb of RAM which can easily be exhausted, so we are going to set the swap space to double that amount :

dd if=/dev/zero of=/swapfile count=65536 bs=1k
mkswap /swapfile
swapon /swapfile

We can check that swap is enabled using the free command :

             total         used         free       shared      buffers
Mem:         29212        23548         5664            0         2032
-/+ buffers:              21516         7696
Swap:        65532            0        65532

To activate the swap file at boot time, we need to add the following directives in /etc/config/fstab :

config swap
        option device    /swapfile

And enable fstab autostart at boot time :

/etc/init.d/fstab enable

Things to do on OpenWrt

IRCing from the console is something I have fond memories of, and I vividly remember using IrcII and especially BitchX (which came packed with some really nice ASCii and ANSi art logos). Nowadays I still use IRC from time to time to get my dose of nostalgia and I’m using irsii for this purpose.

Irssi on OpenWrt

There are some other IRC related packages available such as psyBNC (a permanent IRC-Bouncer) and bitlbee (an IRC instant messaging gateway).

Download service

OpenWrt comes with several packages for downloading torrents directly from command line : ctorrent (which for some reasons keeps crashing my router), rtorrent, or transmission-cli.

rTorrent on OpenWrt

Media server

Thanks to MiniDLNA, it is possible to stream audio, pictures and video content to DLNA/UPnP AV compatible devices such as Smart TVs.

Here is a minimal configuration file : /etc/minidlna.conf

port=8200
network_interface=br-lan

friendly_name=OpenWrt

db_dir=/mnt/sda1/minidlna/db

media_dir=A,/mnt/sda1/audio
media_dir=P,/mnt/sda1/pictures
media_dir=V,/mnt/sda1/videos

Serving static content

Nginx, my favorite web server, is of course available as a package on OpenWrt and can be leveraged to serve static content. As the LuCI web interface is running on port 80, we need to change nginx configuration in order to use a different port, for example 8080.

Here is a minimal configuration file : /etc/nginx/nginx.conf

user nobody nogroup;
worker_processes 1;

events {
    worker_connections 1024;
}


http {
    include mime.types;

    server {
        listen 8080;
        server_name localhost;

        location / {
            root /etc/nginx/html;
            index index.html;
        }
    }
}

Networking stuff

Being a router targeted Linux distribution, there are of course lots of interesting network related packages available such as DNS servers, IPv6 tunnels, VPN software, and so far and so on. I will post some detailed articles about using OpenWrt for such purposes in the following weeks, so stay tuned!