NSD でゾーン転送（AXFR / IXFR） with TSIG 

## 前置き
NSD 4.5.0 から IXFR の送信側機能が追加されています。（以前は非対応でした）
https://www.nlnetlabs.nl/news/2022/May/13/nsd-4.5.0-released/

NSD -> NSD の構成です。
プライマリサーバ: `192.168.182.10` は以下の手順で構築されたと仮定します。

https://qiita.com/ahra_/items/75056bc86d56e0bc773e

セカンダリサーバー: `192.168.182.11` の環境は以下の通りです。
OS: Rocky Linux 9.5
NSD 4.12.0
SELinux 有効

## プライマリサーバーの設定
セカンダリサーバーの NS を追加します。
ゾーンファイルの編集時は必ずシリアルナンバーを増加させましょう。
```diff:/etc/nsd/zone/example.com.zone
$ORIGIN example.com.
$TTL 3600
@       IN      SOA     dns1.example.com. root.example.com. (
-                 0          ; Serial
+                 1          ; Serial
                  900        ; refresh
                  600        ; retry
                  86400      ; expire
                  900        ; minimum
)

@                 IN NS        dns1.example.com.
+ @               IN NS        dns2.example.com.
dns1              IN A         192.168.182.10
+ dns2            IN A         192.168.182.11
dns-r             IN A         192.168.182.12
```
```diff:/etc/nsd/zone/182.168.192.in-addr.arpa.zone
$ORIGIN 182.168.192.in-addr.arpa.
$TTL 3600
@       IN      SOA     dns1.example.com. root.example.com. (
-                 0          ; Serial
+                 1          ; Serial
                  900        ; refresh
                  600        ; retry
                  86400      ; expire
                  900        ; minimum
)

@                                IN NS        dns1.example.com.
+ @                              IN NS        dns2.example.com.
10                               IN PTR       dns1.example.com.
+ 11                             IN PTR       dns2.example.com.
12                               IN PTR       dns-r.example.com.
```
ゾーンファイルが正しいか確認して、リロードさせます。
```
# nsd-checkzone example.com. /etc/nsd/zone/example.com.zone
# nsd-checkzone 182.168.192.in-addr.arpa /etc/nsd/zone/182.168.192.in-addr.arpa.zone
# nsd-control reload
```
TSIG 鍵を生成します。
NSD には TSIG 鍵を生成するようなツールがないため、今回は `bind-utils` の `tsig-keygen` を使います。
生成した TSIG 鍵はセカンダリサーバーの設定でも使用するので、メモしておきましょう。（コマンドの結果の`secret "<この文字列>"` が TSIG 鍵です。）
```
# dnf install bind-utils
# tsig-keygen -a hmac-sha512 xfr_notify
```
以下のような定義ファイルを作成します。
TSIG 鍵の追加、notify の送出先の設定、XFR によるゾーン転送の許可と、NSD 特有の IXFR を有効にする設定を行っています。
```/etc/nsd/tsig-key.conf
key:
    name: xfr_notify
    algorithm: hmac-sha512
    secret: <TSIG 鍵>
```
```diff:/etc/nsd/nsd.conf
server:
    ip-address: 192.168.182.10
    ip-address: 127.0.0.1

    reuseport: yes
    do-ip6: no

    server-count: 2 # システムのCPUコア数に合わせる

    tcp-count: 1000 # チューニング事項
    tcp-reject-overflow: yes
    tcp-timeout: 3

    username: nsd

    logfile: /var/log/nsd.log
    verbosity: 3

    hide-identity: yes
    hide-version: yes

remote-control: # nsd-control の設定
    control-enable: yes
    control-interface: 127.0.0.1

pattern:
    name: acl_query
    allow-query: 192.168.182.0/24 NOKEY # TSIG 鍵無しでこのサブネットからのクエリを受け付ける
    allow-query: 192.168.207.0/24 NOKEY
    allow-query: 127.0.0.0/8      NOKEY

+ include: /etc/nsd/tsig-key.conf
    
+ pattern:
+    name: conf_xfr_notify_out
+    notify: 192.168.182.11 xfr_notify
+    provide-xfr: 192.168.182.11 xfr_notify
+    store-ixfr: yes
+    create-ixfr: yes 

zone:
    name: example.com.
    zonefile: /etc/nsd/zone/example.com.zone
    include-pattern: acl_query
+    include-pattern: conf_xfr_notify_out
zone:
    name: 182.168.192.in-addr.arpa.
    zonefile: /etc/nsd/zone/182.168.192.in-addr.arpa.zone
    include-pattern: acl_query
+    include-pattern: conf_xfr_notify_out
```
TSIG 鍵が意図しないユーザーから閲覧されないよう、所有者とパーミッションを変更します。
その後、定義ファイルの内容をチェックし、リロードします。
```
# chown nsd:nsd /etc/nsd/tsig-key.conf
# chmod 600 /etc/nsd/tsig-key.conf
# nsd-checkconf /etc/nsd/nsd.conf
# nsd-control reconfig
```
## セカンダリサーバー側の設定
インストール、初期設定まではプライマリサーバーと概ね同様です。
TSIG 鍵を追加し、notify を受け入れる設定を記述した以下の定義ファイルを作成します。
```/etc/nsd/tsig-key.conf
key:
    name: xfr_notify
    algorithm: hmac-sha512
    secret: <TSIG 鍵>
```
```/etc/nsd/nsd.conf
server:
    ip-address: 192.168.182.11
    ip-address: 127.0.0.1
    
    reuseport: yes
    do-ip6: no
    
    server-count: 2 # システムのCPUコア数に合わせる
    
    tcp-count: 1000 # チューニング事項
    tcp-reject-overflow: yes
    tcp-timeout: 3
    
    username: nsd
    
    logfile: /var/log/nsd.log
    verbosity: 3

    hide-identity: yes
    hide-version: yes

    zonefiles-write: 1

remote-control: # nsd-control の設定
    control-enable: yes
    control-interface: 127.0.0.1

include: /etc/nsd/tsig-key.conf

pattern:
    name: acl_query
    allow-query: 192.168.182.0/24 NOKEY # TSIG 鍵無しでこのサブネットからのクエリを受け付ける
    allow-query: 192.168.207.0/24 NOKEY
    allow-query: 127.0.0.0/8      NOKEY

pattern:
    name: conf_xfr_notify_in
    allow-notify: 192.168.182.10 xfr_notify
    request-xfr: 192.168.182.10 xfr_notify

zone:
    name: example.com.
    zonefile: /etc/nsd/zone/example.com.zone
    include-pattern: acl_query
    include-pattern: conf_xfr_notify_in

zone:
    name: 182.168.192.in-addr.arpa.
    zonefile: /etc/nsd/zone/182.168.192.in-addr.arpa.zone    
    include-pattern: acl_query
    include-pattern: conf_xfr_notify_in
```
TSIG 鍵のパーミッションの変更後、定義ファイルをチェックし、FW の通信許可を設定して NSD を起動します。
```
# chown nsd:nsd -R /etc/nsd
# chmod 600 /etc/nsd/tsig-key.conf
# nsd-checkconf /etc/nsd/nsd.conf
# firewall-cmd --add-service=dns --permanent
# firewall-cmd --reload
# systemctl enable nsd
# systemctl start nsd
```
## 確認
セカンダリサーバーの起動後すぐに AXFR によるゾーン転送が行われています。
```
[2025-05-09 14:35:42.892] nsd[11831]: info: request full zone transfer (AXFR) for example.com. to 192.168.182.10
[2025-05-09 14:35:42.893] nsd[11831]: info: request full zone transfer (AXFR) for 182.168.192.in-addr.arpa. to 192.168.182.10
[2025-05-09 14:35:42.897] nsd[11831]: info: xfrd: zone example.com. committed "received update to serial 1 at 2025-05-09T14:35:42 from 192.168.182.10 TSIG verified with key xfr_notify"
[2025-05-09 14:35:42.899] nsd[11831]: info: xfrd: zone 182.168.192.in-addr.arpa. committed "received update to serial 1 at 2025-05-09T14:35:42 from 192.168.182.10 TSIG verified with key xfr_notify"
[2025-05-09 14:35:42.900] nsd[11832]: info: zone example.com. received update to serial 1 at 2025-05-09T14:35:42 from 192.168.182.10 TSIG verified with key xfr_notify of 198 bytes in 0.000115 seconds
[2025-05-09 14:35:42.900] nsd[11832]: info: zone 182.168.192.in-addr.arpa. received update to serial 1 at 2025-05-09T14:35:42 from 192.168.182.10 TSIG verified with key xfr_notify of 225 bytes in 7.9e-05 seconds
[2025-05-09 14:35:42.909] nsd[11831]: info: zone 182.168.192.in-addr.arpa. serial 0 is updated to 1
[2025-05-09 14:35:42.909] nsd[11831]: info: zone example.com. serial 0 is updated to 1
[2025-05-09 14:35:43.825] nsd[11832]: info: writing zone 182.168.192.in-addr.arpa. to file /etc/nsd/zone/182.168.192.in-addr.arpa.zone
[2025-05-09 14:35:43.825] nsd[11832]: info: zone 182.168.192.in-addr.arpa. written to file /etc/nsd/zone/182.168.192.in-addr.arpa.zone
[2025-05-09 14:35:43.825] nsd[11832]: info: writing zone example.com. to file /etc/nsd/zone/example.com.zone
[2025-05-09 14:35:43.825] nsd[11832]: info: zone example.com. written to file /etc/nsd/zone/example.com.zone
```
各クエリに対してプライマリサーバーと同じ応答が返ってくることが確認できます。
```
Resolve-DnsName dns1.example.com. -server 192.168.182.11

Name                                           Type   TTL   Section    IPAddress
----                                           ----   ---   -------    ---------
dns1.example.com                               A      3600  Answer     192.168.182.10

Name      : example.com
QueryType : NS
TTL       : 3600
Section   : Authority
NameHost  : dns1.example.com


Name      : example.com
QueryType : NS
TTL       : 3600
Section   : Authority
NameHost  : dns2.example.com

dns2.example.com                               A      3600  Additional 192.168.182.11


Resolve-DnsName example.com. -server 192.168.182.11

Name                        Type TTL   Section    PrimaryServer               NameAdministrator           SerialNumber
----                        ---- ---   -------    -------------               -----------------           ------------
example.com                 SOA  900   Authority  dns1.example.com            root.example.com            1


Resolve-DnsName 182.168.192.in-addr.arpa. -server 192.168.182.11

Name                        Type TTL   Section    PrimaryServer               NameAdministrator           SerialNumber
----                        ---- ---   -------    -------------               -----------------           ------------
182.168.192.in-addr.arpa    SOA  900   Authority  dns1.example.com            root.example.com            1


Resolve-DnsName 192.168.182.10 -server 192.168.182.11

Name                           Type   TTL   Section    NameHost
----                           ----   ---   -------    --------
10.182.168.192.in-addr.arpa    PTR    3600  Answer     dns1.example.com
182.168.192.in-addr.arpa       NS     3600  Authority  dns1.example.com
182.168.192.in-addr.arpa       NS     3600  Authority  dns2.example.com
```
## ACL、TSIG の確認
セカンダリサーバーから TSIG 鍵無しの AXFR 要求を送ると失敗しますが、
```
# dig example.com. @192.168.182.10 axfr

; <<>> DiG 9.16.23-RH <<>> example.com. @192.168.182.10 axfr
;; global options: +cmd
; Transfer failed.
```
TSIG 鍵を付与すればゾーンが転送されます。
```
# dig example.com. -y hmac-sha512:xfr_notify:<TSIG 鍵> @192.168.182.10 axfr

; <<>> DiG 9.16.23-RH <<>> example.com. -y hmac-sha512:xfr_notify:<TSIG 鍵> @192.168.182.10 axfr
;; global options: +cmd
example.com.            3600    IN      SOA     dns1.example.com. root.example.com. 1 900 600 86400 900
example.com.            3600    IN      NS      dns1.example.com.
example.com.            3600    IN      NS      dns2.example.com.
dns-r.example.com.      3600    IN      A       192.168.182.12
dns1.example.com.       3600    IN      A       192.168.182.10
dns2.example.com.       3600    IN      A       192.168.182.11
example.com.            3600    IN      SOA     dns1.example.com. root.example.com. 1 900 600 86400 900
xfr_notify.             0       ANY     TSIG    hmac-sha512. 1746769056 300 64 0NMlcnWXpcYrAlbKZKmz89Q7HSIEI1Ki/tTwRFnvfUt3qxCmzxKDWAWw b7KCGKEujfz6YtNwEWg/FOSdQS2eOg== 235 NOERROR 0
;; Query time: 0 msec
;; SERVER: 192.168.182.10#53(192.168.182.10)
;; WHEN: Fri May 09 14:37:36 JST 2025
;; XFR size: 7 records (messages 1, bytes 324)
```
これを他サーバー（以下は `192.168.207.132` 上の Ubuntu）から要求すると失敗します。
```
# dig example.com. -y hmac-sha512:xfr_notify:<TSIG 鍵> @192.168.182.10 axfr

; <<>> DiG 9.18.30-0ubuntu0.22.04.2-Ubuntu <<>> example.com. -y hmac-sha512:xfr_notify:<TSIG 鍵> @192.168.182.10 axfr
;; global options: +cmd
xfr_notify.             0       ANY     TSIG    hmac-sha512. 1746769100 300 64 KmirYyPrkG2VnIbmy1HDeXc9We3/57K1Mz3XgPHXW6/KyLsEe1F9mtKf ZpiBzwtriGE3W/NxdWSZRZHWiLKEfA== 39054 NOERROR 0
; Transfer failed.
```
## ゾーンを更新してみる
ゾーンファイルを書き換えます。
```diff:/etc/nsd/zone/example.com.zone
$ORIGIN example.com.
$TTL 3600
@       IN      SOA     dns1.example.com. root.example.com. (
-                  1          ; Serial
+                  2          ; Serial
                  900        ; refresh
                  600        ; retry
                  86400      ; expire
                  900        ; minimum
)

@                 IN NS        dns1.example.com.
@                 IN NS        dns2.example.com.
dns1              IN A         192.168.182.10
dns2              IN A         192.168.182.11
dns-r             IN A         192.168.182.12
+ dhcp            IN A         192.168.182.15
```
```diff:/etc/nsd/zone/182.168.192.in-addr.arpa.zone
$ORIGIN 182.168.192.in-addr.arpa.
$TTL 3600
@       IN      SOA     dns1.example.com. root.example.com. (
-                  1          ; Serial
+                  2          ; Serial
                  900        ; refresh
                  600        ; retry
                  86400      ; expire
                  900        ; minimum
)

@                                IN NS        dns1.example.com.
@                                IN NS        dns2.example.com.
10                               IN PTR       dns1.example.com.
11                               IN PTR       dns2.example.com.
12                               IN PTR       dns-r.example.com.
+ 15                             IN PTR       dhcp.example.com.
```
ゾーンファイルが正しいか確認して、リロードさせます。
```
# nsd-checkzone example.com. /etc/nsd/zone/example.com.zone
# nsd-checkzone 182.168.192.in-addr.arpa /etc/nsd/zone/182.168.192.in-addr.arpa.zone
# nsd-control reload
```

プライマリサーバーのゾーンが更新されたため、notify が送られ、IXFR によるゾーン転送が発生します。
```
[2025-05-09 14:41:56.752] nsd[11842]: info: notify for 182.168.192.in-addr.arpa. from 192.168.182.10 serial 2
[2025-05-09 14:41:56.752] nsd[11842]: info: notify for example.com. from 192.168.182.10 serial 2
[2025-05-09 14:41:56.753] nsd[11831]: info: request incremental zone transfer (IXFR) for 182.168.192.in-addr.arpa. to 192.168.182.10
[2025-05-09 14:41:56.754] nsd[11831]: info: request incremental zone transfer (IXFR) for example.com. to 192.168.182.10
[2025-05-09 14:41:56.755] nsd[11831]: info: xfrd: zone 182.168.192.in-addr.arpa. committed "received update to serial 2 at 2025-05-09T14:41:56 from 192.168.182.10 TSIG verified with key xfr_notify"
[2025-05-09 14:41:56.760] nsd[11831]: info: xfrd: zone example.com. committed "received update to serial 2 at 2025-05-09T14:41:56 from 192.168.182.10 TSIG verified with key xfr_notify"
[2025-05-09 14:41:56.762] nsd[11832]: info: zone 182.168.192.in-addr.arpa. received update to serial 2 at 2025-05-09T14:41:56 from 192.168.182.10 TSIG verified with key xfr_notify of 229 bytes in 0.000441 seconds
[2025-05-09 14:41:56.762] nsd[11832]: info: zone example.com. received update to serial 2 at 2025-05-09T14:41:56 from 192.168.182.10 TSIG verified with key xfr_notify of 204 bytes in 7.9e-05 seconds
[2025-05-09 14:41:56.772] nsd[11831]: info: zone 182.168.192.in-addr.arpa. serial 1 is updated to 2
[2025-05-09 14:41:56.772] nsd[11831]: info: zone example.com. serial 1 is updated to 2
[2025-05-09 14:41:57.244] nsd[11832]: info: writing zone 182.168.192.in-addr.arpa. to file /etc/nsd/zone/182.168.192.in-addr.arpa.zone
[2025-05-09 14:41:57.244] nsd[11832]: info: zone 182.168.192.in-addr.arpa. written to file /etc/nsd/zone/182.168.192.in-addr.arpa.zone
[2025-05-09 14:41:57.244] nsd[11832]: info: writing zone example.com. to file /etc/nsd/zone/example.com.zone
[2025-05-09 14:41:57.245] nsd[11832]: info: zone example.com. written to file /etc/nsd/zone/example.com.zone
```
更新・追加されたレコードは以下の通り解決できます。
```
Resolve-DnsName dhcp.example.com. -server 192.168.182.11

Name                                           Type   TTL   Section    IPAddress
----                                           ----   ---   -------    ---------
dhcp.example.com                               A      3600  Answer     192.168.182.15

Name      : example.com
QueryType : NS
TTL       : 3600
Section   : Authority
NameHost  : dns1.example.com


Name      : example.com
QueryType : NS
TTL       : 3600
Section   : Authority
NameHost  : dns2.example.com

dns1.example.com                               A      3600  Additional 192.168.182.10
dns2.example.com                               A      3600  Additional 192.168.182.11


Resolve-DnsName example.com. -server 192.168.182.11

Name                        Type TTL   Section    PrimaryServer               NameAdministrator           SerialNumber
----                        ---- ---   -------    -------------               -----------------           ------------
example.com                 SOA  900   Authority  dns1.example.com            root.example.com            2


Resolve-DnsName 192.168.182.15 -server 192.168.182.11

Name                           Type   TTL   Section    NameHost
----                           ----   ---   -------    --------
15.182.168.192.in-addr.arpa    PTR    3600  Answer     dhcp.example.com
182.168.192.in-addr.arpa       NS     3600  Authority  dns1.example.com
182.168.192.in-addr.arpa       NS     3600  Authority  dns2.example.com


Resolve-DnsName 182.168.192.in-addr.arpa. -server 192.168.182.11

Name                        Type TTL   Section    PrimaryServer               NameAdministrator           SerialNumber
----                        ---- ---   -------    -------------               -----------------           ------------
182.168.192.in-addr.arpa    SOA  900   Authority  dns1.example.com            root.example.com            2
```