この記事はCyberAgent Developers Advent Calendar 2025 16 日目の記事です 🎅
みなさん、IPv6やってますか? サイバーエージェントの黒崎(@kuro_m88 ) です。
今年はIPv6でクリスマスツリー🎄を作ってみました!
既にIPv6を活用している人も、よくわからないので無効化してしまっている人もこの記事を通してIPv6を身近に感じていただければと思います。
AWS上に構築することで真似したいと思った方が挑戦できるようにほぼterraformで完結させるようこだわりましたが、サポートチケットをあげないと解決不能な問題に直面するなど、個人的にはかなり頑張ったので最後までお楽しみください!
つくったもの🎄
こちらです。綺麗なクリスマスツリーが見えますね。IPv6がある環境ではMac, Linuxをお使いの方はcliでtraceroute6 -I xmas.as63790.net (Windowsはtracert)とコマンドを打っていただくと再現可能です(2026年1月頃までの期間限定公開です)。

IPv6のインターネット接続環境さえあれば誰でもこのクリスマスツリーが見えるということは、インターネットに存在するということですね!☁️
お使いのインターネット接続環境がIPv6に対応しているか簡単に確かめる方法として、AWSがホストしているIPv6の接続性確認のサイトがあります。https://ipv6.ec2-reachability.amazonaws.com/ (なぜかhttpsでアクセスしてもhttpにリダイレクトされます) ここにアクセスして緑色のチェックマークが表示されればIPv6に対応しています。IPv6に対応していない場合は、こちらも環境依存ですがスマホのテザリング経由だとIPv6が使えることが多いかもしれません。
しくみ🎄
先ほど用いた tracerouteコマンドはネットワークのトラブルシューティングにおいてよく使われるコマンドです。
パケットがどのルータを経由しているか、遅延はどこで発生しているのか、パケットはどこで破棄されているのか等の情報を得ることでトラブルシューティングのヒントが得られます。
この仕組みを応用してネットワーク経路上に順番にクリスマスツリーのアスキーアートの各行をホスト名に持つルータをAWS上にデプロイすることで、tracerouteコマンドで経路を調べると出力にクリスマスツリーが浮かび上がる仕組みになっています。
IPv6におけるtraceroute
今回はICMPv6プロトコルを利用した手法を紹介します。ICMPv6はInternet Control Message ProtocolのIPv6版という意味で、今回はEcho Request, Echo Reply, Time Exceededの3つのメッセージを活用しています。
Echo Request / Echo Reply
一方がEcho Requestを送信すると、他方はEcho Replyを返信します。これによりネットワーク上に通信相手が存在するのか調査することができます。ping(ping6)コマンドでも使われています。

Time Exceeded
IPv6パケットにはHop Limitというフィールドが存在し、ルータを経由(Hop)するごとにこの値は1つ減算されます。Hop Limitが0になったとき、そのパケットは破棄されると同時に送信者にTime Exceededというメッセージを送信します。

Hop Limitのフィールドがあるおかげで途中経路のルーティングにループが発生してしまっていてもいつかはHop Limitが0になりパケットが破棄されます。これがないとインターネットのどこかでループが発生すると一生インターネット上をパケットがさまようことになります。また、宛先が存在しなかったのか、Hop Limitが0になったのかを区別できるよう、Time Exceededというメッセージで送信者にフィードバックできるようになっています。
tracerouteでは通常十分に大きな値に設定されているHop Limitを小さくすることでわざとTime Exceededを発生させ、その送信元IPv6アドレスを見ることで途中経路のルータの存在を検出します。具体的にはHop Limitを1, 2, 3…と順番に大きくしていき、Echo Reply(もしくはDestination Unreachable)を受け取るまでくり返せば完全な経路が得られます。

ちなみにネットワークの性質上、往路と復路の経路は必ず同一とは限らないため、厳密には往路の経路しか調査不能で、復路は相手方から逆方向にtracerouteをしてもらわないとわかりません。また、セキュリティ等の観点からICMPv6メッセージの応答をしてくれないルータやサーバも存在します。
IPv6アドレスの逆引き
このIPv6アドレスについているホスト名を調べるにはそのDNSでPTRレコードをクエリします。
逆引き用の特殊な ipv6.arpa というドメインがあり、2406:da14:1cc1:2a00:ca::3というIPv6アドレスに対するPTRレコードをクエリするには3.0.0.0.0.0.0.0.0.0.0.0.a.c.0.0.0.0.a.2.1.c.c.1.4.1.a.d.6.0.4.2.ip6.arpaというFQDNに対してクエリします。長くてみづらいですが、IPv6アドレスを逆順に4bitずつの16進数表記をピリオドで区切って繋げるルールになっています。
あとは各HopごとのIPv6アドレスに対する逆引きのホスト名が順番にクリスマスツリーのアスキーアートになるようにすれば完成です!🎄
クリスマスツリーのつくりかた🎄
実際にどう実装したのかご紹介します。前述した理論どおりにいくとVPC上にEC2を大量に起動することになりますが、リソースの無駄なのでENIのPrefix DelegationとLinuxのNetwork Namespaceを活用して1台のEC2の中に大量のルータとサブネットを構築可能にしました。
95%くらいはこちらのterraformで完結するようこだわったものをこちらで公開しています。
https://github.com/kurochan/ipv6-christmas-tree/tree/v1.0.0
これに沿って解説していきます。
ネットワーク構築: VPC, インターネットゲートウェイ, サブネット, ルートテーブル
https://github.com/kurochan/ipv6-christmas-tree/blob/v1.0.0/christmas_tree.tf#L1-L98
VPCを作成し、AWSからIPv6アドレスを /56 のサイズで割り当てを受けます。どんなPrefixが破り当たるかは実行してみてもお楽しみです。そこから /64 サイズでサブネットを切り出します。今回は冗長化などは一切行わないため、1AZ構成です。
セキュリティ: セキュリティグループ, IAM Role, インスタンスプロファイル
https://github.com/kurochan/ipv6-christmas-tree/blob/v1.0.0/christmas_tree.tf#L59-L152
セキュリティグループでICMPv6パケットの受信を許可し、tracerouteのパケットに応答できるようにします。IAM Roleではデバッグ用にSystemsManagerでシェルログインできるようにするのと、後述するS3バケットへのアクセスができるようにします。
サーバ: AMI, ENI, EC2インスタンス
https://github.com/kurochan/ipv6-christmas-tree/blob/v1.0.0/christmas_tree.tf#L153-L212
EC2にアタッチするENI(ネットワークインターフェース)を作成するのですが、その際にサブネットの /64 のアドレスからこのENIに /80 を切り出して委譲(Prefix Delegation)します。202という数値はprefixの最後が16進数でcaになってサイバーエージェントっぽいからです。
resource "aws_network_interface" "ec2" { subnet_id = aws_subnet.main.id security_groups = [aws_security_group.ec2.id] ipv6_prefixes = [cidrsubnet(aws_subnet.main.ipv6_cidr_block, 16, 202)] # 64 + 16 = 80 tags = { Name = "${local.project_name}-eni" }}プロビジョニング: cloud-init, S3, シェルスクリプト
https://github.com/kurochan/ipv6-christmas-tree/blob/v1.0.0/christmas_tree.tf#L213-L248
後述する理由からシェルスクリプトを設置するためのS3バケットが必要なため、作成しています。
https://github.com/kurochan/ipv6-christmas-tree/blob/v1.0.0/user_data.sh.tftpl
EC2はcloud-initをサポートしており、メタデータ(user data)にスクリプトを記述することで、初回起動時に自動実行されセルフプロビジョニングを行えます。EC2のuser dataはサイズ制限があり、16KB以上は記述できません。今回プロビジョニングに利用するスクリプトが16KBを超えるためuser dataには収まりません。そのためS3にシェルスクリプト本体を設置し、user dataはS3からシェルスクリプトを読み込むためだけに利用します。
snap install aws-cli --classicaws s3 cp s3://${s3_bucket}/multihop.sh /opt/multihop.shbash /opt/multihop.sh | tee -a /opt/multihop.loghttps://github.com/kurochan/ipv6-christmas-tree/blob/v1.0.0/multihop.sh.tftpl
今回の一番重要な部分です。かなり長いので順を追って紹介していきます。
各ルータのセットアップ
ip netns addコマンドを使って、Linux上にNetwork Namespaceを作成しています。Network Namespaceは、コンテナ技術でも利用されている仕組みで、ネットワーク設定(インタフェースやルーティングテーブルなど)を独立させることができます。このNamespaceを 1 つのルータに見立て、それぞれに独立したルーティングテーブルを持たせることで、「多数のルータが存在する環境」を擬似的に作ります。
さらに、それらの Namespace 同士を仮想リンク(veth)で接続することで、複数のルータが横一列につながっているようなネットワーク構成を、1 つのLinuxカーネル上に再現しています。
# r1sysctl -w net.ipv6.conf.all.forwarding=1sysctl -w net.ipv6.conf.default.forwarding=1ip netns add r1ip netns exec r1 sysctl -w net.ipv6.conf.all.forwarding=1ip netns exec r1 sysctl -w net.ipv6.conf.default.forwarding=1ip link add veth-h-r1 type veth peer name veth-r1-hip link set veth-h-r1 upip link set veth-r1-h netns r1ip -n r1 link set veth-r1-h upip -6 addr add ${ipv6_prefix}2/127 dev veth-h-r1ip -6 -n r1 addr add ${ipv6_prefix}3/127 dev veth-r1-hip -6 -n r1 route add default dev veth-r1-h via ${ipv6_prefix}2static routeの注入
今回11個ルータがあればよかったのですが、多めに32個数珠つなぎにしました。それぞれのルータはstatic routeがなければルーティングできないので、大量のルーティング設定を注入しています。全部で528経路あります。
# r27ip -6 -n r27 route add ${ipv6_prefix}3a/127 dev veth-r27-r28 via ${ipv6_prefix}39 # r29ip -6 -n r27 route add ${ipv6_prefix}3c/127 dev veth-r27-r28 via ${ipv6_prefix}39 # r30ip -6 -n r27 route add ${ipv6_prefix}3e/127 dev veth-r27-r28 via ${ipv6_prefix}39 # r31ip -6 -n r27 route add ${ipv6_prefix}40/127 dev veth-r27-r28 via ${ipv6_prefix}39 # r32# r28ip -6 -n r28 route add ${ipv6_prefix}3c/127 dev veth-r28-r29 via ${ipv6_prefix}3b # r30ip -6 -n r28 route add ${ipv6_prefix}3e/127 dev veth-r28-r29 via ${ipv6_prefix}3b # r31ip -6 -n r28 route add ${ipv6_prefix}40/127 dev veth-r28-r29 via ${ipv6_prefix}3b # r32# r29ip -6 -n r29 route add ${ipv6_prefix}3e/127 dev veth-r29-r30 via ${ipv6_prefix}3d # r31ip -6 -n r29 route add ${ipv6_prefix}40/127 dev veth-r29-r30 via ${ipv6_prefix}3d # r32# r30ip -6 -n r30 route add ${ipv6_prefix}40/127 dev veth-r30-r31 via ${ipv6_prefix}3f # r32ファイアウォール
traceroute以外に用途がないので、念の為nftablesを使って制限をかけています。セキュリティグループでICMPv6のEcho Requestしか受信しないようにしていますが、サーバ内でEcho Requestのペイロードサイズが128バイトより大きかった場合、送信元のIPv6アドレスの /56 単位で集計して秒間10パケット(200パケットのバーストは許可)より多かった場合はパケットを捨てるようにして、不必要に大量な通信が発生しないようにしています。
nft add table inet icmpv6-filternft add chain inet icmpv6-filter input '{ type filter hook input priority 0 ; policy accept ; }'nft add chain inet icmpv6-filter forward '{ type filter hook forward priority 0 ; policy accept ; }'nft add rule inet icmpv6-filter input 'icmpv6 type echo-request meta length > 128 counter drop'nft add rule inet icmpv6-filter input 'icmpv6 type echo-request meter in_ping_prefix_limit { ip6 saddr & ffff:ffff:ffff:ff00:: limit rate 10/second burst 200 packets } counter accept'nft add rule inet icmpv6-filter input 'icmpv6 type echo-request counter drop'nft add rule inet icmpv6-filter forward 'icmpv6 type echo-request meta length > 128 counter drop'nft add rule inet icmpv6-filter forward 'icmpv6 type echo-request meter fwd_ping_prefix_limit { ip6 saddr & ffff:ffff:ffff:ff00:: limit rate 10/second burst 200 packets } counter accept'nft add rule inet icmpv6-filter forward 'icmpv6 type echo-request counter drop'スクリプト生成
この量をいちいち手で書くのは厳しく、規則性もあるのでこのシェルスクリプトを生成するrubyスクリプトを書きました。理論上はもっとたくさんのルータを構築可能です。ただ、IPv6のHop Limitの仕様上最大値が255なのでそれ以上の数のルータがあってもどう頑張っても到達不可能になってしまいます。
https://github.com/kurochan/ipv6-christmas-tree/blob/v1.0.0/multihop.rb
DNS
https://github.com/kurochan/ipv6-christmas-tree/blob/v1.0.0/dns.tf
インターネットゼミのドメインを使った都合上、ここだけCloudflareになっていますが、Route53でも特に支障はありません。
アスキーアートの各行はこちらのファイルに記載されています。
https://github.com/kurochan/ipv6-christmas-tree/blob/v1.0.0/christmas_tree.txt
テキストファイルを改行で区切って配列にし、それをループしながらDNSレコードを生成しています。
locals { hosts = split("\n", chomp(file("${path.module}/christmas_tree.txt")))}resource "cloudflare_dns_record" "tree" { for_each = { for host in local.hosts : host => index(local.hosts, host) } zone_id = var.zone_id name = "${each.key}.${var.domain}" ttl = 300 type = "AAAA" comment = "for CyberAgent Developers Advent Calendar 2025" content = cidrhost(tolist(aws_network_interface.ec2.ipv6_prefixes)[0], each.value * 2 + 1) proxied = false}DNSレコードとIPv6アドレスが一対一で紐づく必要があるため、重複した行ができないように注意してください。

以上にてAWSアカウント内(とCloudflareが少し)で完結する操作は完了です。
仕上げ: サポートチケットを起票する
先ほど設定したDNSの設定は「正引き」であり、traceroute時にクエリされるのは「逆引き」です。逆引きのDNSレコードは逆引きの仕組み上そのIPv6アドレス(プレフィックス)の持ち主でなければ設定ができません。今回利用しているAWSのVPC内のIPv6アドレスは自分の持ち物ではなく、あくまでもAWSから割り当ててもらっているアドレスです。そのため、持ち主であるAWSにお願いをして設定してもらう必要があります。
Elastic IPは逆引きの設定をするAPIが存在するのですが、Elastic IPはIPv4の概念のため、IPv6に対応できません。
https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/Using_Elastic_Addressing_Reverse_DNS.html
あまりなさそうなユースケースなのでダメ元でサポートチケットを起票してみました。かなり怪しいというかお恥ずかしいサポートチケットになってしまいました。

結論から言うと、Eメール送信用途ではないかつElastic IPではない場合に関してもこちらのフォームで申請可能なようです。
https://support.console.aws.amazon.com/support/contacts#/rdns-limits
これでめでたくクリスマスツリーが完成しました!🎅
おわりに
IPv6は最初の仕様策定から30年、日本初のIPv6商用サービスリリースから26年が経過した技術で、かなりの年月が経過しもはや新しい技術ではなくなりました。
サイバーエージェントのとある広告プロダクトの実トラフィックのIPv6比率は2023年時点で60%弱あり、クライアント側の対応が進んできていることがわかりますが、それ以上にIPv4のアドレス空間の逼迫状況はどんどん厳しくなってきており、AWSですと2023年にはパブリックIPv4アドレスの料金体系の変更があり、コスト増の要因となっています。
AWSのパブリックIPv4の料金体系の変更とサイバーエージェントのIPv6活用推進事例 | CyberAgent Developers Blog
LBやCDNにIPv6アドレスをつければクライアントとのインターネット上の通信はIPv6化が達成されるため、これだけでもかなりの進歩ですが、その裏がIPv4のみで構築されている段階は「完全なIPv6対応」と言うにはまだ距離があります。
VPC内やコンテナクラスタを含めかなり積極的なIPv6化の取り組みをしているケースとしてNetflixの事例を紹介します。
AWS re:Invent 2021 – How Netflix is using IPv6 to enable hyperscale networking

Netflixは2021年にVPC内のフローのIPv6比率を1%未満から25%程度まで向上させたそうです。

「ビジネス上、経済上の理由から必要だからIPv6化に取り組んでいる」という趣旨の発言が非常に印象的でした。
作ってみた感想ですが、学生時代に本記事と似たようなことを友人とやったことがあります。当時はLinuxで実装する方法がわからず、短いLANケーブルを大量に製作し48ポートのL3スイッチ(ルータのようなもの)を3台繋げて物理的にパケットをHopさせたのですが、今ではterraformで実装できるようになり成長を感じました。

IPv6アドレスはアドレス幅もそうですが設計も異なるため、今回ご紹介したような極端な構成をインターネットに繋がった状態で実装したところで全く問題が起きないことがわかります。本記事をきっかけにIPv6により興味を持つ人が増えましたら幸いです。

