syntax highlight

Tuesday 13 November 2012

Easy bind dns log analyzer

Any real-programmer (tm) 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 (tm), 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.

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: [source lang="ruby"]#!/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

7 comments:

  1. 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...

    ReplyDelete
  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.
    Cheers

    ReplyDelete
  3. 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

    ReplyDelete
  4. 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.

    ReplyDelete
  5. See also http://statdns.nedze.com, https://github.com/mcdir/statdnslog. But they use myskk for data storage (

    ReplyDelete
  6. Thanks mcdir. That project looks like much less of a hack than mine. In my defense, it didn't exist back then :)

    ReplyDelete
  7. I'll just be happy if my program will enjoy anyone. Another demo was made available http://demo.statdns.nedze.com/

    ReplyDelete