🐱

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