Tailscale を Plan 9 へ移植する作業
(tailscale.com)- エイプリルフール発表として始まった Plan 9 対応は、実際の PR とカーネル・Go の修正へとつながり、2025 年 4 月 2 日時点で Tailscale が Plan 9 上で動作する状態にまで到達した
- 単なる
GOOS=plan9 GOARCH=386のビルド問題ではなく、Go の Plan 9 ポートで ランタイムクラッシュとコンパイラの特殊処理の問題が先に明らかになった - Russ Cox による Plan 9 カーネルおよび Go ランタイム・コンパイラの修正により、SSE、浮動小数点コンテキスト、単調時刻、DNS、開発環境の問題があわせて整理された
- Tailscale は Plan 9 の
/netファイルインターフェイスを活用して TUN 風の実装、ルーティング、Tailscale SSH、MagicDNS、サービス収集を組み込んだが、一部は暫定実装または未完成である - 現在の検証範囲は主に 9legacy と
GOARCH=386であり、9front・amd64・exit node・Go のnet/netns対応には追加検証や再設計が必要である
エイプリルフールの冗談が実際の移植へつながる
- Tailscale は 2025 年 4 月 1 日に Plan 9 対応を発表し、翌日、この発表が実際に動作する移植作業をもとにしていたという背景を公開した
- 作業は Tailscale PR と、複数の Plan 9・Go 修正へとつながった
- 初期のアプローチは、Tailscale の Go バイナリ 2 つを
GOOS=plan9 GOARCH=386 go install ./cmd/tailscale{,d}でビルドすればよいだろうという期待から始まった - 2023 年 8 月の最初の試みでは一部のビルドは進んだが、実行中に異常クラッシュが発生した
- Go の Plan 9 ポートが first-class port ではなかったため、リグレッションが放置された状態だった
- Tailscale が Plan 9 上で、従来よりも Go を強く使い込んだ可能性もある
- 移植作業は 2024 年を通じて止まっていたが、2025 年 3 月にエイプリルフールのアイデアとともに再開された
SSE と Go の Plan 9 対応の整理
- 1999 年に Intel Pentium III に導入された SSE 命令が、今回の作業の主な出発点の一つだった
- Go コンパイラは Plan 9 ターゲットで SSE の使用を避けようとしていた
- Plan 9 カーネルが note handler で SSE レジスタを保存・復元していなかったためである
- Go コンパイラは、どのコードが note handler 内で実行されるのか分からないため、SSE をグローバルに無効化しようとしていた
- この特殊処理はたびたび壊れ、コンパイラの各所に
plan9例外が増えていった
- Russ Cox は、Plan 9 カーネルが note handler で浮動小数点・SIMD コンテキストを扱うよう修正した
- 386 側には sys/src/9: allow floating point in note handlers の修正が入った
- amd64 9k カーネルでは、fork 後の FP 状態の aliasing、note handler の SIMD、
noted(NCONT)のレジスタ喪失など、追加の問題が見つかった
- Go 側では Plan 9 コード生成の特殊処理削除 が進み、
tailscaledがより長く実行できるようになった
IPC と開発環境
- その後
tailscaledは、スタック破損ではなく メモリ不足でクラッシュし始めた - 以前の Plan 9 移植の試みで、Tailscale の
safesocketIPC パッケージに goroutine を無限に作るバグがあった - ひとまず localhost TCP を使うよう変更することで問題は解決した
- Plan 9 の「すべてがファイル」という方式にはあまり合わないが、他の Plan 9 サービスも localhost TCP を使っていることを確認した
- 将来的には Russ が Go に移植した srv9p package を使って LocalAPI を載せる方式の方がよいかもしれない
- 現在の実装は他のプラットフォームのような localhost 認証を付けられないため、共有 Plan 9 マシンでは使わないよう明記されている
- 初期開発は 9legacy CD イメージベースの VM で行われ、バイナリを HTTP でダウンロードして実行する反復作業は遅かった
- Russ Cox が作成した rsc/plan9 は、Plan 9 ソース、事前コンパイル済みバイナリ、
./boot/qemuスクリプトを含む- qemu VM はディスクなしで起動し、localhost の 9P サーバが提供する Git リポジトリをルートファイルシステムとして使う
- 開発マシンと Plan 9 ファイルシステムを共有することで、反復時間が分単位から秒単位に短縮された
- qemu は virtio も使うため、さらに高速になった
ネットワーク統合: TUN、ルーティング、MagicDNS
- 最初に動作した Tailscale は、カーネルのネットワークスタックを使わない ユーザー空間ネットワーキングモードだった
- TCP、UDP、ICMP などは gVisor の netstack を通じて処理された
- Plan 9 マシンから tailnet へアクセスするには、
tailscaledの HTTP/SOCKS5 プロキシを使う必要があった - Plan 9 プログラムのうち
HTTP_PROXYやALL_PROXY環境変数を認識するものはほとんどなく、理想的ではなかった
- Plan 9 の TUN 風実装は非常に単純だった
/net/ipifc/cloneを開き、新しいインターフェイス番号を読む- control fd に
"bind pkt\n"を書き込むと、/net/ipifc/2/*のような新しいインターフェイスが作られる /net/ipifc/2/dataを開き、IP パケットをそのまま読み書きする- 別途 ioctl や長さフレーミングは不要である
- ルーティングテーブルの操作も
/net/iprouteファイルを通じて行われる"tag tail\n"を書き込んで、以後追加される route にtailタグを付ける"add 100.64.0.0 /106 100.102.103.104"のようなメッセージで route を追加する- Plan 9 内部は IPv6 中心で、IPv4 を IPv4-mapped IPv6 アドレスとして扱うため、CGNAT
100.64.0.0/10は/106のように表現される
- MagicDNS は Plan 9 で peer に
fooまたはfoo.tailnet-name.ts.netのような名前でアクセスできるようにする作業だった/net/dnsや/net/csの問い合わせを横取りする方法も議論された- 最終的に Russ が、特定の DNS suffix に対して代替 DNS サーバを指定できるよう Plan 9 を修正した
- DNS 問い合わせが誤って negative cache される問題も 修正 された
Tailscale SSH とサービス収集
- Tailscale SSH は
tailscaled内蔵の SSH サーバで、パケットに結び付いた WireGuard キーを通じて、既知の Tailscale identity として認証する - 最初は Plan 9 のシェルである
/bin/rcをos/exec.Commandで実行し、stdin/stdout を接続した- シェルは実行されたが、echo、ナビゲーション、プロセス interrupt などが正しく動作しなかった
- Russ は netshell example を 9fans/go に追加した
- この例は非常に安全でない telnet サーバに近かったが、Tailscale SSH の背後に接続するには十分だった
- その後、SSH で Plan 9 の
/dev/snarfの内容を取得したり、ノート PC で Go テストを cross-compile した後に SSH で実行したりしやすくなった
- Tailscale の任意機能である サービス収集も Plan 9 向けに検討された
/proc/NNN/fdを巡回して/net/tcp/cloneを開いているプロセスを探す- fd の QID を
/net/tcp/NNN/{status,local}と照合して、listening 状態かどうかとポートを確認する - QID から TCP 番号を計算する方式はカーネル実装の変化に弱く、惜しい部分として残っている
時間、Web デモ、v86
- gVisor netstack が単調時刻(monotonic time)が戻ったと報告し、
tailscaledがクラッシュする場合があった- Go の Plan 9 時刻実装が、単調時刻として wall time を使っていた
- ntpd が時計を戻すと、netstack の単調時刻に関する前提が壊れる
- Russ は Plan 9 の
/dev/bintimeに 単調時刻を追加し、Go がそれを使うよう 修正 した - Web 上で Plan 9 を実行するには v86 が使われた
- v86 は WASM で 32 ビット OS を実行し、複数のネットワーク方式を提供する
- これも
GOARCH=386に集中した理由の一つだった
- 当初は Ethernet フレームを websocket relay へ送るため、Tailscale のネットワークシミュレーション環境に wsproxy protocol support を追加した
- ARP、DHCP、DNS、NAT、control plane、DERP などを gVisor netstack で模倣する統合テスト環境で動作する
- しかし DHCP の往復のため、relay が遠いと Plan 9 GUI である
rioの起動が遅くなる
- その後 WISP サーバも実装したが、本番化前に時間が足りず、copy.sh/v86 のデフォルトネットワーク relay 設定でリリースした
- Tailscale と Plan 9 を入れたディスクイメージは 16MB で、Tailscale バイナリは展開後 23MB だった
- 起動時に “gunzip…” 段階が見える理由はここにある
- サンプルイメージは
copy.sh/v86の 9legacy プロファイルに含まれる
残された作業と実際の成果
- 現在の Tailscale Plan 9 移植は 9legacy でのみテストされている
- Plan 9 の主要な fork には、最小限の 9legacy と、より多く修正された 9front がある
- Russ が 9legacy に書いた一部のパッチは、9front へ移植する必要があるかもしれない
GOARCH=amd64の 64 ビット対応も、まだ検証が必要である- exit node 対応と Go の
net/netnsパッケージ対応は実装されていない- そのためには、Tailscale が Plan 9 上で自身を別個の
/netとして見せる方式など、再検討が必要になる可能性がある
- そのためには、Tailscale が Plan 9 上で自身を別個の
- 今回の作業により、Go の Plan 9 対応も改善された
- cmd/compile: use FMA on plan9, and drop UseFMA
- runtime: remove nextSampleNoFP from plan9
- cmd/compile, runtime: remove plan9 special case avoiding SSE
- net: fix parsing of interfaces on plan9 without associated devices
- os: guarantee min buffer size for ReadFile reads on /proc-like files
- net: unblock UDP Reads upon Close on plan9, add test
- runtime: fix plan9 monotonic time, crypto randomness
- 特に Go コンパイラから Plan 9 の特殊処理を削除したことで、コンパイラはより単純になり、修正しやすくなった
- v86 デモ公開時点では、v86 作者のエイプリルフールのいたずらにより VGA テキスト出力まで偽のオランダ語のように表示される問題があったが、
&nojokequery argument で回避できた
1件のコメント
Hacker News のコメント
気になることがあれば答えられる
今、何人かが https://meet.google.com/qre-gydb-mkv でこの話をしている
追記: 1時間たって全員退出した
以前の 4月1日のブログ記事は https://tailscale.com/blog/tailscale-enterprise-plan-9-suppo... だった
Russ Cox がこの冗談を最後までやり切ったのは本当に伝説的
9fans のリストには、エイプリルフール用にこんなのが投稿されていた
mips、386、arm、arm64、amd64 といった未成熟なコンピュータアーキテクチャの保守コストが大きすぎるため、より成熟して安定したアーキテクチャに集中することにした、という内容
対象は power64 と itanium で、したがって power64 と itanium を除くすべてのアーキテクチャは凍結・保存され、EOL に昇格されるとのこと
冗談ではなく、本当に Plan 9 Enterprise 版があったらいいのにと思う
最近はスクリプトのほとんどを
rcで書いているけど、うちは nix を使っていて dirnev で自動的に取り込めるので、同僚たちも目をつぶってくれているし、かなり良かったrcスクリプトを実行できるかより、それを読んで修正できるかを心配すると思うrcの利点の一つはこれ[1]:「rc の設計で最も重要な原則は、マクロ処理器ではないという点だ。入力は字句・構文解析コードで決して2回以上スキャンされない」
以前働いていた Unix 企業で、実行中のシェルスクリプトが修正されたせいで作業ディスクの大半を消したことがある。幸い日次バックアップをテープに保管していたし、およそ17年前の話だった
[1] https://www.scs.stanford.edu/nyu/04fa/sched/readings/rc.pdf
最初の記事を見逃していて、とにかく自分で試してみたいなら、この v86 イメージで動く:
https://copy.sh/v86/?profile=custom&m=768&vram=16&hda.url=ht...
VM 内で
tailscaledとtailscaleを起動できる。プロキシの可用性が限られているため、オンラインになるまで少し時間がかかる場合がある追記: alt が第3ボタンとして機能する。ターミナルを起動するには、alt を押したまま右クリックして new を選び、alt から手を離して右クリックドラッグでターミナルウィンドウのサイズを調整すればよい
ウェビナーが進行中(Google Meet) https://ftp.plan9.ts.net/webinar
冗談の前提は気に入ったけど、説明が長くなるほど急に気が滅入ってきた
壊れているものが多すぎるし、複雑さも大きすぎる。結局何のためなのか、ネットワークトンネルを1つ作るため? この追加作業自体が冗談だったなら笑えたと思う
rsc、rob pike、bradfitz とは、特に Plan 9 の話で何時間でも捕まえて話していられそうだ。もちろん彼らの時間を完全に無駄にすることになるけど
あのオペレーティングシステムは本当に魅力的だ
キャリア初期に一緒に働いていた専門家が隣に座って、私が十分理解するまで辛抱強くやり方を見せ、質問に答えてくれたことを思い出す。深い水に落ちても泳げるようにしてくれるような感じで、3時間で特定分野の学士号を1つ取ったような、キャリアの中でも最速の成長の一つだった
C も知らないし、Plan 9 を生産的に使えるほど知っているわけでもないけれど、現在の主要3大OSにはないことを惜しむためだけでも、もっと知って学びたい素晴らしく有用な機能がある
お金があるなら、Go の知識を広げるために3人全員と直接話す時間を買い、ずっと望んでいたのに自力では得られなかった Plan 9 の理解のために rsc と rob pike の時間も買いたい
Plan 9 が本当に好きだ。その原則をたくさん取り入れて自分の OS を作るのが、引退後のプロジェクトであり人生の目標
追記: このプロジェクト名は「chaos10」で予約してある。SerenityOS みたいに計画はないだろうから
これを動かすために Plan 9 カーネルまでパッチするとはまったく予想していなかった