JANOG57 NETCON Level3-6 解説記事
はじめに
JANOG57 NETCONにご参加いただきありがとうございます。
本記事では、出題した「Level3-6」について解説します。
問題文
あなたは小規模オフィスのネットワーク管理者です。最近、オフィスのユーザー数が増えてきたところ、一部のDNSクエリが正常に解決できなくなる問題が発生しています。
原因を調査し、問題を解決してください。
特に業務が忙しくなる時間帯(=DNSクエリが多い時間帯)に問題が顕著になるようです。
ネットワーク構成

- RT01: Linuxルータ (NAPT, iptables)
- DNS01: 内部DNSキャッシュサーバ (Unbound)
- External DNS: 外部DNSサーバ (203.0.113.53) - DoT (DNS over TLS, TCP/853) に対応
- Client: オフィスPC
達成条件
client 上で dns-test コマンドを実行し、200件のDNSクエリがすべて成功すること。
==============================================
DNS Connection Test
==============================================
DNS Server: 192.168.1.53
Test Count: 200
Sending 200 queries...
Result: 200 / 200
==============================================
Result
==============================================
+------------------------------------------+
| OK |
+------------------------------------------+
制約
- external-dns の設定を変更しないこと
- client の /etc/resolv.conf を変更しないこと
- rt01 では iptables 以外のコマンドでホストに変更を加えないこと
ヒント
- RT01のファイアウォールルールを確認してみてください
- NAPTテーブルのサイズには限りがあります
- TCPとUDPでは、NAPTセッションの扱いが異なります
- External DNS は DoT に対応しています
初期設定
RT01 の iptables ルール
# デフォルトポリシー
iptables -P FORWARD DROP
# NAPT
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o ens3 -j MASQUERADE
# conntrack テーブルサイズ
net.netfilter.nf_conntrack_max = 100
# ファイアウォールルール
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -i ens2 -s 192.168.1.0/24 -p udp --dport 53 -j ACCEPT
iptables -A FORWARD -i ens2 -s 192.168.1.0/24 -p tcp --dport 80 -j ACCEPT
iptables -A FORWARD -i ens2 -s 192.168.1.0/24 -p tcp --dport 443 -j ACCEPT
iptables -A FORWARD -i ens2 -s 192.168.1.0/24 -p icmp -j ACCEPT
dns01 の Unbound 設定 (/etc/unbound/unbound.conf)
server:
interface: 0.0.0.0
port: 53
access-control: 192.168.1.0/24 allow
access-control: 127.0.0.0/8 allow
local-zone: "test." nodefault
verbosity: 1
log-queries: yes
log-replies: yes
log-servfail: yes
num-threads: 2
msg-cache-size: 16m
rrset-cache-size: 32m
cache-min-ttl: 60
cache-max-ttl: 86400
hide-identity: yes
hide-version: yes
tls-cert-bundle: /etc/unbound/certs/ca.pem
forward-zone:
name: "."
forward-addr: 203.0.113.53
ポイントをまとめると:
- RT01 は conntrack テーブルサイズが 100 に制限されている
- ファイアウォールは UDP/53、TCP/80、TCP/443、ICMP のみ許可(TCP/853 は許可されていない)
- dns01 の Unbound は外部DNS (203.0.113.53) に UDP/53 で問い合わせている
- 外部DNSは DoT (TCP/853) に対応しているが、Unbound 側では DoT を使っていない
- TLS証明書 (
tls-cert-bundle) は設定済みだが、DoT は有効化されていない
解法
ステップ1: 症状を確認する
まず dns-test を実行して症状を確認します。
client:~# dns-test
==============================================
DNS Connection Test
==============================================
DNS Server: 192.168.1.53
Test Count: 200
Sending 200 queries...
Result: 83 / 200
==============================================
Result
==============================================
+------------------------------------------+
| NG |
+------------------------------------------+
200件中83件しか成功しません。大量のクエリを同時に送ると一部が失敗するようです。
ステップ2: RT01 の状態を確認する
ファイアウォールルールを確認してみましょう。
rt01:~# sudo iptables -L FORWARD -n -v --line-numbers
Chain FORWARD (policy DROP)
num target prot opt source destination
1 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
2 ACCEPT udp -- 192.168.1.0/24 0.0.0.0/0 udp dpt:53
3 ACCEPT tcp -- 192.168.1.0/24 0.0.0.0/0 tcp dpt:80
4 ACCEPT tcp -- 192.168.1.0/24 0.0.0.0/0 tcp dpt:443
5 ACCEPT icmp -- 192.168.1.0/24 0.0.0.0/0
UDP/53 は許可されています。次に conntrack テーブルの状態を確認します。
rt01:~# cat /proc/sys/net/netfilter/nf_conntrack_max
100
rt01:~# cat /proc/net/nf_conntrack | wc -l
98
conntrack テーブルの上限が 100 しかなく、ほぼ埋まっています。DNS クエリの負荷テスト中にテーブルが溢れ、新しい接続が確立できなくなっているようです。
ステップ3: なぜ conntrack が枯渇するのか?
現在の構成では、dns01 は外部DNSに UDP/53 で問い合わせています。UDP はコネクションレスなプロトコルなので、各クエリが個別の NAPT セッション(conntrack エントリ)を生成 します。さらに、UDP のセッションは明確な終了通知がないため、タイムアウト(約30秒)まで conntrack エントリが保持されます。
UDP/53 の場合:
クエリ1 → conntrack エントリ1 (30秒後に消える)
クエリ2 → conntrack エントリ2 (30秒後に消える)
クエリ3 → conntrack エントリ3 (30秒後に消える)
...
クエリ100 → conntrack エントリ100
クエリ101 → テーブル満杯!→ ドロップ ❌
200件のクエリを同時に送ると、あっという間に 100 エントリの上限に達してしまいます。
ステップ4: 解決策を考える
ここでヒントを振り返ります。
- TCPとUDPでは、NAPTセッションの扱いが異なります
- External DNS は DoT に対応しています
TCP の場合、1つの接続で複数のクエリを送信できます(TCP keepalive、パイプライニング)。また、FIN/RST で接続の終了が明確なため、conntrack エントリを速やかに解放できます。
UDP/53 の場合: DoT (TCP/853) の場合:
クエリ1 → エントリ1 クエリ1 ┐
クエリ2 → エントリ2 クエリ2 ├→ エントリ1 (1つのTCP接続)
クエリ3 → エントリ3 クエリ3 ┘
... ...
= 200エントリ消費 ❌ = 数エントリで済む ✅
つまり、dns01 の Unbound を DNS over TLS (DoT) で外部DNSに接続するように変更すれば、conntrack エントリの消費を大幅に削減できます。
ステップ5: ファイアウォールで TCP/853 を許可する
まず RT01 のファイアウォールに DoT のポートを許可するルールを追加します。
rt01:~# sudo iptables -I FORWARD 2 -i ens2 -s 192.168.1.0/24 -p tcp --dport 853 -j ACCEPT
ステップ6: Unbound を DoT に切り替える
dns01 の Unbound 設定を編集して、DoT を有効化します。
dns01:~# vim /etc/unbound/unbound.conf
forward-zone セクションを以下のように変更します。
# 変更前
forward-zone:
name: "."
forward-addr: 203.0.113.53
# 変更後
forward-zone:
name: "."
forward-addr: 203.0.113.53@853
forward-tls-upstream: yes
-
forward-addr: 203.0.113.53@853→ ポート853(DoT)に接続 -
forward-tls-upstream: yes→ TLS を有効化
Unbound を再起動します。
dns01:~# pkill unbound
dns01:~# unbound -c /etc/unbound/unbound.conf
ステップ7: 動作確認
client で dns-test を実行します。
client:~# dns-test
==============================================
DNS Connection Test
==============================================
DNS Server: 192.168.1.53
Test Count: 200
Sending 200 queries...
Result: 200 / 200
==============================================
Result
==============================================
+------------------------------------------+
| OK |
+------------------------------------------+
200件すべて成功しました。
RT01 の conntrack テーブルも確認してみましょう。
rt01:~# cat /proc/net/nf_conntrack | wc -l
5
DoT(TCP/853)では1つのTCP接続で複数のクエリを処理するため、conntrack エントリ数が劇的に減っています。
解説: UDP と TCP の NAPT セッションの違い
この問題の本質は、UDP と TCP で NAPT セッションの扱いが大きく異なるという点です。
| UDP/53 | DoT (TCP/853) | |
|---|---|---|
| クエリごとのセッション | 1クエリ = 1セッション | 複数クエリ = 1セッション |
| セッション終了の検知 | タイムアウト待ち(~30秒) | FIN/RST で即座に解放 |
| 200クエリ時のエントリ数 | 最大200 | 数個 |
| 暗号化 | なし | TLS で暗号化 |
conntrack テーブルのサイズが十分に大きい環境では問題になりにくいですが、今回のように上限が小さい場合(100エントリ)、UDP/53 による大量のクエリは致命的です。
DoT には conntrack エントリの削減だけでなく、DNS通信の暗号化というセキュリティ上のメリットもあります。外部のDNSリゾルバが DoT に対応しているなら、積極的に活用するのがよいでしょう。
※この記事はClaude Codeを活用して書かれました
Discussion