Linux環境でTaygaとBIND9によるNAT64とDNS64を構築:IPv6専用ネットワークをIPv4へ接続する

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

ステージング環境でデュアルスタックを廃止した理由

6か月前、私たちのインフラチームはステージング環境からデュアルスタックネットワークを完全に撤退させました。2つのプロトコルスタックを並行して運用することは、エンジニアリング工数を激しく浪費していました。ファイアウォールルールは倍増し、DHCPスコープは断片化され、貴重なパブリックIPv4サブネットが無駄に消費されていたのです。そこで私たちは確固たる目標を掲げました。全85台の開発マシンとCI/CDランナーからIPv4を完全に排除しつつ、レガシーなIPv4パッケージミラーやベンダーAPI、Dockerレジストリへのアウトバウンドアクセスは維持することです。

これを可能にしたのが、DNS64とNAT64という2つのオープンソースツールです。

  • DNS64(BIND9を使用): IPv6専用ホストが、Aレコードしか持たない外部ドメインのAAAAレコードを要求した際、リゾルバが即座にAAAAレコードを合成(シンセサイズ)します。リモートホストの32ビットIPv4アドレスを指定のプレフィックス(最も一般的にはRFC 6052で定義された周知の64:ff9b::/96プレフィックス)に埋め込みます。
  • NAT64(Taygaを使用): クライアントがその合成されたアドレス宛にパケットを送信すると、ローカルルーティングによってTaygaへと渡されます。Taygaはステートレスなユーザ空間トランスレーションデーモンです。40バイトのIPv6ヘッダーを取り除き、パケットをプライベートIPv4アドレスプールにマッピングしてLinuxのnetfilterへ渡します。そこから標準的なiptablesのマスカレードによって、トラフィックがIPv4インターネットへと送り出されます。

その結果はどうだったでしょうか。180日間にわたり、カーネルパニックゼロ、最小限のメンテナンスで、1日あたり約45GBのアウトバウンドトラフィックを淡々と処理し続ける無駄のないエッジルーターが完成しました。

前提条件と環境トポロジー

今回の構成は、Ubuntu 22.04 LTSが動作する2コア/2GB RAMの仮想マシン上でテストおよびデプロイを行いました。このホストは2つのネットワークインターフェースをブリッジします。

  • eth0: ルーティング可能なパブリックIPv4アドレスと、委譲されたIPv6プレフィックス(2001:db8:1::2/64)を持つアップストリームのWANインターフェース。
  • eth1: 完全なIPv6開発ワークステーション向けのデフォルトゲートウェイ(2001:db8:100::1/64)として機能するダウンストリームのLANインターフェース。

必要なトランスレーションデーモン、DNSツール、およびファイアウォール補助ツールをインストールします。

sudo apt update
sudo apt install -y bind9 bind9utils tayga iptables iptables-persistent

RHEL 9やRocky Linux 9を使用している場合は、EPELおよび標準リポジトリからパッケージを取得します。

sudo dnf install -y epel-release
sudo dnf install -y bind tayga iptables-services

ステップ別の設定手順

1. Linuxカーネルのパケット転送を有効化

ゲートウェイは、プロトコルおよびインターフェースをまたいでパケットを転送する必要があります。sysctlでIPフォワーディングを有効にします。

sudo bash -c 'cat >> /etc/sysctl.d/99-nat64.conf <<EOF
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
EOF'

sudo sysctl --system

2. ステートレスNAT64向けにTaygaを設定

Taygaは、IPv6と内部IPv4ステージングプールとの間のステートレスな変換を担います。/etc/tayga.confを編集して、変換用のtunインターフェース、動的プール、および対象プレフィックスを定義します。

sudo bash -c 'cat > /etc/tayga.conf <<EOF
tun-device nat64
ipv4-addr 192.168.255.1
prefix 64:ff9b::/96
dynamic-pool 192.168.255.0/24
data-dir /var/spool/tayga
EOF'

tunインターフェースを起動し、ルーティングテーブルをバインドします。再起動後もルートを保持できるよう、これらのコマンドをネットワークディスパッチャスクリプトまたはsystemdのoverrideに配置してください。

# 仮想tunデバイスを初期化
sudo tayga --mktun

# ローカルエンドポイントを設定してデバイスを起動
sudo ip link set nat64 up
sudo ip addr add 192.168.255.1 dev nat64
sudo ip addr add 2001:db8:100::ffff dev nat64

# 変換対象プレフィックスのルーティングをTaygaに向ける
sudo ip route add 192.168.255.0/24 dev nat64
sudo ip route add 64:ff9b::/96 dev nat64

3. iptablesによるアウトバウンドIPv4パケットのマスカレード

Taygaは受信したIPv6トラフィックをプライベートな192.168.255.0/24サブネットにマッピングします。これらのアドレスはパブリックインターネット上をルーティングできないため、iptablesを使用してWANインターフェース(eth0)の背後でマスカレードします。

# 動的TaygaプールのNAT変換
sudo iptables -t nat -A POSTROUTING -s 192.168.255.0/24 -o eth0 -j MASQUERADE

# インターフェース間のフォワーディングルール
sudo iptables -A FORWARD -i nat64 -o eth0 -j ACCEPT
sudo iptables -A FORWARD -i eth0 -o nat64 -m state --state RELATED,ESTABLISHED -j ACCEPT

# 再起動後もルールを永続化
sudo netfilter-persistent save

4. DNS64シンセサイザとしてBIND9を設定

内部のワークステーションがIPv6レコードを持たない外部サービスを問い合わせた際、BIND9がレコードを合成します。/etc/bind/named.conf.optionsを更新し、アクセス制御リスト(ACL)とDNS64ブロックを設定します。

acl "trusted_clients" {
    127.0.0.1;
    ::1;
    2001:db8:100::/64;
};

options {
    directory "/var/cache/bind";

    recursion yes;
    allow-query { trusted_clients; };
    allow-recursion { trusted_clients; };

    listen-on { any; };
    listen-on-v6 { any; };

    forwarders {
        1.1.1.1;
        8.8.8.8;
    };

    dnssec-validation auto;

    dns64 64:ff9b::/96 {
        clients { trusted_clients; };
        mapped { any; };
        exclude { 64:ff9b::/96; };
        suffix ::;
    };
};

構文を検証し、両方のサービスを起動します。

sudo named-checkconf
sudo systemctl restart bind9 tayga
sudo systemctl enable bind9 tayga

テストと運用における注意点

1. 合成されたDNSレスポンスの確認

リゾルバがゲートウェイ(2001:db8:100::1)を向いているIPv6専用ワークステーションにログインします。ipv4.google.comなどのIPv4専用の宛先に対して問い合わせを実行します。

dig AAAA ipv4.google.com +short

BINDは合成されたAAAA応答(64:ff9b::d83a:ce6e)を返します。末尾の16進数値(d8.3a.ce.6e)に注目してください。これを10進数に変換すると、Googleの対象IPv4アドレスである216.58.206.110と一致します。

2. スループットと接続性のテスト

完全なIPv6経由でエンドツーエンドのICMPおよびHTTPSハンドシェイクを実行します。

ping -6 -c 3 64:ff9b::d83a:ce6e
curl -6 -I https://ipv4.google.com

クライアントはIPv6経由でTLSネゴシエーションを完了し、宛先サーバーがIPv4で動作していることをまったく意識せずに通信できます。

3. 実運用から得られた重要な知見

本構成を本番運用する中で、極めて重要な3つのポイントが明らかになりました。

  • TCP MSSクランピングは必須: IPv4ヘッダーが20バイトであるのに対し、IPv6パケットヘッダーは40バイトを消費します。一部の外部CDNはICMP Packet Too Bigメッセージをドロップするため、Path MTUブラックホールが発生します。私たちの環境でも、iptablesでMSSをクランプするまでnpm installの実行が99%で停止していました。
    sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
  • ステートレストランスレータは低コストでスケールする: Taygaが高速であり続けられるのは、セッションテーブルを保持しないためです。接続追跡(コネクショントラッキング)は完全にカーネルのconntrackモジュールにオフロードされます。私たちの環境では、12,000個のアクティブソケットという疑似負荷条件下でも、Taygaの消費RAMは18MB未満でした。
  • 生のIPv4リテラル指定は失敗する: DNS64はDNS名前解決を前提としています。開発者がハードコードされたIPv4リテラルを含むツールを使用した場合(例: curl http://198.51.100.4)、リクエストはBINDを経由しないため失敗します。こうしたレガシーな特殊ケースには、クライアント側でCLATデーモン(clatdなど)を動かすか、内部SOCKS5プロキシを利用するよう誘導してください。
Share: