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:

#!/usr/bin/ruby1.8

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

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

	def <(o) @n < o.n; end
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"
	exit
end

@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
	end
}

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
	end
}

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"
	}
end

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 "."
	}
end
Advertisements