8.8.8.8や1.1.1.1より速い宅内DNSサーバーの作り方

こんにちは。日々是発見が楽しみな西山です。

先日、ご自宅のルーター、キャッシュDNSの性能は足りてますか?
というブログを投稿したのですが、
「キャッシュDNSサーバーを自作してみるって書いてるのに作り方書いてない」
という至極もっともなツッコミをいただきました。
なので、今回はUnboundでキャッシュDNSサーバーを作る記事を書こうと思います。

私の自宅環境ではProxmox VEが稼働しているので、例によってLXCコンテナを立てて構築しましたが、自宅用のキャッシュDNS程度ならメモリやCPUリソースは少なくていいので、例えば旧世代のラズパイでも全然かまいません。
また、QNAPやSynologyのNASでDockerを動かせるモデルがあるので、そういう環境があればUnboundのDockerコンテナを作ってもいいかと思います。

インストール

AlmaLinux9のコンテナを立て、Unboundをインストールしました。

dnf install unbound

Linux系OSなら大体はリポジトリからUnboundをインストールできるかと思います。

cd /etc/unbound

でUnboundのコンフィグディレクトリに移動し、主に「unbound.conf」を変更していきます。
私はカスタマイズを見やすくするため、/etc/unbound/unbound.conf はわざと

include: /etc/unbound/conf.d/*.conf

の1行だけに書き換え、/etc/unbound/conf.d/ 配下に設定ファイルを全部まとめています(もちろん、/etc/unbound/unbound.conf をそのまま編集しても構いません)。
また、宅内のPCやスマホからIPv6 DNSとして参照できるよう設定していますが、そこの設定がけっこう複雑になりますので、割り切ってIPv4だけで利用できるようにしてもいいと思います。

基本設定

base.conf というファイルに、ネットワーク設定や参照先DNSサーバーなど、基本的な設定をまとめました。

server:
    interface: 0.0.0.0
    interface: ::0
    interface: 2001:db8::2

unboundデーモンがクエリを受け付けるインターフェースを指定しています。
IPアドレス「2001:db8::1」は、このDNSサーバーの固定IPv6アドレスです。

    prefer-ip6: yes

    access-control: 0.0.0.0/0 refuse
    access-control: 127.0.0.0/8 allow
    access-control: 192.168.0.0/24 allow
    access-control: ::0/0 refuse
    access-control: ::1/128 allow
    access-control: 2001:db8::/64 allow
    access-control: fe80::/64 allow

「access-control:」はローカルIPからのクエリのみ許可し、他は拒否する設定です。
特にIPv6でインターネット接続可能な場合、ルーター等の環境によっては外部ネットワークから到達できてしまう場合がありますので、ファイアウォールと組み合わせてしっかり止めておきましょう。

    interface-automatic: yes

    module-config: "iterator"

    val-permissive-mode: yes

    hide-version: yes
    hide-identity: yes

    root-hints: "/var/lib/unbound/root.hints"

「root-hints:」の行で、最新のルートヒントを利用しています。
これを指定した場合、下記コマンドでルートヒントを取得する必要があります。

curl -o /var/lib/unbound/root.hints https://www.internic.net/domain/named.cache

「root-hints:」の行を省略すると、Unbound組み込みのルートヒントが利用されます。組み込みルートヒントでも別に問題はありません。

    logfile: "/var/log/unbound/unbound.log"
    use-syslog: no
    log-time-ascii: yes

Unboundのログをsyslogとは別に出力します(しなくても構いません)。
この設定をする場合、起動前に /var/log/unbound ディレクトリの作成+オーナーをunboundユーザーに変更する作業が必要です。

forward-zone:
    name: "."
    forward-addr: 2001:a7ff:5f01::a
    forward-addr: 2001:a7ff:5f01:1::a

クエリ問い合わせ先のキャッシュDNSを指定します。
私は自宅のネットワークから一番近く、レイテンシが小さいNTTフレッツ網用のキャッシュDNSを指定しています。

※このアドレスはNTT西日本用のDNSサーバーなので、東日本のフレッツ網では違うアドレスになります。また、NUROやCATV系など、フレッツ以外の回線で接続されている方は各々のプロバイダで指定されたDNSを設定します。
remote-control:
    control-interface: 127.0.0.1
    control-interface: ::1

Unboundのコントロールコマンド「unbound-control」や、APIへの接続許可設定です。デフォルト設定(サーバーローカルからのみ実行可)のままです。

パフォーマンス設定

performance.conf というファイルに、キャッシュサイズやスレッド数など、Unboundの性能に関する設定をまとめました。
(私はチューニングオタクなので、カリッカリに性能を出す設定になってます)

server:
    msg-cache-slabs: 8
    rrset-cache-slabs: 8
    infra-cache-slabs: 8
    key-cache-slabs: 8

    rrset-cache-size: 512m
    msg-cache-size: 128m

    outgoing-num-tcp: 1000
    incoming-num-tcp: 1000

    outgoing-range: 8192
    num-queries-per-thread: 4096

    so-rcvbuf: 4m
    so-sndbuf: 4m

    minimal-responses: yes
    qname-minimisation: yes

    infra-cache-numhosts: 1000000

その他の設定

unbound.conf というファイルを作り、デフォルトコンフィグから変更していない設定をまとめています。

server:
        verbosity: 1
        statistics-interval: 0
        statistics-cumulative: no
        extended-statistics: yes
        num-threads: 4
        interface-automatic: no
        outgoing-port-permit: 32768-60999
        outgoing-port-avoid: 0-32767
        so-reuseport: yes
        ip-transparent: yes
        max-udp-size: 3072
        chroot: ""
        username: "unbound"
        directory: "/etc/unbound"
        log-time-ascii: yes
        harden-glue: yes
        harden-dnssec-stripped: yes
        harden-below-nxdomain: yes
        harden-referral-path: yes
        qname-minimisation: yes
        aggressive-nsec: yes
        unwanted-reply-threshold: 10000000
        prefetch: yes
        prefetch-key: yes
        rrset-roundrobin: yes
        minimal-responses: yes
        trust-anchor-signaling: yes
        root-key-sentinel: yes
        val-clean-additional: yes
        serve-expired: yes
        val-log-level: 1
        ipsecmod-enabled: no
        ipsecmod-hook:/usr/libexec/ipsec/_unbound-hook
python:
remote-control:
        control-enable: yes
        server-key-file: "/etc/unbound/unbound_server.key"
        server-cert-file: "/etc/unbound/unbound_server.pem"
        control-key-file: "/etc/unbound/unbound_control.key"
        control-cert-file: "/etc/unbound/unbound_control.pem"

configを設定し終わったら、起動前に

unbound-checkconf

で文法チェックを行い、OKならば

systemctl start unbound

で起動します。

無事起動したら、DNS問い合わせができるか、PCからnslookupやdigで確認します。

dig www.google.com @192.168.0.2
dig www.google.com @2001:db8::2

ここまで動作確認ができたら、宅内ルーターのDHCP設定を開いて、DNSサーバーのアドレスを今回作ったサーバーに変更しましょう。
メーカーや機種ごとに設定箇所が違うのでお手持ちの製品マニュアルを参照願いたいですが、メジャーどころだとこの辺でしょうか。

BUFFALO:DNS解決が極端に遅くなり...

NEC:DNSサーバを設定する

ASUS:[WiFiルーター] DHCPサーバーの設定方法

さらにスピードを追い求めるチューニング

上記の「performance.conf」でスレッド数・セッション数を相当上げているので、そのままではOSデフォルトのポートオープン数制限(ファイルディスクリプタ上限)に当たってしまいます。
対策として、unboundデーモンの起動時に上限値を変更します。

AlmaLinuxの場合、unboundの起動ユニットファイルは /lib/systemd/system/unbound.service になります。
ファイルをエディタで開き、[Service] セクションに下記の一行を追記します。

ExecStartPre=/bin/ulimit -HSn 65536

「デーモンを起動する前に、unboundプロセスのファイルディスクリプタ上限を65536に変更する」というコマンドです。
[Service] セクション全体はこうなります。

[Service]
Type=simple
EnvironmentFile=-/etc/sysconfig/unbound
ExecStartPre=/bin/ulimit -HSn 65536
ExecStartPre=/usr/sbin/unbound-checkconf
ExecStartPre=/bin/bash -c 'if [ ! "$DISABLE_UNBOUND_ANCHOR" == "yes" ]; then /usr/sbin/unbound-anchor -a /var/lib/unbound/root.key -c /etc/unbound/icannbundle.pem -f /etc/resolv.conf -R; else echo "Updates of root keys with unbound-anchor is disabled"; fi'
ExecStart=/usr/sbin/unbound -d $UNBOUND_OPTIONS
ExecReload=/usr/sbin/unbound-control reload

ユニットファイルを変更したので、内容を反映してunboundを再起動します。

systemctl daemon-reload
systemctl restart unbound

自作DNSサーバーの力は……

namebenchというDNSベンチマークソフトで、自作キャッシュDNSサーバーとGoogle DNS、Cloudflare DNSのスピード比較をしてみました。
その結果がこちらの画像です。

スクリーンショット 2023-10-12 141030-1-1

スクリーンショット 2023-10-12 141030-2
性能差がスコアで現れると気持ちがいいですね~。