dnstapをUnboundと使ってみる方法

# はじめに
dnstapというものの存在を知ったので、一先ずどういったものか試してみます。
せっかくなので導入方法の日本語情報を残します。

* dnstapとは
    * http://dnstap.info/
    * DNSソフトウェアのための構造化された柔軟なバイナリログ
    * 高速、低負荷
    * DNSリクエスト、応答をロギング可能
    * 必要ライブラリ等を準備し、対応したDNSソフトウェアで設定を行うことで取得が可能

    ---

* 手順について
    * 基本的には http://dnstap.info/ に記載の通りです
    * パスの恒久的な設定やインストール先の変更は必要に応じて実施してください
    * 手順の簡易化のためrootアカウントで作業を行っております
        * 正しくsudoコマンドを使用すればrootアカウントで操作（ログイン）する必要はありません

# 使用した環境
* OS
    * CentOS 6.6
        * SELINUX=disabled
* DNS Server
    * Unbound 1.5.10
* OTHER
    * fstrm 0.2.0
    * protobuf-c 1.2.1

# 導入手順
## fstrmインストール
### fstrmに必要なパッケージのインストール 
* libeventは2以上のバージョン[^1]
* autoconfは2.64以上のバージョン[^2]

[^1]: エラー出力

 ```none:./configure@fstrm 
 configure.ac:1: error: Autoconf version 2.64 or higher is required
 ```
[^2]: エラー出力

 ```none:./configure@fstrm
 configure: error: Package requirements (libevent >= 2) were not met:
 No package 'libevent' found
 ```

```
# yum install automake libtool libevent2-devel.x86_64[^1]
# rpm -qa | grep -e automake -e libtool -e libevent2
automake-1.11.1-4.el6.noarch
libevent2-2.0.21-2.el6.x86_64
libtool-2.2.6-15.5.el6.x86_64
libevent2-devel-2.0.21-2.el6.x86_64

# cd /usr/local/src/
# wget http://ftp.gnu.org/gnu/autoconf/autoconf-2.69.tar.gz
# tar xf autoconf-2.69.tar.gz
# cd autoconf-2.69
# ./configure
# make; echo $?
（略）
0
# make install
# autoconf --version | head -n 1
autoconf (GNU Autoconf) 2.69
```
### fstrmインストール
```
# cd /usr/local/src/
# git clone https://github.com/farsightsec/fstrm.git
# cd fstrm/
# ./autogen.sh
# ./configure
（略）
    fstrm 0.2.0

        compiler:               gcc -std=gnu99
        cflags:                 -g -O2 -pthread
        ldflags:
        libs:                   -lrt

        prefix:                 /usr/local
        sysconfdir:             ${prefix}/etc
        libdir:                 ${exec_prefix}/lib
        includedir:             ${prefix}/include
        pkgconfigdir:           ${libdir}/pkgconfig
# make; echo $?
（略）
0
# make install
# echo '/usr/local/lib' >> /etc/ld.so.conf.d/usr-local-lib.conf
# ldconfig
# ldconfig -p | grep fstrm
	libfstrm.so.0 (libc6,x86-64) => /usr/local/lib/libfstrm.so.0
	libfstrm.so (libc6,x86-64) => /usr/local/lib/libfstrm.so
```
## protobuf-cインストール
### protobuf-cに必要なパッケージのインストール
* protobufは2.6.0以上のバージョン[^3]
* protobufの最新版（3.1.0）をインストールしようとするとmakeが通りませんでした
    * 力不足で解決できず、とりあえず古いバージョンを採用したところ先に進めました

```
# yum install gcc-c++ unzip
# rpm -qa | grep -e gcc-c++ -e unzip
gcc-c++-4.4.7-17.el6.x86_64
unzip-6.0-4.el6.x86_64
```
```
# cd /usr/local/src/
# wget https://github.com/google/protobuf/releases/download/v2.6.1/protobuf-2.6.1.tar.gz
# tar xf protobuf-2.6.1.tar.gz
# cd protobuf-2.6.1
# ./autogen.sh
# ./configure
# make; echo $?
（略）
0
# make check
（略）
Testing output to jar...
./google/protobuf/compiler/zip_output_unittest.sh: line 76: jar: コマンドが見つかりません
Warning:  'jar' command not available.  Skipping test.
PASS
PASS: google/protobuf/compiler/zip_output_unittest.sh
==================
All 4 tests passed
==================
（略）
# make install
# ldconfig
# protoc --version
libprotoc 2.6.1
# ls -l /usr/local/lib/pkgconfig/protobuf*
-rw-r--r-- 1 root root 397 12月 12 09:06 2016 /usr/local/lib/pkgconfig/protobuf-lite.pc
-rw-r--r-- 1 root root 412 12月 12 09:06 2016 /usr/local/lib/pkgconfig/protobuf.pc
# echo $PKG_CONFIG_PATH

# export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
# echo $PKG_CONFIG_PATH
/usr/local/lib/pkgconfig
```
### protobuf-cインストール

```
# cd /usr/local/src/
# git clone https://github.com/protobuf-c/protobuf-c.git
# cd protobuf-c/
# ./autogen.sh
# ./configure
（略）
    protobuf-c 1.2.1

        CC:                     gcc -std=gnu99
        CFLAGS:                 -g -O2
        CXX:                    g++
        CXXFLAGS:               -g -O2
        LDFLAGS:
        LIBS:

        prefix:                 /usr/local
        sysconfdir:             ${prefix}/etc
        libdir:                 ${exec_prefix}/lib
        includedir:             ${prefix}/include
        pkgconfigdir:           ${libdir}/pkgconfig

        bigendian:              no
        protobuf version:       libprotoc 2.6.1
# make; echo $?
（略）
0
# make install
# protoc-c --version
protobuf-c 1.2.1
libprotoc 2.6.1
```
## Unboundインストール
### Unboundに必要なパッケージのインストール
```
# yum install openssl-devel expat-devel
# rpm -qa | grep -e openssl -e expat
expat-devel-2.0.1-13.el6_8.x86_64
expat-2.0.1-13.el6_8.x86_64
openssl-devel-1.0.1e-48.el6_8.3.x86_64
openssl-1.0.1e-48.el6_8.3.x86_64
```
### Unboundインストール
* Unboundは1.5.0以上のバージョンでdnstapに対応

```
# cd /usr/local/src/
# wget http://unbound.net/downloads/unbound-1.5.10.tar.gz
# tar xf unbound-1.5.10.tar.gz
# cd unbound-1.5.10
# ./configure --enable-dnstap
# make; echo $?
0
# make install
# unbound -h | grep Version
Version 1.5.10
# ldconfig
# ldconfig -p | grep unbound
	libunbound.so.2 (libc6,x86-64) => /usr/local/lib/libunbound.so.2
	libunbound.so (libc6,x86-64) => /usr/local/lib/libunbound.so
```

## Unbound設定・起動
* 詳細な設定や、パフォーマンスチューニングは今回は対象外
    * 参考情報
    * http://unbound.jp
    * http://dnsops.jp/event/20160624/unbound.pdf

### ユーザ、グループの作成

```
# groupadd --system unbound
# useradd --system -g unbound unbound
# grep unbound /etc/group /etc/passwd
/etc/group:unbound:x:498:
/etc/passwd:unbound:x:498:498::/home/unbound:/bin/bash
# chown unbound:unbound /usr/local/etc/unbound
```
### trust anchor（DNSSEC)の準備
```
# sudo -u unbound mkdir /usr/local/etc/unbound/key
# sudo -u unbound /usr/local/sbin/unbound-anchor -a /usr/local/etc/unbound/key/root.key
# ls -l /usr/local/etc/unbound/key/root.key
-rw-r--r-- 1 unbound unbound 759 12月 12 09:19 2016 /usr/local/etc/unbound/key/root.key
```
### リモートコントロールの準備
```
# sudo -u unbound `which unbound-control-setup`
# ls -l /usr/local/etc/unbound/*{.key,.pem}
-rw-r----- 1 unbound unbound 2455 12月 12 09:38 2016 /usr/local/etc/unbound/unbound_control.key
-rw-r----- 1 unbound unbound 1330 12月 12 09:38 2016 /usr/local/etc/unbound/unbound_control.pem
-rw-r----- 1 unbound unbound 2455 12月 12 09:38 2016 /usr/local/etc/unbound/unbound_server.key
-rw-r----- 1 unbound unbound 1318 12月 12 09:38 2016 /usr/local/etc/unbound/unbound_server.pem
```
### dustup用ディレクトリ作成
```
# sudo -u unbound mkdir /usr/local/etc/unbound/dnstap/
# ls -ld /usr/local/etc/unbound/dnstap/
drwxr-xr-x 2 unbound unbound 4096 12月 12 09:51 2016 /usr/local/etc/unbound/dnstap/
```
### 設定ファイル編集
```
# cp -ip /usr/local/etc/unbound/unbound.conf{,_org}
# ls -l /usr/local/etc/unbound/unbound.conf{,_org}
# vim /usr/local/etc/unbound/unbound.conf
# diff /usr/local/etc/unbound/unbound.conf{,_org}
664c664
< 	 control-enable: yes
---
> 	# control-enable: no
721,727d720
< dnstap:
< 	dnstap-enable: yes
< 	dnstap-socket-path: "/dnstap/dnstap.sock"
< 	dnstap-send-identity: yes
< 	dnstap-send-version: yes
< 	dnstap-log-resolver-response-messages: yes
< 	dnstap-log-client-query-messages: yes
```
### 起動・確認
```
# unbound-control start
# unbound-control status
version: 1.5.10
verbosity: 1
threads: 1
modules: 2 [ validator iterator ]
uptime: 297 seconds
options: control(ssl)
unbound (pid 30584) is running...
# tail /var/log/messages
Dec 12 10:26:10 dnstap unbound: [30584:0] notice: init module 0: validator
Dec 12 10:26:10 dnstap unbound: [30584:0] notice: init module 1: iterator
Dec 12 10:26:10 dnstap unbound: [30584:0] notice: attempting to connect to dnstap socket /dnstap/dnstap.sock
Dec 12 10:26:10 dnstap unbound: [30584:0] notice: dnstap identity field set to "dnstap.localhost"
Dec 12 10:26:10 dnstap unbound: [30584:0] notice: dnstap version field set to "unbound 1.5.10"
Dec 12 10:26:10 dnstap unbound: [30584:0] notice: dnstap Message/RESOLVER_RESPONSE enabled
Dec 12 10:26:10 dnstap unbound: [30584:0] notice: dnstap Message/CLIENT_QUERY enabled
Dec 12 10:26:10 dnstap unbound: [30584:0] info: start of service (unbound 1.5.10).
```

## dnstap（command-line tool）インストール
### go-langインストール
```
# cd /usr/local/
# wget https://storage.googleapis.com/golang/go1.7.4.linux-amd64.tar.gz
# tar xf go1.7.4.linux-amd64.tar.gz
# /usr/local/go/bin/go version
go version go1.7.4 linux/amd64
```
### dnstapインストール
```
# mkdir /usr/local/go/work
# export GOPATH=/usr/local/go/work/
# /usr/local/go/bin/go get -u -v github.com/dnstap/golang-dnstap
# /usr/local/go/bin/go get -u -v github.com/dnstap/golang-dnstap/dnstap
```

## dnstap使用
* ソケットはUnboundがアクセスできる必要があるため、unboundユーザを指定します
* Unboundはデフォルト設定でchrootが設定されるため、それを考慮したパスを指定します

    ```none:unbound.conf（1.5.10）
    chroot: "/usr/local/etc/unbound"
    ```
* dnstapコマンドを使ってUNIXソケットを作成、一先ず標準出力にログを出力

```
# sudo -u unbound /usr/local/go/work/bin/dnstap -u /usr/local/etc/unbound/dnstap/dnstap.sock
dnstap: opened input socket /usr/local/etc/unbound/dnstap/dnstap.sock
dnstap.FrameStreamSockInput: accepted a socket connection
10:37:00.867951 CQ ::1 UDP 29b "dnstap.info." IN A
10:37:00.911424 RR 193.0.14.129 UDP 1097b "." IN NS
10:37:00.946684 RR 193.0.14.129 UDP 814b "dnstap.info." IN A
10:37:01.043128 RR 199.249.121.1 UDP 604b "dnstap.info." IN A
10:37:01.069026 RR 192.5.5.241 UDP 871b "ns6.dnsmadeeasy.com." IN A
10:37:01.075176 RR 192.36.148.17 UDP 871b "ns6.dnsmadeeasy.com." IN AAAA
```

* verboseモードで出力

```
# sudo -u unbound /usr/local/go/work/bin/dnstap -u /usr/local/etc/unbound/dnstap/dnstap.sock -y
dnstap: opened input socket /usr/local/etc/unbound/dnstap/dnstap.sock
dnstap.FrameStreamSockInput: accepted a socket connection
type: MESSAGE
identity: "dnstap.localhost"
version: "unbound 1.5.10"
message:
  type: CLIENT_QUERY
  query_time: !!timestamp 2016-12-12 01:46:46.984486
  socket_family: INET6
  socket_protocol: UDP
  query_address: ::1
  query_port: 48319
  query_message: |
    ;; opcode: QUERY, status: NOERROR, id: 54132
    ;; flags: rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0

    ;; QUESTION SECTION:
    ;dnstap.info.	IN	 A
```

# さいごに
* dnstapはUnbound専用の機能ではなく、例えばISC BINDも対応済みです
    * 複数のDNSソフトウェアを使用する環境においても同じログを得られます
    * https://kb.isc.org/article/AA-01342/0/Using-DNSTAP-with-BIND-9.11.html
* dnstap（command-line tool）は今回使用した go-lang のものの他にも、ISC BINDの dnstap-read や、UnboundのNLnetLabsが提供する dnstap-ldns など多数の実装がある模様
* UNIXソケットの作成は fstrm_capture コマンドでも行うことができ、ファイルに出力を行う際のローテーと間隔（秒）も指定することができる模様
* 他のDNSソフトウェアでの使用や、クエリログとの負荷具合の比較、各種ツールの使い勝手の比較、実際の運用方法の検討といったテーマはまたの機会があれば

# おまけ
* 設定の不備で上手くログが取得できないことがあったが、今回は以下のstraceコマンドで原因が判明しました
    * （No such file・・・？　lsしたら見えるんだけどな・・・）
    * 答え：chrootされていることを忘れ、ソケットのパスを誤って指定していた

```
# strace -f -p `pgrep unbound` 2>&1 | grep dnstap
[pid 30585] connect(12, {sa_family=AF_LOCAL, sun_path="/dnstap/dnstap.sock"}, 110) = -1 ENOENT (No such file or directory)
[pid 30585] connect(12, {sa_family=AF_LOCAL, sun_path="/dnstap/dnstap.sock"}, 110) = -1 ENOENT (No such file or directory)
```

# 参考
* http://dnstap.info
* https://www.slideshare.net/slideshow/embed_code/key/Er7pdJ9wHBQkT8

---
---
---

以下、注釈

[^3]: エラー出力

 ```none:./configure@protobuf-c
 configure: error: Package requirements (protobuf >= 2.6.0) were not met:
 No package 'protobuf' found
 ```