・LAN 内のドメインを権威サーバーとして名前解決する
・それ以外のドメインへのクエリは反復問い合わせする
構成の例です。
現代では、権威サーバーとキャッシュサーバーは分離することが推奨されています。
参考: JPRS トピックス&コラム No.020
https://jprs.jp/related-info/guide/topics-column/no20.html
前提
サーバー IP アドレス: 192.168.182.12
AlmaLinux 9.5
Unbound 1.23.0
SELinux 有効
OSの設定
通信ソケットバッファの拡大のため、以下を実行してカーネルパラメーターを変更します。
詳細については公式のドキュメントを参照してください。
# sysctl -w net.core.rmem_max=4194304 >> /etc/sysctl.conf
# sysctl -w net.core.wmem_max=4194304 >> /etc/sysctl.conf
Unbound の実行ユーザー、グループを追加します。
# groupadd unbound
# useradd -g unbound -s /sbin/nologin unbound
Unboundのインストール
標準のリポジトリにあるバージョンが古いため、前提となるパッケージをインストールし、ソースを入手してビルド、インストールします。
# dnf install gcc openssl-devel libevent-devel expat-devel systemd-devel bison flex
# cd /usr/src
# curl -O https://nlnetlabs.nl/downloads/unbound/unbound-latest.tar.gz
# tar xf unbound-latest.tar.gz
# cd unbound-1.23.0
# ./configure --enable-systemd --with-libevent
# make
# make install
Unbound の初期設定
systemd への登録、Unbound で使用するディレクトリの作成と、所有者の変更を行います。
また、unbound-control
用の証明書と秘密鍵の生成を行います。
ルートゾーンのトラストアンカーも取得します。
Unbound 1.23.0 は RFC5011 に対応しているため、自動的にトラストアンカーを更新します。
[Unit]
Description=Validating, recursive, and caching DNS resolver
Before=nss-lookup.target
Requires=network-online.target
After=network-online.target
[Install]
WantedBy=multi-user.target
[Service]
Type=notify
ExecReload=/usr/sbin/unbound-control reload
ExecStartPre=/usr/local/sbin/unbound-checkconf
ExecStart=/usr/local/sbin/unbound -d -p
NotifyAccess=main
MemoryDenyWriteExecute=true
NoNewPrivileges=true
PrivateDevices=true
PrivateTmp=true
ProtectControlGroups=true
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=false
ProtectProc=invisible
ProtectSystem=strict
RuntimeDirectory=unbound
ConfigurationDirectory=unbound
StateDirectory=unbound
RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK AF_UNIX
RestrictRealtime=true
SystemCallArchitectures=native
SystemCallFilter=~@clock @cpu-emulation @debug @keyring @module mount @obsolete @resources
RestrictNamespaces=yes
LockPersonality=yes
RestrictSUIDSGID=yes
ReadWritePaths=/usr/local/etc/unbound /var/log/unbound
# mkdir /usr/local/etc/unbound/files
# chown unbound:unbound -R /usr/local/etc/unbound/files
# unbound-control-setup
# sudo -u unbound /usr/local/sbin/unbound-anchor -a /usr/local/etc/unbound/files/root.key
ログの設定
空のログファイルを作成し、所有者を変更します。
# mkdir /var/log/unbound
# touch /var/log/unbound/unbound.log
# chown -R unbound:unbound /var/log/unbound/
ログローテーションの設定を追加します。
/var/log/unbound/unbound.log
{
daily
rotate 7
missingok
create
dateyesterday
postrotate
unbound-control log_reopen
endscript
}
Unboundの設定
以下の定義ファイルを作成します。
server:
chroot: ""
interface: 192.168.182.12
interface: 127.0.0.1
port: 53
logfile: /var/log/unbound/unbound.log
log-time-ascii: yes
# サーバーの CPU コア数を設定する
num-threads: 4
msg-cache-slabs: 4
rrset-cache-slabs: 4
infra-cache-slabs: 4
key-cache-slabs: 4
# サーバーの CPU コア数を設定する項目終了
so-reuseport: yes
outgoing-num-tcp: 1000
incoming-num-tcp: 1000
msg-cache-size: 128m
rrset-cache-size: 256m
num-queries-per-thread: 2048
do-ip4: yes
do-ip6: no
do-daemonize: no
access-control: 127.0.0.0/8 allow # ACL を設定
access-control: 192.168.182.0/24 allow
access-control: 192.168.207.0/24 allow
private-address: 192.168.0.0/16 # 左記を含む回答は破棄される DNS rebinding 対策
private-address: 169.254.0.0/16
private-address: fd00::/8
private-address: fe80::/10
private-domain: "example.com" # private-address を回答に含めるドメインを列挙
local-zone: "182.168.192.in-addr.arpa" transparent # 逆引きゾーンの数だけ列挙
auto-trust-anchor-file: "/usr/local/etc/unbound/files/root.key"
hide-identity: yes
hide-version: yes
remote-control:
control-enable: yes
control-interface: 127.0.0.1
auth-zone:
name: "example.com"
fallback-enabled: yes
zonefile: /usr/local/etc/unbound/example.com.zone
auth-zone:
name: "182.168.192.in-addr.arpa"
fallback-enabled: yes
zonefile: /usr/local/etc/unbound/182.168.192.in-addr.arpa.zone
組み込み以外のルートヒントファイルを使用する場合は、定義ファイルに以下を追加します。
server:
(中略)
+ root-hints: "<ルートヒントファイルの絶対パス>"
(後略)
ゾーンファイルの作成
以下のようなゾーンファイルを作成します。
$ORIGIN example.com.
$TTL 3600
@ IN SOA dns.example.com. root.example.com. (
1 ; Serial
900 ; refresh
600 ; retry
86400 ; expire
900 ; minimum
)
@ IN NS dns.example.com.
dns IN A 192.168.182.12
dhcp IN A 192.168.182.15
$ORIGIN 182.168.192.in-addr.arpa.
$TTL 3600
@ IN SOA dns.example.com. root.example.com. (
1 ; Serial
900 ; refresh
600 ; retry
86400 ; expire
900 ; minimum
)
@ IN NS dns.example.com.
12 IN PTR dns.example.com.
15 IN PTR dhcp.example.com.
以下のコマンドにて設定ファイルのチェックを行います。
# unbound-checkconf
Unbound の起動
DNS の着信接続を許可し、Unboundを起動します。
# firewall-cmd --add-service=dns --permanent
# firewall-cmd --reload
# systemctl enable unbound
# systemctl start unbound
確認
以下の通り解決できます。
# dig @192.168.182.12 example.com.
; <<>> DiG 9.18.30-0ubuntu0.22.04.2-Ubuntu <<>> @192.168.182.12 example.com.
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 45700
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com. IN A
;; AUTHORITY SECTION:
example.com. 900 IN SOA dns.example.com. root.example.com. 1 900 600 86400 900
;; Query time: 10 msec
;; SERVER: 192.168.182.12#53(192.168.182.12) (UDP)
;; WHEN: Wed May 07 12:04:27 JST 2025
;; MSG SIZE rcvd: 85
# dig @192.168.182.12 example.com. NS
; <<>> DiG 9.18.30-0ubuntu0.22.04.2-Ubuntu <<>> @192.168.182.12 example.com. NS
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 54709
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 2
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com. IN NS
;; ANSWER SECTION:
example.com. 3600 IN NS dns.example.com.
;; ADDITIONAL SECTION:
dns.example.com. 3600 IN A 192.168.182.12
;; Query time: 0 msec
;; SERVER: 192.168.182.12#53(192.168.182.12) (UDP)
;; WHEN: Wed May 07 12:04:41 JST 2025
;; MSG SIZE rcvd: 74
# dig @192.168.182.12 dns.example.com.
; <<>> DiG 9.18.30-0ubuntu0.22.04.2-Ubuntu <<>> @192.168.182.12 dns.example.com.
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19297
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;dns.example.com. IN A
;; ANSWER SECTION:
dns.example.com. 3600 IN A 192.168.182.12
;; Query time: 0 msec
;; SERVER: 192.168.182.12#53(192.168.182.12) (UDP)
;; WHEN: Wed May 07 12:05:01 JST 2025
;; MSG SIZE rcvd: 60
# dig @192.168.182.12 -x 192.168.182.15
; <<>> DiG 9.18.30-0ubuntu0.22.04.2-Ubuntu <<>> @192.168.182.12 -x 192.168.182.15
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 468
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;15.182.168.192.in-addr.arpa. IN PTR
;; ANSWER SECTION:
15.182.168.192.in-addr.arpa. 3600 IN PTR dhcp.example.com.
;; Query time: 0 msec
;; SERVER: 192.168.182.12#53(192.168.182.12) (UDP)
;; WHEN: Wed May 07 12:05:23 JST 2025
;; MSG SIZE rcvd: 86
# dig @192.168.182.12 google.com.
; <<>> DiG 9.18.30-0ubuntu0.22.04.2-Ubuntu <<>> @192.168.182.12 google.com.
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 52179
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;google.com. IN A
;; ANSWER SECTION:
google.com. 300 IN A 172.217.175.14
;; Query time: 180 msec
;; SERVER: 192.168.182.12#53(192.168.182.12) (UDP)
;; WHEN: Wed May 07 12:05:49 JST 2025
;; MSG SIZE rcvd: 55
DNSSEC 検証も正しく成功・失敗しています。
# delv @192.168.182.12 jprs.co.jp. +vtrace
;; fetch: jprs.co.jp/A
;; validating jprs.co.jp/A: starting
;; validating jprs.co.jp/A: attempting positive response validation
;; fetch: jprs.co.jp/DNSKEY
;; validating jprs.co.jp/DNSKEY: starting
;; validating jprs.co.jp/DNSKEY: attempting positive response validation
;; fetch: jprs.co.jp/DS
;; validating jprs.co.jp/DS: starting
;; validating jprs.co.jp/DS: attempting positive response validation
;; fetch: jp/DNSKEY
;; validating jp/DNSKEY: starting
;; validating jp/DNSKEY: attempting positive response validation
;; fetch: jp/DS
;; validating jp/DS: starting
;; validating jp/DS: attempting positive response validation
;; fetch: ./DNSKEY
;; validating ./DNSKEY: starting
;; validating ./DNSKEY: attempting positive response validation
;; validating ./DNSKEY: verify rdataset (keyid=20326): success
;; validating ./DNSKEY: marking as secure (DS)
;; validating jp/DS: in fetch_callback_dnskey
;; validating jp/DS: keyset with trust secure
;; validating jp/DS: resuming validate
;; validating jp/DS: verify rdataset (keyid=53148): success
;; validating jp/DS: marking as secure, noqname proof not needed
;; validating jp/DNSKEY: in fetch_callback_ds
;; validating jp/DNSKEY: dsset with trust secure
;; validating jp/DNSKEY: verify rdataset (keyid=35821): success
;; validating jp/DNSKEY: marking as secure (DS)
;; validating jprs.co.jp/DS: in fetch_callback_dnskey
;; validating jprs.co.jp/DS: keyset with trust secure
;; validating jprs.co.jp/DS: resuming validate
;; validating jprs.co.jp/DS: verify rdataset (keyid=13611): success
;; validating jprs.co.jp/DS: marking as secure, noqname proof not needed
;; validating jprs.co.jp/DNSKEY: in fetch_callback_ds
;; validating jprs.co.jp/DNSKEY: dsset with trust secure
;; validating jprs.co.jp/DNSKEY: verify rdataset (keyid=63574): success
;; validating jprs.co.jp/DNSKEY: marking as secure (DS)
;; validating jprs.co.jp/A: in fetch_callback_dnskey
;; validating jprs.co.jp/A: keyset with trust secure
;; validating jprs.co.jp/A: resuming validate
;; validating jprs.co.jp/A: verify rdataset (keyid=43611): success
;; validating jprs.co.jp/A: marking as secure, noqname proof not needed
; fully validated
jprs.co.jp. 300 IN A 117.104.133.165
jprs.co.jp. 300 IN RRSIG A 8 3 300 20250604023001 20250505023001 43611 jprs.co.jp. GNkmT72CLP6GLbPBoVCUNYDKJKdNQqEoT7mBgXr+L4sPVjh1K9oCttS6 eQrBrfBpwFQwSjOmJKk1VcN3aclxF2xd2s1UnOH8tmhKkj0tm16J1GY5 +oLQ+yOwI7MRDLZkevEnbdSjiYDI/EWOavyzEt707VizoD0JsaUTbdfF 2QI=
# delv @192.168.182.12 dnssec-failed.mufj.jp. +vtrace
;; fetch: dnssec-failed.mufj.jp/A
;; resolution failed: SERVFAIL
補足(フォワーダーとして使用する場合)
フォワーダーとして使用する場合は以下のようにします。(Google Public DNS に転送する例)
(前略)
remote-control:
control-enable: yes
control-interface: 127.0.0.1
+ forward-zone:
+ name: "."
+ forward-addr: 8.8.8.8
auth-zone:
name: "example.com"
fallback-enabled: yes
zonefile: /usr/local/etc/unbound/example.com.zone
auth-zone:
name: "182.168.192.in-addr.arpa"
fallback-enabled: yes
zonefile: /usr/local/etc/unbound/182.168.192.in-addr.arpa.zone
補足(権威サーバーへ転送する場合)
権威サーバーにクエリを転送したい場合は、stub-zone
を使用します。
定義ファイルは以下のようになります。
192.168.182.10
上の権威サーバーは以下記事同等のものとします。
server:
chroot: ""
interface: 192.168.182.12
interface: 127.0.0.1
port: 53
logfile: /var/log/unbound/unbound.log
log-time-ascii: yes
# サーバーの CPU コア数を設定する
num-threads: 4
msg-cache-slabs: 4
rrset-cache-slabs: 4
infra-cache-slabs: 4
key-cache-slabs: 4
# サーバーの CPU コア数を設定する項目終了
so-reuseport: yes
outgoing-num-tcp: 1000
incoming-num-tcp: 1000
msg-cache-size: 128m
rrset-cache-size: 256m
num-queries-per-thread: 2048
do-ip4: yes
do-ip6: no
do-daemonize: no
access-control: 127.0.0.0/8 allow # ACL を設定
access-control: 192.168.182.0/24 allow
access-control: 192.168.207.0/24 allow
private-address: 192.168.0.0/16 # 左記を含む回答は破棄される DNS rebinding 対策
private-address: 169.254.0.0/16
private-address: fd00::/8
private-address: fe80::/10
private-domain: "example.com" # private-address を回答に含めるドメインを列挙
+ local-zone: "example.com" transparent
local-zone: "182.168.192.in-addr.arpa" transparent # 逆引きゾーンの数だけ列挙
auto-trust-anchor-file: "/usr/local/etc/unbound/files/root.key"
hide-identity: yes
hide-version: yes
+ domain-insecure: "example.com" # 対象ゾーンでの DNSSEC 検証を無効にする
+ domain-insecure: "182.168.192.in-addr.arpa"
remote-control:
control-enable: yes
control-interface: 127.0.0.1
+ stub-zone: # クエリを権威サーバーに転送
+ name: "example.com"
+ stub-addr: 192.168.182.10
+ stub-zone:
+ name: "182.168.192.in-addr.arpa"
+ stub-addr: 192.168.182.10
この設定では、以下のように解決されます。
# dig @192.168.182.12 example.com.
; <<>> DiG 9.18.30-0ubuntu0.22.04.2-Ubuntu <<>> @192.168.182.12 example.com.
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 65394
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com. IN A
;; AUTHORITY SECTION:
example.com. 701 IN SOA dns1.example.com. root.example.com. 0 900 600 86400 900
;; Query time: 0 msec
;; SERVER: 192.168.182.12#53(192.168.182.12) (UDP)
;; WHEN: Mon May 19 09:49:46 JST 2025
;; MSG SIZE rcvd: 86
# dig @192.168.182.12 dns1.example.com.
; <<>> DiG 9.18.30-0ubuntu0.22.04.2-Ubuntu <<>> @192.168.182.12 dns1.example.com.
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4964
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;dns1.example.com. IN A
;; ANSWER SECTION:
dns1.example.com. 3517 IN A 192.168.182.10
;; Query time: 0 msec
;; SERVER: 192.168.182.12#53(192.168.182.12) (UDP)
;; WHEN: Mon May 19 09:49:49 JST 2025
;; MSG SIZE rcvd: 61
# dig @192.168.182.12 -x 192.168.182.10
; <<>> DiG 9.18.30-0ubuntu0.22.04.2-Ubuntu <<>> @192.168.182.12 -x 192.168.182.10
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 31862
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;10.182.168.192.in-addr.arpa. IN PTR
;; ANSWER SECTION:
10.182.168.192.in-addr.arpa. 3542 IN PTR dns1.example.com.
;; Query time: 0 msec
;; SERVER: 192.168.182.12#53(192.168.182.12) (UDP)
;; WHEN: Mon May 19 09:49:51 JST 2025
;; MSG SIZE rcvd: 86