memo.log

技術情報の雑なメモ

BINDのログのパース、解析

自分のドメインの管理を自分のDNSサーバで行っているため、バーストをくらってないかとか、管理外ドメインに対する問い合わせが来ているかいないか等をちょくちょく確認したくなります。(自分用にしか使っていないドメインのため、ほぼクエリは来ていないに等しいですが…)

flunetd+可視化ツール等が流行っていて、それ用に設定してみてもよいのですが、CLIでサーバにログインしない日は無いため、CLI上のツールで見れたらいいと思い、Perlの勉強がてらツールを作成してみました。

https://github.com/kuredev/tools

以下の情報を調べてみます。

  • 1日の総クエリ数
  • 1日の最高qps(query per second)とその時間
  • 1日の最多問い合わせドメイン名とクエリ数
  • 1日の問い合わせ元IPアドレスとクエリ数
出力結果イメージ
1日のクエリ数
2014-8-06: 60 queries/day
2014-8-07: 46 queries/day
2014-8-05: 40 queries/day
2014-8-08: 61 queries/day
2014-8-13: 19 queries/day
2014-8-12: 94 queries/day
2014-8-11: 43 queries/day
2014-8-10: 47 queries/day
2014-8-09: 45 queries/day
2014-7-31: 60 queries/day
2014-8-01: 91 queries/day
2014-8-03: 39 queries/day
2014-8-04: 97 queries/day
2014-7-30: 60 queries/day
2014-8-02: 50 queries/day
------1日の最高qps------
2014-8-06(23:34:48): 1 qps
2014-8-05(09:40:47): 1 qps
2014-8-07(05:24:17): 1 qps
2014-8-08(22:06:05): 6 qps
2014-8-13(09:15:17): 2 qps
2014-8-12(11:11:32): 1 qps
2014-8-11(09:31:40): 2 qps
2014-8-10(16:48:41): 1 qps
2014-8-03(08:03:25): 1 qps
2014-8-01(11:17:53): 1 qps
2014-7-31(21:04:18): 1 qps
2014-8-09(09:40:50): 4 qps
2014-8-04(22:24:34): 1 qps
2014-8-02(01:04:39): 1 qps
2014-7-30(23:45:13): 1 qps
-----1日の最多問い合わせドメイン数とクエリ数------
2014-8-06(kuredev.info): 38 queris/day
2014-8-05(kuredev.info): 19 queris/day
2014-8-07(kuredev.info): 28 queris/day
2014-8-08(kuredev.info): 31 queris/day
2014-8-13(kuredev.info): 6 queris/day
2014-8-10(kuredev.info): 17 queris/day
前提

BINDのバージョン

# rpm -qa | grep bind
bind-9.8.2-0.17.rc1.el6_4.4.x86_64

ログフォーマット

30-Jul-2014 12:43:51.913 queries: client 1.2.3.4#58334: query: hogehoge.com IN A + (1.2.3.4)
モジュールの作成

BINDログ解析用自作モジュール

package BindParse;

use strict;
use warnings;
use Switch;

sub new{
	my $class = shift;
	my @self = @_;
	return bless \@self, $class;
}

sub parse{
	my ($self, $file) = @_;
	open my $fh, '<', $file
		or die "canoot";
	my $queries = [];
	while(my $line = <$fh>){
		my @param = split /\s+/, $line;
		my @param_ip = split /#/, $param[4];
		my $q_ip = $param_ip[0];
		my $q_port = $param_ip[1];
		my @time = split /\./, $param[1];
		my %query = (
				date => $param[0],
				time => $param[1],
				time_s => $time[0],
				q_ip => $q_ip,
				q_port => $q_port,
				q_domain => $param[6],
				q_type => $param[8]);

		push @$queries, \%query;
	}
	close $fh;
	return $queries;
}

sub convertDate{
	my ($self, $str) = @_;
	my @s = split /-/, $str;
	my $result = $s[2]."";
	switch ($s[1]){
		case 'Jan' { $result .= '-1-' }
		case 'Feb' { $result .= '-2-' }
		case "Mar" { $result .= '-3-' }
		case 'Apr' { $result .= '-4-' }
		case 'May' { $result .= '-5-' }
		case 'Jun' { $result .= '-6-' }
		case 'Jul' { $result .= '-7-' }
		case "Aug" { $result .= "-8-" }
		else {}
	}
	$result .= $s[0];
	return $result;
}

1;

parseメソッドは上述のログフォーマットを以下のようにPerlのデータ構造にパースします。

[
          {
            'q_domain' => 'hoge.info',
            'q_type' => 'A',
            'time' => '12:43:51.913',
            'date' => '30-Jul-2014',
            'q_port' => '58334:',
            'q_ip' => '1.2.3.4',
            'time_s' => '12:43:51'
          },
          {
            'q_domain' => 'hoge.info',
            'q_type' => 'A',
            'time' => '14:30:58.053',
            'date' => '30-Jul-2014',
            'q_port' => '56091:',
            'q_ip' => '1.2.3.4',
            'time_s' => '14:30:58'
          },
]

また、convertDateメソッドは日付を人間が見やすい形に変換します。

解析スクリプト

取得する情報別にfor文を回しているので計算量が多いですが、まぁ取得したい情報は上記の4つ以外にも場合によって違うものが出てくると思いますので、特に気にせず書いています。

#!/usr/bin/perl
use strict;
use warnings;
use BindParse;
use Data::Dumper;

my $file = shift;
my $bind = BindParse->new();
my $result = $bind->parse("query.log");

#1日のクエリ数
#1日の最高qps
#1日の最多問い合わせドメイン名とクエリ数
#1日の最多問い合わせIPアドレスとIPアドレス

##############
#1日のクエリ数
print("1日のクエリ数\n");
my %dayps = ();
foreach my $query (@{$result}){
	$dayps{$query->{date}}++;
}
#出力
foreach my $date (keys %dayps){
	print $bind->convertDate($date).": ".$dayps{$date}." queries/day\n";
}

##############
#1日の最高qps
my %qps_info = ();
print("------1日の最高qps------\n");
foreach my $query (@{$result}){
	$qps_info{$query->{date}}{$query->{time_s}}++;
}

my %max = ();
foreach my $date (keys %qps_info){
	my $dateHash_ref = $qps_info{$date};
	my %dateHash = %{$dateHash_ref};
	$max{$date}->{'num'} = 0;
	foreach my $s (keys %dateHash){
		if($max{$date}->{'num'} < $dateHash{$s}){
			$max{$date}->{'time_s'} = $s;
			$max{$date}->{'qps'} = $dateHash{$s};
		}
	}
}

#出力
foreach my $date (keys %max){
	print $bind->convertDate($date)."(".$max{$date}->{'time_s'}."): ".$max{$date}->{'qps'}." qps\n";
}

######################################
#Number of queries of domain

print("-----1日の最多問い合わせドメイン数とクエリ数------\n");
%qps_info = ();
foreach my $query (@{$result}){
	$qps_info{$query->{date}}{$query->{q_domain}}++;
}

%max = ();
my %domain_result = ();
foreach my $date (keys %qps_info){
	my $dateHash_ref = $qps_info{$date};
	my %dateHash = %{$dateHash_ref};
	$max{$date}->{'num'} = 0;
	foreach my $s (keys %dateHash){
		if($max{$date}->{'num'} < $dateHash{$s}){
			$max{$date}->{'domain'} = $s;
			$max{$date}->{'num'} = $dateHash{$s};
		}
	}
}

#出力
foreach my $date (keys %max){
	print $bind->convertDate($date)."(".$max{$date}->{'domain'}."): ".$max{$date}->{'num'}." queris/day\n";
}


###################################
#IP Address Analysis

#parse to Perl Data Structure
print("-----ip info ----\n");
%qps_info = ();
foreach my $query (@{$result}){
	$qps_info{$query->{date}}{$query->{q_ip}}++;
}

#出力
print("---detail\n");
my $ip_ref = {};
my %ip_hash = ();
foreach my $date (keys %qps_info){
	print $bind->convertDate($date)."\n";
	%ip_hash = %{$qps_info{$date}};
	foreach my $ip (keys %ip_hash){
		print " ".$ip.": ".$ip_hash{$ip}." queries\n";
	}
}

参考:
Apacheのログをパースしてみる - はこべブログ ♨ http://hakobe932.hatenablog.com/entry/20080316/1205659704