日曜日の午後、家のインターネットが落ちた。Mac からも、スマホからも、開発機からも外に出られない。ルーターの管理画面を開くと「インターネット停止」の警告が 15:11 に出ていた。

前回と前々回に続いて、また自宅回線の話になる。ただし今回は今までと落ち方が違った。

結論から書く。

  • 症状: LAN 内は完全に正常なのに、外向きの IPv4 だけが全滅する。パケットは 192.0.0.2 から「宛先に届かない」と突き返されていた
  • 直接の原因: UniFi Cloud Gateway Ultra の WAN ポートが 10Mbps でリンクし、40 秒ほどの周期で切れたり戻ったりしていた。そのせいで WAN 側のグローバル IPv6 アドレスが取れず、その上に乗る transix (DS-Lite) のトンネルが張れなくなっていた
  • 効かなかった対処: ケーブルの挿し直し、UCG の電源入れ直し、ONU の電源入れ直し。全部空振り
  • 効いた対処: ケーブル両端の 2 回目の抜き差し。これで 1000Mbps に戻り、即座に復旧した
  • 根本原因: 特定できていない。容疑者はケーブル・ONU の LAN ポート・UCG の WAN ポートの 3 つで、切り分けきれていない

最後の「特定できていない」が、この記事で一番書きたいところになった。調査は AI(Claude)と対話しながら進めたのだが、途中で一度「ケーブルの接触不良でした」という結論が出て、そのあと撤回している。

症状: ルーターは生きているのに外に出られない

まず切り分けの一手目として、LAN 内とインターネットを分けて測った。

$ ping -c3 192.168.1.1        # ルーター
64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=0.408 ms
3 packets transmitted, 3 received, 0% packet loss

ルーターには 0.4 ミリ秒で応答が返る。LAN は無傷だ。ところが同じ経路で外に出ようとすると、こうなった。

$ ping -c3 -I enp9s0 1.1.1.1
From 192.0.0.2 icmp_seq=1 Destination Host Unreachable
From 192.0.0.2 icmp_seq=2 Destination Host Unreachable
3 packets transmitted, 0 received, +3 errors, 100% packet loss

ここで目に留めるべきは 192.0.0.2 という差出人だ。これは RFC 6333 が DS-Lite のために予約しているアドレスで、うちの場合は UCG 自身が持っているトンネルの入口にあたる。外の誰かが拒否しているのではなく、自分の家のルーターが「そっちには行けない」と言っている。

この時点で、障害はもっと手前、トンネルそのものにあると分かる。

なぜ IPv6 が消えると IPv4 が全部死ぬのか

transix (DS-Lite) は、IPv4 のパケットを IPv6 のトンネルに包んで ISP まで運ぶ方式だ。図にするとこうなる。

[家の中 IPv4] → UCG がIPv6で包む → [ISPのAFTR] → 開封して[インターネット IPv4]
                     ↑
              この土台がIPv6

土台が IPv6 なので、WAN 側の IPv6 が失われると IPv4 は一歩も外に出られない。IPv6 が死ぬと IPv6 が使えなくなるのではなく、IPv4 が丸ごと落ちる。この非対称さが DS-Lite の分かりにくいところだ。

UCG のローカル API で WAN の状態を見ると、案の定こうだった。

wan1:
  ip   = 192.0.0.2
  ipv6 = ['fe80::6e63:f8ff:fe5d:bac5']

fe80:: しかない。これはリンクローカルアドレスといって、同じケーブルの向こう側としか話せない、いわば内線番号にあたるものだ。外と話すためのグローバルアドレスが無い。トンネルの土台が無いのだから、IPv4 が全滅するのは当然だった。

ちなみに UCG のローカル API は、インターネットが死んでいても LAN 経由で普通に叩ける。回線が落ちているときほど頼りになるので、API キーを手元に置いてあるのはこの構成の利点だと思う。

10Mbps という異常値

では、なぜ IPv6 が取れないのか。WAN ポートの物理的な状態を見にいったら、答えがそこにあった。

{'name': 'Port 5', 'up': False, 'speed': 10, 'media': '2.5GE',
 'rx_errors': 0, 'tx_errors': 0}

2.5GbE のポートが 10Mbps でリンクしている。しかも up が False になっている。3 秒おきに 10 回サンプリングすると、状態がひどいことが分かった。

15:30:49 up=True  speed=10
15:30:52 up=True  speed=10
15:30:55 up=False speed=10
15:31:01 up=False speed=10
15:31:09 up=True  speed=10

40 秒ほどダウンして、60 秒ほどアップするを繰り返している。リンクがこの調子では、IPv6 アドレスを配ってもらうためのやりとりが完了しない。IPv6 が取れず、トンネルが張れず、IPv4 が落ちる。連鎖の全体が見えた。

障害の開始時刻は、自宅サーバーで動かしている外形監視の履歴から確定させた。26 個の監視先が 15:09 にいっせいに失敗している。ルーターの警告より 2 分早い。

数字が残っていると「たぶん 15 時ごろ」が「15:09」になる。前回の記事で、疎らなログを繋いで障害時間を 9 時間と書き、あとで訂正する羽目になったことがあったので、今回は最初に履歴を引いた。

試したことと、その結果

ここからは物理作業になる。順番に試した。

1. LAN ケーブルの挿し直し(15:33)

フラッピングは一瞬止まった。30 秒ほどリンクが安定する。しかし速度は 10Mbps のままで、1 分後にはまた切れ始めた。

2. UCG の電源入れ直し(15:39)

これが一番情報量のある一手だった。ルーターは uptime 550 秒、つまり完全な初期状態から立ち上がってくる。それでも症状は寸分変わらなかった。

この時点で、設定・セッション状態・ソフトウェアの残骸といった線が全部消えた。前回の障害は ISP 側のトンネル終端装置が原因で、設定を変えると 10 秒で直った。今回はそういう種類のものではない、と確定できた。

3. ONU の電源入れ直し(15:48)

変化なし。10Mbps のフラッピングが続く。

4. ケーブル両端の、2 回目の抜き差し(16:01)

直った。

16:01:44 up=True speed=1000 | global_v6= ['2409:10:ace0:2800:...']

速度が 1000Mbps に戻り、グローバル IPv6 が生え、4 分間で 150MB のトラフィックが流れた。外形監視も全復旧。不通はちょうど 52 分だった。

「ケーブルの接触不良でした」と書いて、撤回した

ここで Claude は、原因をケーブルの接触不良だと結論づけた。理屈はこうだ。

2.5Gbps と 1Gbps のイーサネットは 4 対 8 本の線を全部使うが、10Mbps と 100Mbps は 2 対 4 本しか使わない。だから一部の線が導通しなくなると、リンクは 10M や 100M まで落ちる。2.5GbE のポートが 10Mbps に落ちたまま戻らないのは、その状態と一致する。エラーカウンタが 0 だったのも、ノイズではなく結線そのものの問題であることを支持している。

そして復旧が抜き差しと同時だった。筋は通って見える。

この結論に、私は引っかかって聞き返した。

「LAN ケーブルは抜き差しなんてほとんどしないのに、急にそんなことがあるんでしょうか」

これは正当な反論だった。触っていないケーブルが、日曜の 15:09 に突然接触不良になる理由を、この結論は何も説明できていない。

推論を見直してもらうと、穴が 2 つ出てきた。

穴その 1: 「10Mbps だからケーブルの断線」は言い過ぎだった。

確かに線が切れれば 10M に落ちる。しかし 10M に落ちる原因はそれだけではない。両端のチップ(PHY)の不調でも、速度を決めるためのやりとり(オートネゴシエーション)の失敗でも、同じように落ちる。「A ならば B」から「B だから A」を導いてしまっていた。

しかも、よく考えるとこの症状はケーブル断線説と相性が悪い。線が物理的に切れているなら、10Mbps で安定してリンクするはずだ。40 秒周期で切れたり戻ったりするのは、もっと動的な何かが起きている。挙げた証拠のうち、都合のいい半分しか見ていなかった。

穴その 2: 「再起動で直らなかったから無実」は成り立たない。

推論の中では、UCG を再起動しても直らなかったことを、暗に「UCG は悪くない」の根拠として扱っていた。だがハードウェアが壊れているなら、再起動で直らないのは当たり前だ。あの実験で否定できたのはソフトウェアの状態だけで、UCG のポートが壊れている可能性は少しも減っていない。

もう一つの仮説: オートネゴシエーションのデッドロック

撤回したうえで、あらためて仮説を並べ直した。その中で、観測をいちばん広く説明できるものがある。

速度を決めるやりとりが、異常な状態で固着したという説だ。

観測されたことこの説での説明
誰も触っていないのに発症した瞬断などをきっかけにネゴシエーションが失敗し、そのまま固まった
UCG を再起動しても直らない向こう側(ONU)が状態を保持している
ONU を再起動しても直らない今度は UCG 側が状態を保持している
ケーブルを抜くと直る両端を同時に、数秒間だまらせることができる
10Mbps + 不安定ネゴシエーションに失敗した結果、いちばん下のモードに落ちている
エラーカウンタが 0データが化けているのではなく、繋ぎ方の交渉が失敗している

この説なら「触っていないケーブルが突然壊れる」という不自然な前提を置かずに済む。片側ずつの再起動が 2 回とも空振りし、両端を同時に切ったときだけ直った、という順序ともよく合う。

ただしこれも証明はできていない。ONU の電源を切れば片側のキャリアは落ちるはずで、それでは不十分だった理由を完全には説明できない。

結局、今わかっているのはここまでだ。

  • 確実: リンク層の障害だった。ソフトウェアや設定ではない。ISP 側のトンネル終端装置の問題でもない
  • 不明: ケーブル・ONU のポート・UCG のポートのどれが悪かったか

n は 1 で、しかも復旧の直前に再起動を 2 回挟んでいる。この条件で犯人を名指しするのは、証拠が足りない。

分からなかった本当の理由は、記録が無かったこと

犯人を特定できなかったのには、切り分け作業を尽くせなかったこと以上に、根本的な理由があった。

過去のデータが 1 バイトも無かった。

UCG のイベントログは再起動で消えていた。自宅の Prometheus には 26 サイトの外形監視や太陽光発電のデータまで入っているのに、肝心のルーターの WAN ポートの指標だけが無かった。

だから「以前にも 10Mbps に落ちたことがあるか」という、仮説の切り分けに直結する問いに答えられなかった。もし半年前にも同じことが起きていたなら、ハードウェアが少しずつ壊れている話になる。今回が初めてなら、一回限りの事故の可能性が高い。同じ観測データでも、過去があるかないかで意味が反転する。

ここが本当の反省点だった。原因を外したことよりも、外したときに参照するものが無かったことのほうが痛い。

記録を取る仕組みを足した

そこで、WAN ポートのリンク速度と上下を毎分記録する仕組みを自宅サーバーに常設した。UCG のローカル API を叩いて、速度・リンク状態・エラーカウンタ・WAN 側 IPv6 の有無を Prometheus に流し込む。

判定の設計で気をつけたことが 3 つある。

リンクが完全に落ちているときは鳴らさない。回線の断やメンテナンスと区別が付かないからだ。見るのは「リンクは上がっているのに速度が出ていない」という、リンク層だけが壊れている状態に絞った。今回の症状はまさにこれだった。

正常値は 1000Mbps であって 2500Mbps ではない。うちの ONU が 1GbE なので、2.5GbE のポートでも 1000 で頭打ちになる。これを知らずに「2500 でなければ異常」としきい値を置くと、毎分鳴り続ける。異常の基準は「10 や 100 に落ちているか」にした。

値の監視には必ず鮮度の監視を添える。仕組み自体が止まったときに黙ってしまっては意味がない。「監視が動いていない」「データが届いていない」「API を読めない」の 3 本を別建てで足した。これは以前、監視の沈黙を正常と読み違えた経験から決めているルールになっている。

そしてもう一つ、私から頼んで入れた機能がある。劣化を検知したら、通知に切り分けの手順を書いて送るようにした。

復旧を急ぐ前に、この順で 2 つだけ試してください。

  1. ケーブルを別のものに交換 → 直れば犯人はケーブルで確定
  2. 直らなければ ONU と Mac を直結し、Mac 側のリンク速度を見る → 1Gbps で安定すれば UCG のポート、不安定なら ONU の故障と確定

今回いちばん悔しかったのは、復旧を優先した結果、切り分ける前に直ってしまったことだ。回線が落ちている最中は誰もが早く直したいので、証拠を取る作業は後回しになる。だからその場で「これを先にやってください」と言ってくれる仕組みにしておく。人間の判断力に期待しない、という設計にした。

導入したあとは、指標を意図的に異常値にして、5 分後にアラートが発報し Slack に届くところまで確認した。監視は足したら発火させるまでが一組だと思っている。

まとめ

  • 外向きのパケットが 192.0.0.2 から拒否されるなら、それは ISP ではなく自分の家のルーターが返している。DS-Lite のトンネルが張れていない合図になる
  • DS-Lite では WAN の IPv6 が消えると IPv4 が丸ごと落ちる。IPv6 が使えなくなるのではない、という非対称さが分かりにくい
  • リンク速度を最初に見る。10 や 100 に落ちていたり、上下を繰り返していたら、その時点でリンク層の障害だと確定できる。設定をいじる前にここを見れば、今回なら 1 分で見当がついた
  • リンク層だと分かったら、電源ではなくコネクタを触る。片側ずつの再起動は 2 回とも空振りし、両端を同時に切ったときだけ直った
  • 1 回の抜き差しで直らなくても、もう一度やる価値がある
  • そして、証拠が取れないうちに直ってしまうと原因は分からないままになる。急いで直したい気持ちと、原因を突き止めたい気持ちは両立しない。だから次に備えて、記録を自動で取っておく

今回いちばん学びになったのは、技術的な内容より、出てきた結論に反証をぶつけることの大切さだった。「触っていないケーブルが急に壊れるのは不思議だ」という素朴な疑問は、AI が組み上げた理屈よりも強かった。都合のいい証拠だけを拾って筋が通ってしまうと、それらしく見えて疑いにくい。

次に同じことが起きたら、今度は記録が残っている。そのときは犯人を名指しできるはずだ。