Linux バイパスルーター上で UFW と sing-box TUN を同時に実行すると、誤診しやすい障害が発生することがあります。sing-box 自体は正常に動作しているのに、LAN 上のデバイスがバイパスルーター経由でインターネットにアクセスできない状態です。その時点で遮断されていた TCP ポートを許可したとしても、sing-box を再起動すると問題が再発することがあります。

本稿では、実際に観察された原因と、回帰検証待ちの修復方法を記録します。例はすべて文書予約アドレスを使用し、実際のネットワークには対応していません。

環境と症状

  • Linux バイパスルーター。LAN インターフェースは eth0、アドレスは 192.0.2.1(例示のみ)。
  • 下流端末 192.0.2.20 は、IPv4 ゲートウェイとしてバイパスルーターを使用します。
  • sing-box の TUN インターフェース名は tun0 で、auto_route と auto_redirect を有効にしています。
  • UFW は既定で、許可されていない着信トラフィックを拒否します。

sing-box の主要設定:

{
  "type": "tun",
  "interface_name": "tun0",
  "auto_route": true,
  "auto_redirect": true
}

障害発生中のログは次のようなものです:

[UFW BLOCK] IN=eth0 OUT= SRC=192.0.2.20 DST=192.0.2.1 PROTO=TCP DPT=24461 SYN

DPT=24461 は一度の実行で観察された動的ポートにすぎないため、UFW に固定して書き込むべきではありません。OUT= が空であることから、このパケットは FORWARD 経路を通るだけでなく、本機の INPUT 経路に入り込んでいることが示唆されます。

NAT リダイレクトの追跡

sing-box の待ち受けを確認します:

sudo ss -lntup | grep sing-box

nftables を確認します:

sudo nft -a list ruleset | grep -n -C 8 -E 'redirect|tproxy'

障害発生時のシナリオでは、次のようなルールが見られます:

meta l4proto tcp redirect to :24461
# 内部端口不允许直接连接
tcp dport 24461 ct status ! dnat reject with tcp reset

つまり、sing-box はまず宛先の TCP 接続を本機の待ち受けポートにリダイレクトし、その後、UFW の INPUT ルールが許可するかどうかを決定します。この種類のトラフィックを通常の FORWARD パケットとして許可しても、観察された INPUT での遮断は通常解消されません。

修復方針:動的ポートではなく DNAT 接続にマッチさせる

明確に信頼できる単一の下流端末については、一時的に次を試せます:

sudo iptables -I ufw-before-input 1 \
  -i eth0 \
  -s 192.0.2.20/32 \
  -d 192.0.2.1 \
  -p tcp \
  -m conntrack --ctstate DNAT \
  -j ACCEPT

例示の IP とインターフェースを実際の値に置き換えてください。DNAT は --ctstate の有効な仮想状態であり、--ctstatus ではありません。--ctstatus を使用すると Bad ctstatus "DNAT" というエラーが発生します。

このルールには依然として安全上の制約があります。sing-box が作成した接続だけでなく、条件を満たす DNAT TCP 接続にもマッチします。したがって、送信元と入口インターフェースを制限し、手間を省くために INPUT 全体に対する許可設定を行うべきではありません。

検証

下流デバイスで次を実行します:

curl -4 -v --connect-timeout 5 https://www.cloudflare.com/

バイパスルーター側でルールのマッチ数と遮断ログを監視します:

sudo iptables -L ufw-before-input -n -v --line-numbers
sudo journalctl -k --since '10 minutes ago' --grep='UFW BLOCK' --no-pager

カウンターが増加し、インターネット接続が復旧し、さらに sing-box を再起動して内部ポートが変化した後も正常であれば、この方法が現在の環境に適していると判断できます。

永続化

検証が成功したことを確認したら、まずバックアップを取得します:

sudo cp -a /etc/ufw/before.rules /etc/ufw/before.rules.bak
sudo nano /etc/ufw/before.rules

*filter セクションで、すべてのチェーン定義の後ろ、既存の ufw-before-input 処理ルールの前に次を追加します:

-A ufw-before-input -i eth0 -s 192.0.2.20/32 -d 192.0.2.1 -p tcp -m conntrack --ctstate DNAT -j ACCEPT

チェックしてからリロードします:

sudo iptables-restore --test < /etc/ufw/before.rules
sudo ufw reload

ロールバック時は、バックアップを使ってファイルを復元し、sudo ufw reload を実行します。一時的なテストルールも、挿入時と完全に一致するマッチングパラメータを指定して iptables -D で削除できます。

注意事項

  1. ufw default allow routed を直接使用したり、UFW 全体を無効にするものを長期的な解決策にしないでください。
  2. この方法は主に IPv4 TCP リダイレクト後の INPUT 遮断を対象とするものであり、すべての UDP、IPv6、WireGuard の障害に対する汎用的な修復ではありません。
  3. API、DNS、SOCKS など、他の明示的に設定された待ち受けポートについては、実際の用途に応じてアクセス元を引き続き制限してください。
  4. UFW と sing-box のどちらも nftables を変更する可能性があります。ルールのマッチカウンターが増加しても接続が失敗する場合は、他のベースチェーンのフック優先度や後続の破棄ルールも確認してください。

参考文献