Easy bind dns log analyzer

Any real-programmer ™ should be able to memorize all the static IPs in his network and should feel more comfortable using the IP to access the LAN resources, instead of those user friendly URL things. Of course, not everyone is a real-programmer ™, and these people usually drive crazy the poor guys who do remember all the static IPs in the office. For them, the DNS was invented.

Some time ago I set up a bind9 on a spare server I had. Easy job, a side project of a boring late night. Of course, there was no point in just setting up my own TLD; I now had the power to have fun with DNS poisoning, and I could also create nice reports of the queries to the DNS server.

This is a little bit how it felt.

Having fun with a DNS might be the topic for another post, for this one I’ll just focus on how to get query statistics from a bind server.

After grepping the web a little bit, I found a rather disconcerting lack of bind log analyzers. I just wanted a list of the most popular queries, as well as the ability to group the log by it’s IP (and may be even to get the computer’s SMB name). Couldn’t have asked for a better chance to flex a little bit my Ruby-foo, and here is the hackish result for whoever may want to do something similar:


class Hits
	def initialize(n,v,k=nil)

	def n() @n; end
	def v() @v; end
	def k() @k; end

	def <(o) @n < o.n; end

if ARGV.length == 0 then
	puts "Usage: dns.rb DNS_LOG [--domains] [--ip [--no-samba]]"
	puts "	--domains will list all queried domains"
	puts "	--ip will list every query gruoped by IP"

@domains = @ip = @nosamba = false
ARGV.each { |arg|
	case arg
		when '--domains' then @domains = true
		when '--ip' then @ip = true
		when '--no-samba' then @nosamba = true

fp = File.open ARGV[0]

queries_by_ip = {}
queries_by_domain = {}

fp.each_line { |l|
	v = l.split(' ')
	if not (v.nil? or v.length < 4) then
		ip = v[1].split('#')[0]
		query = v[3]

		if queries_by_domain[query].nil? then queries_by_domain[query] = 0 end
		queries_by_domain[query] += 1

		if queries_by_ip[ip].nil? then queries_by_ip[ip] = [] end
		queries_by_ip[ip].push query

if @domains then
	hits = []
	queries_by_domain.each { |k,v|
		hits.push Hits.new(v, k)

	hits.sort!.reverse!.each { |h|
		puts h.v + " has " + h.n.to_s + " hits"

if @ip then
	lst = []
	queries_by_ip.each { |ip,queries|
		lst.push Hits.new(queries.length, ip, queries)

	lst.sort!.reverse!.each { |h|
		puts "Report for " + h.v + ", Samba addr:"
		if not @nosamba then Kernel.system "nmblookup -A " + h.v end
		puts "Requested " + h.n.to_s + " URLs:"
		h.k.uniq.each { |url|
			puts "t" + url
		puts "."
		puts "."

7 Comments on “Easy bind dns log analyzer”

  1. GLR says:

    It seems they are some issues with HTML codes in this code, can you please repost a cleaned version ?
    I would be interested to test it…

  2. Every single time I migrate blogs… anyway, it should work now. Haven’t tested it, though. Let me know if you found it useful.

    • GLR says:

      Thx. But I’m still encountering a problem :

      ./bind-dns-log-analyzer:14: syntax error, unexpected tIVAR, expecting tCOLON2 or ‘.’
      def (o) @n o.n; end
      ./bind-dns-log-analyzer:15: syntax error, unexpected kEND, expecting $end

  3. You’re right, looks like the method name on line 14 is gone. I assume it’s something like “def <(o) @n instead. Unfortunately I don’t have a dns bind log to test it… let me know if you find out which one it is.

  4. Thanks mcdir. That project looks like much less of a hack than mine. In my defense, it didn’t exist back then 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s