いつものように自宅サーバー(Raspberry Pi)にSSHしようとしたら、見たことのないエラーが出ました。
$ ssh omikuji@home.local
ssh: connect to host home.local port 22: Undefined error: 0
「Undefined error: 0」。エラーが未定義とはどういうことなのか。
鍵を変えても同じエラーだったので、「あ、これはサーバーが落ちたな」と思って調査を始めたのですが、結論から言うと サーバーもルーターも完全に無実 でした。
犯人はまさかの、手元のMacのプライバシー設定です。
症状があまりにもネットワーク障害の顔をしていて切り分けにかなり苦労したので、同じ罠にはまった人のために記録を残しておきます。
サーバーは死んでいるのか?
まずは定番の生存確認から。
ping home.local→ 応答なし- mDNSでの名前解決 → なぜかIPv6のリンクローカルアドレスしか返ってこない
- IPv4を直打ちしてping →
No route to host
ここまで見ると完全に「サーバー死亡」です。
ところが一つだけ引っかかることがありました。このサーバーで動かしているWebサービスは、Cloudflare Tunnelで外部公開しています。それが 外からは普通に閲覧できる のです。
Cloudflare Tunnelはサーバー側から外に張りに行く接続なので、つまりサーバーは生きていて、インターネットにも出られている。
死んでいるのは「私のMacからサーバーへの経路」だけということになります。
深まる謎:ゲートウェイだけは通る
そこで、Macからどこへなら通信できるのかを総当たりで切り分けました。
| 通信先 | 結果 |
|---|---|
| ルーター(192.168.1.1) | ✅ 通る |
| インターネット | ✅ 通る |
| ルーターの上流にある別セグメント | ✅ 通る |
| 同じLANのサーバー(192.168.1.141) | ❌ No route to host |
| 同じLANのカメラ・スマホ・ロボット掃除機 | ❌ 全滅 |
No route to host は、同一セグメント宛てならARP(IPアドレスからMACアドレスを解決する仕組み)に誰も答えていないときに出るエラーです。
LAN内の機器が全員ARPを無視しているのに、ゲートウェイだけは元気に応答する。おかしい。
この「ゲートウェイとインターネットはOK、隣の機器は全滅」というパターンは、無線LANの クライアント分離(Client Isolation) の典型的な症状です。ゲスト用Wi-Fiでよく使われる、端末同士の通信を遮断する機能ですね。
私はUniFiのルーターを使っているので、真っ先にこの設定を疑いました。
しかし管理画面を隅々まで見ても、クライアント分離はオフ。SSIDの設定も何も変えていない。ネットワーク側に犯人が見つかりません。
sudo tcpdump で世界が変わった
次の一手として、「そもそもARP要求は電波に乗っているのか」をパケットキャプチャで確かめることにしました。
$ sudo sh -c 'tcpdump -i en0 -n -e arp & ping -c 5 192.168.1.141'
すると、衝撃の結果が。
64 bytes from 192.168.1.141: icmp_seq=0 ttl=64 time=22.713 ms
Request who-has 192.168.1.141 tell 192.168.1.152
Reply 192.168.1.141 is-at 2c:cf:67:xx:xx:xx
pingが通っている。 ARP要求も応答もきれいに流れています。
「直ったのか?」と思って直後に普通にpingを打つと、また No route to host。
ここでようやく気づきました。違いはネットワークではなく実行方法です。
「sudoで実行したときだけ、通っている」
正体:macOSの「ローカルネットワーク」権限
macOSには「ローカルネットワーク」というプライバシー権限があります。アプリがLAN内の機器と通信することを、アプリ単位で許可制にする仕組みです。
iPhoneで見覚えのある「〜がローカルネットワーク上のデバイスの検索および接続を求めています」のMac版ですね。
私の環境では、ターミナル(Alacritty)がこの権限を 持っていない状態 になっていました。そうなると挙動はこうなります。
| 通信先 | 結果 | 理由 |
|---|---|---|
| デフォルトゲートウェイ | 通る | 権限の適用除外 |
| インターネット | 通る | ローカルネットワークではない |
| ルーター越えの別セグメント | 通る | 同上 |
| 同一LANの機器 | ブロック | 権限がない |
| rootで実行したプロセス | 通る | プライバシー制御(TCC)の対象外 |
先ほどの切り分け結果と完全に一致します。ゲートウェイだけ通るのは仕様、sudoで通るのも仕様。
ネットワーク障害にしか見えなかった症状は、すべてこの権限の仕様どおりの動きでした。
たちが悪いのは、ブロックされたときのエラーがコマンドによって No route to host や Undefined error: 0 という、権限とは無関係な顔をしていることです。
「アクセスが拒否されました」とでも言ってくれれば一瞬で気づけたはずですが、エラーメッセージからプライバシー設定を連想するのはほぼ不可能だと思います。
なお、思い当たる節としては直近のmacOSのメジャーアップグレードがありました。アップグレード後にローカルネットワーク権限がリセットされたり、許可ダイアログが出ないまま拒否扱いになったりするのは既知の問題のようです。
「ネットワーク構成は何も変えていないのに、ある日突然SSHできなくなった」という状況はこうして完成しました。
直し方
分かってしまえば一瞬です。
システム設定 → プライバシーとセキュリティ → ローカルネットワーク → 使っているターミナルアプリをオン
これだけで、何事もなかったかのようにSSHできるようになりました。
一覧にアプリが表示されていない場合は、ターミナルからLAN内の機器へpingを打つと追加されることがあります。それでもダメならMacの再起動で一覧が更新されるケースが多いようです。
教訓
今回の調査で得た教訓をまとめておきます。
- Macで突然
Undefined error: 0やNo route to hostが出たら、ネットワークを疑う前に プライバシー設定のローカルネットワーク権限 を確認する - 「ゲートウェイとインターネットは通るのに、LAN内の機器だけ全滅」はこの権限の典型症状
- sudoの有無で挙動が変わるかどうかは、TCC絡みの問題を見抜く強力な切り分けになる
- macOSのメジャーアップグレード後は、権限のリセットを疑う
ちなみにこの自宅サーバーがなぜRaspberry Pi 1台+Cloudflare Tunnelという構成に落ち着いたのかは、以前の記事にまとめています。
👉 自宅サーバーとDocker Swarmに夢を見て、結局すべて手放した話
サーバーの無実が証明されて何よりでした。疑ってごめん。