id:hanazukiです.Kaigi on Rails 2025でもRubyKaigi NOCチームに来場者向けWi-Fiの運用を任せてもらうことになって準備をしています.4月のRubyKaigi 2025と同様のIPv6-mostlyネットワークを提供する予定ですが,RubyKaigiでは相互運用性の問題に当たったので,macOSでの検証を続けていました.この記事ではmacOS 15 (Sequoia)のCLAT実装を検証する中で遭遇した謎を記録しておきます.また,ちょうど今月macOS 16 (Tahoe)がリリースされたので,MacBookを更新して挙動に差異があるか確認しました.
- 追記 (9/27): id:sora_h が Kaigi on Rails 2025 での追試についていくつか追記しました.
- 追記 (11/4): id:sora_h が本記事を英訳したものを https://blog.sorah.jp/2025/11/04/mystery-of-clat-on-apple-devices に置きました
RFC 7050を利用するネットワークでスリープ復帰後にCLATが無効になる
RFC 7050は,NAT64の行われているネットワークに接続したクライアント端末が,DNSを使ってPREF64(IPv4からIPv6へのNATに使われるIPv6プレフィクス)を認識する仕組みです. RFC 7050を利用する環境下で,MacBookがスリープしている最中に何らかのタイマが切れると,それ以降CLATが機能しなくなる場合があることを確認しています.この状態はifconfigコマンドなどを使って,CLATが無効になっていることを見て確認できます.“ipv4only.arpa.”レコードのTTLを伸ばすなど試してみましたが,どうにも回避できないようでした.Wi-Fiを一旦切って接続しなおすと回復します.
通常時(CLATが有効になっているのが見える):
% ifconfig en0
en0: flags=89e3<UP,BROADCAST,SMART,RUNNING,NOARP,PROMISC,SIMPLEX,MULTICAST> mtu 1500
options=6460<TSO4,TSO6,CHANNEL_IO,PARTIAL_CSUM,ZEROINVERT_CSUM>
ether f2:22:7b:94:99:c7
inet6 fe80::41:4a0c:9034:bb5c%en0 prefixlen 64 secured scopeid 0xb
inet6 2001:df0:8500:ca31:66:bc01:e0a2:a774 prefixlen 64 autoconf secured
inet6 2001:df0:8500:ca31:2034:f93d:238:a003 prefixlen 64 autoconf temporary
inet 192.0.0.2 netmask 0xffffffff broadcast 192.0.0.2
inet6 2001:df0:8500:ca31:1863:bba2:38de:72fe prefixlen 64 clat46
nat64 prefix 2001:df0:8500:ca64:a9:8200:: prefixlen 96
nd6 options=201<PERFORMNUD,DAD>
media: autoselect
status: active
異常時(スリープ復帰後にCLATが無効になっていることがある):
% ifconfig en0
en0: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
options=6460<TSO4,TSO6,CHANNEL_IO,PARTIAL_CSUM,ZEROINVERT_CSUM>
ether f2:22:7b:94:99:c7
inet6 fe80::41:4a0c:9034:bb5c%en0 prefixlen 64 secured scopeid 0xb
inet6 2001:df0:8500:ca31:66:bc01:e0a2:a774 prefixlen 64 autoconf secured
inet6 2001:df0:8500:ca31:2034:f93d:238:a003 prefixlen 64 autoconf temporary
nd6 options=201<PERFORMNUD,DAD>
media: autoselect
status: active
自宅の検証環境ではこの問題に悩まされていましたが,RubyKaigi本番のネットワークではRFC 8781対応のルータが用意されたため,特に対策をしていません.Tahoeにアップデートしてからは起こっていないので直ったのかもしれないですが,発生条件が謎なので定かではありません.
draft-ietf-v6ops-prefer8781-07でも議論されているように,RFC 7050方式には短所が多く,ルータ機器がRFC 8781に対応するまでの繋ぎの技術と考えるべきでしょう.RFC 8781はルータ広告(RA)のオプションにPREF64を記述できるようにします.知る限りでは,CLAT機能をもつコンシューマデバイスは最新版でRFC 7050とRFC 8781の両対応になっているため,IPv4/IPv6デュアルスタックに縮退可能なIPv6-mostlyネットワークにおいては,RFC 8781対応が可能であればRFC 7050を実装する必要はないと思います.また,IPv6-mostlyのためにネットワークにRFC 7050を実装する場合は,すべてのAレコードに対してDNS64を行う必要はなく,“ipv4only.arpa.”のAAAAレコードのみを合成して返せば問題ないようです.
CLATアドレス宛ユニキャストの近隣要請に応答しない
RubyKaigi 2025でIPv6-mostly Wi-Fiを提供した際に,macOSからIPv4ホスト宛の通信が,数分から十数分後に不通になる問題が発生していました.有線接続では問題が再現しなかったことと,事前の検証環境ではCisco Mobility Expressを利用していたのに対して,本番では本物のCisco WLCを使っていたことから,macOSのCLAT実装とWLCに何らかの相性があるのだろうと当たりをつけ,同型のWLCで検証を行いました.
macOS Sequoiaで検証した結果,macOSはCLATに使うIPアドレス(ifconfigの出力で“clat46”とか書かれているアドレス)宛にユニキャストで届く近隣要請(NS)に近隣広告(NA)で答えないことがわかりました.一方で,マルチキャストで届いたNSには正常に応答します.したがって,ルータからのユニキャストNSに答えられずに近隣キャッシュから消されてしまっても,その後に送られるマルチキャストNSに答えるので,通常の環境下では(わずかな遅延があるでしょうが)問題なく通信できているように見えます.
Wi-Fiの影響を取り除くためにルータ(3560-CX)とMacBookを有線接続して,NS/NAのやり取りを記録しました:
en8: flags=89e3<UP,BROADCAST,SMART,RUNNING,NOARP,PROMISC,SIMPLEX,MULTICAST> mtu 1500
options=404<VLAN_MTU,CHANNEL_IO>
ether c8:a3:62:00:67:74
inet6 fe80::1e:79e8:6ce7:f060%en8 prefixlen 64 secured scopeid 0x14
inet6 2001:df0:8500:ca31:1c20:bd02:b146:2ddc prefixlen 64 autoconf secured
inet6 2001:df0:8500:ca31:a00d:5968:8e62:f5e8 prefixlen 64 autoconf temporary
inet 192.0.0.2 netmask 0xffffffff broadcast 192.0.0.2
inet6 2001:df0:8500:ca31:148a:b2a:d796:4886 prefixlen 64 clat46
nat64 prefix 2001:df0:8500:ca64:a9:8200:: prefixlen 96
nd6 options=201<PERFORMNUD,DAD>
media: autoselect (1000baseT <full-duplex>)
status: active
No.
Time
Source
Destination
Protocol
Length
Info
83048
13:55:05.310318
fe80::66c:9dff:fe31:41c6
2001:df0:8500:ca31:a80d:5968:8e62:f5e9
ICMPv6
86
Neighbor Solicitation for 2001:df0:8500:ca31:a80d:5968:8e62:f5e9 from 04:6c:9d:31:41:c6
83049
13:55:05.317197
fe80::de:79e8:6c9f:b968
fe80::66c:9dff:fe31:41c6
ICMPv6
78
Neighbor Advertisement 2001:df0:8500:ca31:a80d:5968:8e62:f5e9 (sol)
83087
13:55:08.371306
fe80::66c:9dff:fe31:41c6
2001:df0:8500:ca31:148a:d2a:d796:4806
ICMPv6
86
Neighbor Solicitation for 2001:df0:8500:ca31:148a:d2a:d796:4806 from 04:6c:9d:31:41:c6
83102
13:55:09.462731
fe80::66c:9dff:fe31:41c6
2001:df0:8500:ca31:148a:d2a:d796:4806
ICMPv6
86
Neighbor Solicitation for 2001:df0:8500:ca31:148a:d2a:d796:4806 from 04:6c:9d:31:41:c6
83108
13:55:10.514967
fe80::66c:9dff:fe31:41c6
2001:df0:8500:ca31:148a:d2a:d796:4806
ICMPv6
86
Neighbor Solicitation for 2001:df0:8500:ca31:148a:d2a:d796:4806 from 04:6c:9d:31:41:c6
83115
13:55:11.625881
fe80::66c:9dff:fe31:41c6
ff02::1:ff96:4806
ICMPv6
86
Neighbor Solicitation for 2001:df0:8500:ca31:148a:d2a:d796:4806 from 04:6c:9d:31:41:c6
83116
13:55:11.626185
fe80::de:79e8:6c9f:b968
fe80::66c:9dff:fe31:41c6
ICMPv6
78
Neighbor Advertisement 2001:df0:8500:ca31:148a:d2a:d796:4806 (sol, ovr) is at c8:a3:62:08:ab:e0
WLCはNS/NAをスヌーピングして代理で答えることで,無線区間を転送されるマルチキャストフレームを減らす機能を持っています.WLCはNAを観測すると,自身のNeighbor Bindingテーブルにこれを記録します.NAを観測してから(Reachable Lifetime)秒以内に合致するNSを観測した場合,NSを破棄してNAを代返します.(Reachable Lifetime)秒以降(Stale Lifetime)秒以内にユニキャストNSを観測した場合は,NSをクライアントに転送します.通常は,このユニキャストNSに対してクライアントが返したNAをWLCが観測することで,Neighbor Bindingのタイマが更新されます.マルチキャストNSは既定では転送されません.
ところが,macOSはCLATアドレス宛のユニキャストNSに答えないので,正常にWLCのNeighbor Bindingのタイマが更新されず,ネクストホップルータの近隣キャッシュからも抜け落ちてしまいます.結果として,上流方向へのパケットは届くが,下流方向へのパケットは届かない状態になります.
この問題の緩和策を2つ見つけています.いずれかを適用すれば十分で,Kaigi on Rails 2025では前者を試してみる予定です.
- WLCのNeighbor BindingのReachable Lifetimeを大きく設定すれば,WLCが隣接ルータからのNSに代返する期間を伸ばすことができます.これがWi-Fiクライアントが接続を持続している時間より長ければ疎通を保ち続けられます.ただし,WLCのNeighbor Bindingの最大エントリ数(Cisco 3504では36,000)を考慮して値を決める必要があると思われます.
- WLCで“Unknown Address Multicast NS Forwarding”を許可すると,WLCは自身のNeighbor Bindingに合致しないマルチキャストNSをクライアントに転送するようになります.macOSはマルチキャストNSには応答するので,NAがWLCのNeighbor Bindingに記録され,以降はWLCが代返するようになります.しかし,マルチキャストフレームがWi-Fiのエアタイムを消費することが問題になりえます.特に,RubyKaigiネットワークではIPv6のステートフルファイアウォールを設けておらず,インターネット側からのアドレススキャンによって大量のマルチキャストNSが発生する可能性があります.
draft-ietf-v6ops-claton-08は,CLATアドレスと通常のIPv6アドレスで異なる挙動を示す実装が見つかっていることに言及しています.ここで紹介したmacOSの問題もその筋では知られているかもしれませんが,ワイヤレスアプライアンスとの組み合わせで問題になりうることを指摘する資料は見つけられませんでした.
なお,CLATアドレス宛のユニキャストNSに応答しない問題は,Tahoeで修正されたように見えます.また,同アドレス宛のICMPv6 Echo Requestに答えない問題もありましたが,同様に修正を確認しました.
Kaigi on Rails 2025 での追試と、上記とは異なる挙動について (id:sora_h)
id:sora_h です。以上を踏まえて 9/26-27 に開催された Kaigi on Rails 2025 にて追加検証を行いました。Kaigi on Rails 2025 では前述の緩和策 (1) を実装していましたが *1、いくつかの macOS や iOS のバージョンで CLAT アドレス宛近隣要請の挙動を確認しました。
前述の報告では Neighbor Solicitiation への返答 (solicited Neighbor Advertisement) がないという内容でしたが、Kaigi on Rails 2025 での追加検証では異なる挙動を確認しました。 *2。今回は solicited Neighbor Advertisement は観測できましたが、その挙動は下記の通りです:
- macOS 15.5, macOS 15.6.1, iOS 18.5: solicited Neighbor Advertisement の内容に含まれる target address は CLAT アドレス (GUA)、IPv6 ヘッダの source はそれと異なりリンクローカルアドレス
- iOS 26, macOS 26: solicited Neighbor Advertisement の target address・IPv6 ヘッダの source はともに CLAT アドレス
前述の solicited Neighbor Advertisement が観測できないという結果にはなりませんでしたが、macOS 15, iOS 18 では当該パケットの source address がその target address とは一致せず、リンクローカルアドレスになってしまっているようでした。 RFC 4861 等をざっくり読んでもこの点に関してどうあるべきかの記述はみられませんでしたが、iOS 26, macOS 26 ではこの挙動が変更になり状況が改善したと見られることから、何らかの相互接続性観点の改善、あるいは不具合修正が入ったと考えられます。
また、30 分程度でパケットが不通になってしまうという症状も設営日に緩和策を入れずに検証していましたが、macOS 15.6.1, iOS 18.5 で再現させることはできませんでした (おそらくある程度近隣テーブルにエントリが必要で、設営日に再現しなかったという仮説を立てています)。
その他に報告されている事象
macOSには他に,CLATが有効化されたインターフェイスに与えられるダミーのIPv4アドレス“192.0.0.2”をネットワークにリークしてしまう問題や,sshコマンドに“-4”のオプションをつけるとIPv4ホストに接続できない問題が報告されています*3.これらの問題はSequoiaでは再現していましたが,Tahoeで修正されたようです.IPv4ホスト宛のtracerouteで中間ホストからのICMPエラーを受け取れない問題はTahoeにも残っていました.
Sequoiaで見つけた問題をKaigi on Railsで実地検証して報告しようと思っていたら,直前にTahoeが出て直ってしまいましたというブログでした.