LinuxでiodineによるDNSトンネリング:ファイアウォールをバイパスしDNSプロトコル経由でネットワークにアクセスする

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

カンファレンスのホテルで「インターネット接続」のお金を払ったのに、ポート80、443、53以外はすべてブロックされている。ポート22のSSH?使えない。VPN?接続できない。でもDNSクエリだけは素通りする――DNSをブロックしたらすべてが壊れるため、誰も手を付けないからだ。

そこでiodineの出番だ。私は正規のネットワーク監査中にクライアント先でこの状況に遭遇し、トンネルを起動してから10分以内に完全な接続を確立できた。それ以降、企業イントラネットやホテルのキャプティブポータルなど様々な環境でこのアプローチを使ってきたが、Tailscale、OpenVPN、通常のSSHがすべて失敗した状況でも、毎回うまく機能してきた。

ここではサーバー、クライアント、ルーティング、そして最初に必ずハマるポイントを含めた完全なセットアップ手順を解説する。

クイックスタート — 5分でiodineを動かす

必要なもの

  • パブリックIPを持つVPS(Ubuntu/Debian/CentOS)
  • 管理しているドメイン(例:example.com
  • NSレコードを作成できるDNSへのアクセス権

ステップ1:DNSレコードの設定

ドメインレジストラで2つのDNSレコードを作成する:

  • Aレコード:vps.example.com → YOUR_VPS_IP
  • NSレコード:tunnel.example.com → vps.example.com

このNSレコードが重要な核心部分だ――*.tunnel.example.comへのすべてのクエリをVPSに委任する。iodineはこのサブドメイン名前空間を使い、IPパケットをDNSクエリのホスト名にエンコードする。

ステップ2:iodineのインストール

VPS(サーバー側)で実行:

# Ubuntu/Debian
sudo apt update && sudo apt install iodine -y

# CentOS/RHEL
sudo yum install iodine -y

# ソースからビルド(リポジトリにない場合)
git clone https://github.com/yarrick/iodine.git
cd iodine && make && sudo make install

クライアントマシンにも同じパッケージをインストールする。

ステップ3:サーバーの起動(iodined)

sudo iodined -f -c -P yourpassword 10.0.0.1 tunnel.example.com

フラグの説明:

  • -f:フォアグラウンドで実行(デーモンモードにする場合は省略)
  • -c:クライアントIPチェックを無効化――VPSがNATの背後にある場合に便利
  • -P yourpassword:認証用の共有パスワード
  • 10.0.0.1:サーバーのTUNインターフェースに割り当てるIP
  • tunnel.example.com:NS委任されたサブドメイン

ステップ4:クライアントから接続する

sudo iodine -f -P yourpassword tunnel.example.com

接続成功時の出力はこのようになる:

Opened dns0
Opened IPv4 UDP socket
Sending DNS queries for tunnel.example.com to 8.8.8.8
Autodetecting DNS query type (use -T to override).
Using EDNS0 extension
...
Server tunnel IP is 10.0.0.1
Sending handshake...
Connection setup complete, transmitting data.

クライアントにIPアドレス10.0.0.2dns0 TUNインターフェースが作成された。ping 10.0.0.1で確認しよう。

詳細解説 — DNSトンネリングの仕組み

プロトコルの仕組み

iodineはIPパケットをDNSクエリ文字列のホスト名としてエンコードする。クライアントから送信されるパケットは分割・base32エンコードされ、次のようなルックアップリクエストとして送られる:

aabbccddee.tunnel.example.com → DNSタイプNULLクエリ

VPSはNS委任を通じてそれらのクエリを受信する。データをデコードし、実際のIPパケットを転送し、レスポンスをエンコードしてDNSアンサーとして返す。途中のファイアウォールからは、通常のDNSトラフィックと区別がつかない。

クエリタイプとエンコーディング

iodineは起動時に最適なDNSレコードタイプを自動ネゴシエーションする:

  • NULLレコード:最高スループットだが、一部のDNSリレーが削除することがある
  • TXTレコード:互換性が高く、若干効率は落ちる
  • CNAME/Aレコード:非常に制限の厳しい環境でのフォールバック

自動検出が最適でないタイプを選んだ場合、特定のタイプを強制できる:

sudo iodine -f -T TXT -P yourpassword tunnel.example.com

私の経験では、企業ネットワークではNULLが最もよく機能し、独自のリゾルバーを通じてDNSトラフィックをプロキシするホテルのキャプティブポータルではTXTの方が安定している。

TUNインターフェースを理解する

接続後、iodineが作成したものを確認する:

ip addr show dns0
# 4: dns0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1130 qdisc fq_codel
#     inet 10.0.0.2/27 scope global dns0

MTUが標準の1500ではなく1130になっていることに注目。DNSレコードには厳しいサイズ制限があるため、iodineはパケットを分割して収める。これがDNSトンネリングが遅い主な理由だ――IPパケットを再組み立てするために複数のDNSラウンドトリップが必要になる。

応用的な使い方

すべてのトラフィックをトンネル経由でルーティングする

この時点でトンネルは動作しているが、デフォルトルートはまだ制限されたネットワーク経由で出ている。すべてのトラフィックをiodine経由に切り替えるには:

# 現在のゲートウェイとDNSサーバーIPを保存する
CURRENT_GW=$(ip route show default | awk '/default/ {print $3}')
DNS_SERVER="8.8.8.8"  # 制限されたネットワークのDNSに置き換える

# 重要:DNSクエリをローカルインターフェース経由でルーティングし続ける
# iodine自体がループしないようにするため
sudo ip route add $DNS_SERVER/32 via $CURRENT_GW

# 残りすべてのトラフィックをトンネル経由にリダイレクトする
sudo ip route del default
sudo ip route add default via 10.0.0.1 dev dns0

サーバー側でNATを有効にし、クライアントのトラフィックをインターネットへ転送する:

# IPフォワーディングを有効にする
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward

# トンネルクライアントをインターネットへNATする(eth0は自分のインターフェース名に合わせて変更)
sudo iptables -t nat -A POSTROUTING -s 10.0.0.0/27 -o eth0 -j MASQUERADE
sudo iptables -A FORWARD -i dns0 -o eth0 -j ACCEPT
sudo iptables -A FORWARD -i eth0 -o dns0 -m state --state RELATED,ESTABLISHED -j ACCEPT

トンネル経由のSOCKSプロキシ

ブラウザやアプリケーションレベルのアクセスだけが必要な場合、トンネル経由のSSH SOCKSプロキシが完全なルーティングより効率的だ:

# トンネルサーバー経由でSOCKS5プロキシを開く
ssh -D 1080 -N [email protected]

# 使用する
curl --socks5 127.0.0.1:1080 https://example.com

iodined をsystemdサービスとして実行する

本番環境や再起動後も動作し続ける必要がある場所では、iodined をsystemdサービスとして設定する:

sudo nano /etc/systemd/system/iodined.service
[Unit]
Description=iodine DNSトンネルサーバー
After=network.target

[Service]
ExecStart=/usr/sbin/iodined -c -P yourpassword 10.0.0.1 tunnel.example.com
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl enable iodined
sudo systemctl start iodined
sudo systemctl status iodined

実践的なヒント

現実的なパフォーマンスの期待値

DNSトンネリングは設計上遅い――デプロイ前に期待値を設定しておこう:

  • アップストリーム(クライアント → サーバー):3〜10 KB/s
  • ダウンストリーム(サーバー → クライアント):5〜20 KB/s
  • レイテンシ:DNSラウンドトリップあたり200〜800ms

SSHセッション、Git over HTTPS、軽量なAPIコールは問題なく動作する。動画ストリーミングや大きなファイル転送はかなり辛い。他のすべての手段がブロックされた緊急アクセスとしては、このスループットで十分価値がある。

よくある問題のトラブルシューティング

「No downstream data received」――DNSリレーがレスポンスをフィルタリングまたは改ざんしている可能性が高い。クエリタイプを切り替えよう:

sudo iodine -f -T TXT -P yourpassword tunnel.example.com

接続は確立できたがpingが通らない――サーバーでIPフォワーディングかNATルールが設定されていない:

cat /proc/sys/net/ipv4/ip_forward       # 出力が 1 であることを確認
sudo iptables -t nat -L POSTROUTING    # MASQUERADEルールが存在するか確認

NS委任が解決されない――DNS伝播には最大48時間かかることがある。レコードが有効かどうか確認しよう:

dig NS tunnel.example.com
# 期待される出力:tunnel.example.com. IN NS vps.example.com.

MSSクランピングでTCPパフォーマンスを改善する

MTUが低いとTCPフラグメンテーションが発生し、スループットが静かに低下する。サーバーでiptablesルールを1つ追加するだけで解決できる:

sudo iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
  -j TCPMSS --clamp-mss-to-pmtu

これはTCPにトンネルを通過できるサイズにセグメントサイズを制限するよう指示する。これを追加するとSSHのレスポンスが再び改善し、HTTP転送も明らかに速くなる。

セキュリティに関する注意

iodineは共有パスワードで認証するが、トラフィックはエンコードされるだけで暗号化はされない。機密性の高い通信には、その上にSSHかWireGuardを重ねること――DNSトンネルは接続性を提供するだけで、暗号化は別の問題だ。

知っておくべきこと:DNSトンネリングは特徴的なトラフィックパターンを生成する――異常に高いクエリ量、長いサブドメイン文字列――ネットワーク監視ツールが検知するだろう。使用するのは、テストを許可されたネットワークのみにすること。

私のiodinedサーバーは1年以上稼働し続け、バックアップアクセス方法として静かに待機している。クライアントのメインVPNがメンテナンス中にダウンし、チームがサーバーへのアクセスを失ったとき、このトンネルが数時間ではなく数分で彼らを復旧させた。事前に設定しておくことで、完全にロックアウトされることはない。その安心感は、15分の設定時間に十分見合う価値がある。

Share: