macOSで49.7日後にTCPネットワーキングが停止するバグが見つかる
(photon.codes)- macOSのTCPタイムスタンプカウンタ(
tcp_now)は、起動後約49.7日が経過すると32ビットオーバーフローにより内部のTCPクロックが停止する - その結果、TIME_WAIT状態の接続が期限切れにならず蓄積し、エフェメラルポートが解放されない
- 時間が経つとエフェメラルポートの枯渇により、新しいTCP接続がすべて失敗し、既存の接続だけが維持される
- ICMP(ping)は正常に動作する一方、TCPの全機能が麻痺し、再起動以外では復旧できない
- 長期間稼働するmacOSサーバー・ビルドマシン・CI環境は49日17時間周期でこの問題にさらされ、カーネル修正までは定期的な再起動が必要
背景: TCPの基本概念
- TCP接続は終了時にすぐ消えるのではなくTIME_WAIT状態に入り、これは遅延したパケットの処理と信頼性のある終了のための段階である
- 古いパケットが新しい接続として誤解釈されるのを防ぎ、最後のACKが失われた場合の再送に対応するため
- TIME_WAITの継続時間は2 × MSL(Maximum Segment Lifetime)として定義され、macOSでは約30秒に設定されている
- MSLはTCPセグメントがネットワーク上で生存できる最大時間で、RFC 793では2分と定義されているが、現代のシステムでははるかに短く設定されている
- 32ビット符号なし整数オーバーフローは、値が最大値(4,294,967,295)を超えると0に戻る現象であり、macOSのTCPタイムスタンプ(
tcp_now)は起動以降ミリ秒単位で増加する32ビットカウンタのため、49日17時間2分47.296秒後にオーバーフローが発生する
発見: 49.7日後にTCP接続が停止する現象
- PhotonのiMessage監視用Macサーバーは24時間365日で運用されており、2026年3月30日、起動後ちょうど49.7日が経過した時点で新しいTCP接続がすべて失敗する現象が発生した
- 既存の接続とICMP(ping)は正常に動作していたが、新しいTCPソケットの作成は不可能だった
- 原因はXNUカーネルのTCPタイムスタンプカウンタ(
tcp_now)のオーバーフローで、単調増加を検証するロジックがwraparound後の更新を遮断し、内部TCPクロックが停止した - TIME_WAIT接続が期限切れにならないため、エフェメラルポートが解放されず蓄積し、最終的に再起動以外では復旧不能になる
- 再起動後も同じ現象が49.7日周期で繰り返される
実験設計: オーバーフロー前後のTCP動作比較
- 仮説: オーバーフロー後にTIME_WAITのガベージコレクションが止まるなら、オーバーフロー前後で短命TCP接続の生成パターンに違いが現れる
- オーバーフロー前: TIME_WAITは30秒後に正常に期限切れ
- オーバーフロー後: TIME_WAITが無期限に継続
- 3段階で構成されたテストスクリプトを実行
- 監視段階: オーバーフロー35分前から5分前まで、TIME_WAIT数を10秒間隔で記録
- バースト段階: オーバーフロー前後10分の間、2秒ごとに約15個の短命TCP接続を生成
- 観察段階: 接続生成を停止した後、TIME_WAITの変化を監視
結果: オーバーフロー後にTIME_WAITが停滞
- オーバーフロー前はTIME_WAIT数が0〜200の間で安定して循環し、正常な回収動作が確認された
- オーバーフロー直後からTIME_WAIT数が増え続け、以後は期限切れにならない
- Machine Bでは2,828個のTIME_WAIT接続が84秒後にも1つも回収されず、その後も継続的に蓄積した
- Machine Aでも手動確認の結果、TIME_WAIT数は単調に増加し、復旧不能な状態となった
根本原因: XNUカーネルのtcp_now 32ビットオーバーフロー
tcp_nowはbsd/netinet/tcp_var.hで定義されたミリ秒単位の32ビットカウンタで、起動以降の経過時間を追跡するcalculate_tcp_clock()関数での(uint32_t)now.tv_sec * 1000演算が49.7日後に最大値を超え、wraparoundが発生するif (tmp < current_tcp_now)条件文のため、オーバーフロー時には既存値の方が新しい値より大きくなって更新が遮断され、tcp_nowが永続的に停止する- TIME_WAITの期限切れ判定は
tcp_nowを基準に行われるため、クロックが止まると期限切れ条件が常に偽となり回収不能になる
連鎖効果: TCP全体の機能停止へ拡大
- 数分後: TIME_WAIT回収が停止し、短命接続の多いワークロードで徐々に問題が発生
- 数時間後: TIME_WAITが数千件蓄積し、エフェメラルポートが枯渇
- ポート枯渇後: 新しいTCP接続はSYN_SENT状態で失敗し、既存接続だけが維持される
- CPU負荷が急増: カーネルがTIME_WAITキューを継続的にスキャンし、負荷が増加
- 結果としてTCPが完全に麻痺し、ICMPだけが正常に動作する
- 唯一の復旧方法は再起動で、その後また49.7日のカウントが再開する
追加の証拠と関連事例
- RFC 7323では、1ms単位の32ビットタイムスタンプの符号ビットラップが約24.8日ごとに発生すると明記している
- macOSの場合は32ビット全体のオーバーフロー(49.7日)であり、RFCで扱うリモートタイムスタンプ問題とは別のローカルカーネル欠陥である
- Appleコミュニティやオープンソースプロジェクトでも同様の症状が多数報告されている
- TCP接続不可、pingは正常、再起動だけが解決策、数週間の稼働後に発生
- Podman issue #12495などでも同じパターンが確認されている
- 共通点: TCPだけ失敗、ICMPは正常、再起動が必要、数週間単位の発生周期
影響範囲
- 49日17時間以上連続稼働したmacOSシステムで発生する可能性がある
- 一般ユーザーは定期的なアップデートで再起動されるため影響は小さい
- 高リスク環境
- 長期稼働サーバーフリート
- macOSベースのCI/CDビルドサーバー
- Mac Proワークステーション
- リモート管理型コロケーションMac
- ビルドファーム・テストインフラ向けのMac miniクラスター
再現手順
- 起動時刻からオーバーフロー予想時点を計算
- オーバーフロー前後のTIME_WAIT数を監視
- オーバーフロー時点で多数の短命TCP接続を生成
- 2分後にTIME_WAIT数が減少しなければバグ再現成功
9.5時間後に観察されたシステム状態
- TIME_WAIT接続が1つも回収されず、継続的に増加
- SYN_SENT状態の失敗接続が3,000件以上蓄積
- 既存接続だけが維持され、新規接続は不可能
- Machine Bの平均負荷は49.74まで上昇し、カーネルがTIME_WAITキューのスキャンに過剰なCPUを使用
結論
- たった1つの32ビット整数と
if (tmp < current_tcp_now)条件文が、49.7日後にTCP全体を停止させる時限爆弾として機能していた - 開発・テスト・コードレビュー段階では見つけにくいタイプの欠陥であり、実運用環境でのみ表面化する
- Photonは複数のサーバーで同現象を再現しており、オーバーフロー前は正常に回収され、その後はTIME_WAITが蓄積することを明確に確認した
tcp_nowが止まるとカーネルのTCPクロックが停止し、システムは見かけ上正常でもTCPポートがすべて使い果たされる- 長期稼働するmacOSシステム管理者は49日17時間2分47秒を覚えておく必要があり、 再起動周期の調整またはカーネル修正が行われるまで定期的な再起動が必要になる
- Photonは現在、再起動せずに
tcp_nowを復旧する回避策を開発中である
1件のコメント
Hacker Newsのコメント
自分のiMacでときどき何の接続もできなくなっていた理由が、ようやく分かった
**稼働時間(uptime)**が原因だったとはまったく知らなかった
記事を読んでいるとAIが書いたような感じが強すぎて、Appleに実際に問い合わせたのか気になった
もちろんバグは重要だが、大げさな表現が多いと感じた
大半のユーザーはほとんど影響を受けない気がする
Macをスリープ状態にしておけばTCPスタックがリセットされるので、問題を避けられるかもしれない
いずれAppleが修正するだろうが、今すぐパニックになるような話ではない
自動スリープを無効にしたMacBookを50日ほどつけっぱなしにしていたら、pingは通るのにTCP接続がまったくできない現象が起きた
Wi‑Fiを切り替えたり有線接続にしても解決せず、再起動したらすぐ正常に戻った
「再起動よりましな代替の回避策を開発中で、それまでは定期的に再起動してほしい」と言っていたそうだ
そういうときが再起動にちょうどいいタイミングだ
最近のAIが書いたブログ記事は読むのが本当にしんどい
文体が不自然で、要点にたどり着くまでが長すぎる
「50日間テストする開発者なんていない」という話には同意しない
実際には時間を加速したシミュレーションテストをすればいい
この場合は
calculate_tcp_clockのような関数を修正して稼働時間を引数として渡せば検証できるこのバグはOpenClawだけでなく、すべてのTCP接続に影響する
macOSの稼働時間が49.7日を超えると、すべてのTCP接続が影響を受け始める
自分の複数のmacOS機器は600〜1000日以上動かしっぱなしだが、TCP接続は正常に期限切れになっている
カーネルバージョンはそれぞれ20.6.0と17.7.0だ
なので、このバグは特定バージョン以降でのみ発生するようだ
tcp_nowの値はオーバーフロー直前で止まり、タイマー計算の誤ったwraparoundによって負の値になって比較に失敗する短い期間だけTIME_WAIT接続が積み上がる可能性はあるが、元記事は過剰に反応しており、LLMが書いた文章のようだ
関連GitHubリンク
こういう問題はいろいろなソフトウェアで繰り返し起きる
以前、Guild Warsのサーバーでも似たことがあって、オーバーフローを早く起こすために
GetTickCount()へ特定の値を足してテストしていたこのバグはWindows 95の49.7日バグを思い出させる
関連する記事
OpenClawとこのバグがどう関係しているのか気になる
この問題はLinuxカーネルスケジューラの208日バグを思い出させる
参考リンク