KMC活動ブログ

京大マイコンクラブの活動の様子を紹介します!!

macOSのCLAT実装の謎

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を更新して挙動に差異があるか確認しました.

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
ルータからCLATアドレス宛に送られたユニキャストNSに応答しない様子.直前に送られた一時アドレス宛のユニキャストNSや,直後に送られたマルチキャストNSにはNAを答えている.

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では前者を試してみる予定です.

  1. WLCのNeighbor BindingのReachable Lifetimeを大きく設定すれば,WLCが隣接ルータからのNSに代返する期間を伸ばすことができます.これがWi-Fiクライアントが接続を持続している時間より長ければ疎通を保ち続けられます.ただし,WLCのNeighbor Bindingの最大エントリ数(Cisco 3504では36,000)を考慮して値を決める必要があると思われます.
  2. 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、いくつかの macOSiOS のバージョンで 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が出て直ってしまいましたというブログでした.

*1:これと直接関係があるかは分かっていませんが、一部クライアントでDHCPv4やRAパケットだけWLCのレイヤで抜け落ちる現象が突如発生する事象は確認していて困ってます。macOS Tahoe 以後がある程度普及したと考えられる 2027 年以降はこの緩和策を解除できますが…

*2:検証方法もとい Neighbor Solicitiation を送る手段が異なるため、その関係もあるかもしれません

*3:Experimental IPv6-only network at APRICOT 2024