Logo address

9P

2021/11/25

想定される読者

UNIX 系 OS としては、ここでは macOS、Ubuntu、FreeBSD を採り上げる。以下ではこれらを(特に断らない限り) "unix" と書く1
また Plan9 に関する説明は、その派生 OS である 9front で試されている2。特に断りが無い限り、"Plan9" は 9front のことと考えて欲しい。


補注1. Ubuntu と FreeBSD のバージョンは新しいが、macOS は(10.12.6)と古い。僕の MacBook は古くてバージョンアップできないのである。
補注2. OS をシステムコールのレベルで分類すると Bell-Labs の Plan9 の最終版と 9front は完全に同じである。従って(CPU が同じであれば)バイナリー互換性を持っている。

はじめに

Plan9 の file sharing protocol である 9P が現在の主要 3 OS で正式に採用され始めている。

Plan9 の文字コード utf-8 は現在では完全に文字コードのスタンダードになっている。
僕の記憶では Apple が Mac の OS X で採用したのが Plan9 以外では最初だったと思う。utf-8 が世に現れてから 10 年を要した。僕が愛用していた NeXT を捨てて、OS X に移行したのはひとえに utf-8 が理由である。僕が Linux 系では Ubuntu に移ったのも utf-8 が理由であった。

9P が現在の主要 3 OS で正式に採用され始めていることを知って、ついにそのような時代になったのだと感じ入っている。9P が世に現れてから 30 年を要したのだ。これで間違いなく 9P が file sharing のスタンダードになるはずである。NFS も CIFS も WebDAV も捨てられていくはずである。個人のパソコンがこの恩恵を直接受けることは考えにくい。しかし企業や学校など大量にパソコンを導入している組織では、運営の方法が劇的に変化していくことは間違いない。もちろん管理が楽になる方向へ1。管理者にとっては 9P が必須の知識になるはずである。

9P の file sharing の能力は素晴らしい。インターネットレベルでも快適に file sharing ができる。
それを示すデモがある。
Plan9 からは

srv -m 'net!9p.io!wiki' wiki
unix からは p9p(plan9port) を使って
srv 'net!9p.io!wiki' wiki
9 mount wiki /n/wiki
を実行すれば、wiki ページが /n/wiki にマウントされる2。相手はインド洋にあるのだ。

9P が普及すれば、ネットワー上のファイルの参照は劇的に楽になる。プログラマーはネットワークを意識しなくても、伝統的な方法でファイルを見ることができるようになる。素晴らしいことである。

BIOS もまた劇的に変わる可能性がある。現在の BIOS は UEFI が主流であるが、やがては 9P をサポートし、そして 9P に置き換わる可能性がある。9P は UEFI の目指す方向にピッタリだからである。実際にこの方向を試みている人がいる[1]。


注1. ITmedia は次のように報じている:「みずほの基幹システム「MINORI(みのり)」は令和元年7月に全面稼働した。これに伴う人事異動などによって、平成30年3月末時点で1143人だった担当者は今年3月末時点で491人となり、3年間で57%減った。」( https://www.itmedia.co.jp/business/articles/2109/01/news085.html )
IT 技術者は墓掘り人として使われているのだ。みずほの場合は自分たちの墓を掘らされていたことになる。このような社会は非常に残念だ。IT 技術者が自らを守るためにも、企業という狭い枠を超えた横の繋がりを大切にしなくてはならない。
注2. p9p の場合には前もって /n/wiki を作っておく必要がある。また FreeBSD の FUSE は問題があって p9p の mount が働かない。

[1] UEFI 向け 9P File Systemを作ってクラウドからネットワークブートできるようにした
https://retrage.github.io/2020/06/15/9pfspkg.html

9P

9P プロトコルの名称

name comment
9P1 used 2'nd and 3'rd edition of Plan9
9P2000 official name for 9P in 4'th edition of Plan9
9P2000.u unix extension proposal[3]
9P2000.L subset of 9P000 for unix poposed in DIOD[4]
9P2010 successor of 9P2000.u
これらはカール組み込みの 9P プロトコルである。

9P2000.u は風呂敷を広げすぎていると思う。多分挫折する1。(挫折した?)
9P2000.u の後継として 9P2010 が提案されている[6,7]。

Plan9 以外では、現在ともかく動いているらしいのは 9P2000 のサブセットである[4]。主要な OS (macOS, Windows, Linux, FreeBSD) である程度使えるらしい2。しかし僕は(現在のところ)論評するだけの材料を持ち合わせていない。

文献[1]が 9P に関する最も基本的な文献である。9P プロトコルの内容が解説されている。なおこの文献によると 9P は "Plan 9 File Protocol" の略語である。

9P は通信路に関しては何も定めていない。ここで言う「通信路」には、使用される暗号も含まれる。また認証の方式も定めていない。9P による接続が成功するためには認証の方式と暗号の選択に関するホストとクライアントとの交渉が成立しなければならず、それに応じた変種もまた発生し得る。例えば unix の 9P server である u9fs では「信頼できるホスト」による「認証」をも許している。


注1. Ron Minnich 氏の Go9P では 9P2000.u がサポートされている
注2. 文献[5] によると macOS Mojave 10.14 でサポートされたらしい。僕の MacBook は古くて、Mojave はインストールできない。

9P service

9P を使った service port の名称
name comment
9fs /lib/ndb/common in Plan9
9pfs /etc/services in FreeBSD
u9fs u9fs マニュアル[2]

注: これらは同じものの別称(方言)である

Plan9 で使われた service port は 564 で、service 名は 9fs である。文献[2]によると 9fs は "Plan9 file service" の略語である。
Plan9 の中では 9fs の名称は現在も使われている。service 名称としても、コマンド名称としても。
文献[2]によれば、u9fs で使用されるプロトコル名は 9fs にする予定だったらしい。しかし inetd のサービス名を数字から始められない unix システムが存在したために u9fs にしたとのことである。

9P プロトコル

文献[1]によると 9P message の集合は次のとおりである。
size[4] Tversion tag[2] msize[4] version[s]
size[4] Rversion tag[2] msize[4] version[s]

size[4] Tauth tag[2] afid[4] {[s] aname[s]
size[4] Rauth tag[2] aqid[13]

size[4] Rerror tag[2] ename[s]

size[4] Tflush tag[2] oldtag[2]
size[4] Rflush tag[2]

size[4] Tattach tag[2] fid[4] afid[4] uname[s] aname[s]
size[4] Rattach tag[2] qid[13]

size[4] Twalk tag[2] fid[4] newfid[4] nwname[2]
nwname*(wname[s])
size[4] Rwalk tag[2] nwqid[2] nwqid*(wqid[13])

size[4] Topen tag[2] fid[4] mode[1]
size[4] Ropen tag[2] qid[13] iounit[4]

size[4] Tcreate tag[2] fid[4] name[s] perm[4] mode[1]
size[4] Rcreate tag[2] qid[13] iounit[4]

size[4] Tread tag[2] fid[4] offset[8] count[4]
size[4] Rread tag[2] count[4] data[count]

size[4] Twrite tag[2] fid[4] offset[8] count[4]
data[count]
size[4] Rwrite tag[2] count[4]

size[4] Tclunk tag[2] fid[4]
size[4] Rclunk tag[2]

size[4] Tremove tag[2] fid[4]
size[4] Rremove tag[2]

size[4] Tstat tag[2] fid[4]
size[4] Rstat tag[2] stat[n]

size[4] Twstat tag[2] fid[4] stat[n]
size[4] Rwstat tag[2]

Txxx はクライアントからサーバーへの message
Rxxx はそれに対するサーバーからクライアントへの応答である。
9P は、まずクライアントからサーバーへの挨拶から始まる。Tversion
その時クライアントの能力(versionが示される。それに基づいてサーバーが version を決める。

クライアントはサーバー X に接続(attach)したいとする。接続要求は Tattach である。この時クライアントはサーバーに対して X でのアカウント名(uname)と aname を伝える必要がある。aname は Plan9 を使ったことのない人には分かり難いと思うので説明しておく。Plan9 のファイルシステムは複数の独立した木から構成されている。僕が使っているファイルシステムでは main と other を持っている。main は主の木で dump 機能を持っている。other は持たない。main に接続したいなら aname は空欄でよい。other に接続したいなら anameother にする必要がある。

unix の場合も性格の異なるいくつかのファイルシステムを持てる。しかしそれらは kernel によって1つの木に統合されている。Plan9 の場合には統合はエンドユーザーのニーズに従ってエンドユーザーのレベルで行う。これは Plan9 の最大の特徴である private name space の賜物である。

さて Plan9 システムでは認証サーバーを置き、他のサーバーへの認証はチケット方式を採用している。このアイデアは MIT の Kerberos に由来すると思う。実は僕は Kerberos の発表会の会場(MIT)に居たのだ。1890 年代の後半だったと思う。あの頃は研究テーマの選択に迷っていたからね。

Kerberos はあれからバージョンアップを繰り返し、現在は ver.5 である。Plan9 の当初の認証は初期の Kerberos の影響を受けていると思える。問題を抱えていたらしく、Plan9 ver.4 で factotum を導入して練り直された。factotum については文献[9]が基本的。文献[10]は、Russ が[9]をどこかで講演したときのスライドであろう。エッセンスが完結に纏まっている。文献[11]に Kerberos と factotum を比較したスライドがある。factotum を理解する上で良い文献だと思う。この文献を見つけたのはチケットの有効期限が気になったからである。この文献には Expiration isn't part of the Plan 9 scheme とある。

クライアントはサーバー X のチケットを持っていなければ、X にアクセスする前に X の認証サーバー A にチケットをお願いしなくてはならない。X と A を関係づける情報は ndb/local の中に含まれている。例えば次のようになっている。

sys=X
	auth=A
サーバーも同じ情報を持っていて突き合わせる。

認証が成立するためには認証方式や password が問題であることは当然であるが、この他に認証に柔軟性を持たせるための仕組みがある。同様な仕組を HTTP では realm と言っている。HTTP サーバの保護された領域にアクセスすると、realm xxx のユーザー名とパスワードを入れてくださいと表示される。これを Plan9 は「認証ドメイン(authentication domain)」と言い、ndb/local の中では authdom で指定している。1つの認証ドメインに対して1つの認証サーバーが対応する。従って認証ドメインが同じならば異なるサーバーでも同じチケットが使える1

この認証ドメインの情報は、クライアント側では factotum key の中に含まれている。例えば

key proto=p9sk1 dom=xxx user=arisawa !password?
の中の xxx がそうである。認証が成立するためにはクライアント側とサーバー側がこのキーに含まれる情報を共有している必要がある。

一般にインターネットの世界ではメッセージは遅延を伴う。そのためにメッセージの送信の順序と受信の順序が一致している保証は無い。tag はこの問題を解決するために存在する。Txxx の応答 Rxxx の tag は Txxx の tag と一致していなければならない。

tag が存在することによって、クライアントはサーバの応答を待つことなく、次々に要求を出すことができる。もちろんその場合には、異なる tag を用いる必要がある。

fid と qid に関しては説明が長くなるので文献[1]を参照願いたい。


注1. チケットは NVRAM に含まれるキーで暗号化されている。チケットを受け取ったサーバーは、NVRAM のキーで戻せることでチケットの正当性を確認できる。チケットの中には認証ドメイン名の情報は含まれない。世界中には多数の Plan9 のシステムが存在する。それらは認証サーバーを中心としたクラスターを形成しているのである。

文献

[1] man INTRO(5)
introduction to the Plan 9 File Protocol, 9P
http://man.cat-v.org/plan_9/5/

[2] U9FS(4)
http://man.cat-v.org/plan_9/4/u9fs

[3] E. Van Hensbergen: Plan 9 Remote Resource Protocol Unix Extension
https://ericvh.github.io/9p-rfc/rfc9p2000.u.html

[4] diod/protocol.md
https://github.com/chaos/diod/blob/master/protocol.md

[5] MOUNT_9P(8)
https://www.unix.com/man-page/mojave/8/mount_9p

[6] 9P2010
https://9p.io/wiki/plan9/9p2010/index.html

[7] 9P2010 Operations
https://9p.io/wiki/plan9/9P2010_Operations/index.html

[8] Kerberos: The Network Authentication Protocol
https://web.mit.edu/kerberos/

[9] Security in Plan 9
http://doc.cat-v.org/plan_9/4th_edition/papers/auth

[10] Russ Cox: Security in Plan 9
https://swtch.com/~rsc/talks/nauth.pdf

[11] Dave Eckhardt: Factotum
https://www.cs.cmu.edu/~412/lectures/L08_Factotum.pdf

認証

以下では maia を unix ホスト、hebe を Plan9 ホスト(認証サーバー兼ファイルサーバー)とする。

認証の様子を調べるために、maia から 9p コマンドで hebe に接続して、9P メッセージを調べる。

maia$ 9 kill factotum|rc	# kill all factotum processes
maia$ factotum -D		# invoke factotum with 9P tracing
maia$ 9p -D -a hebe ls		# invoke 9p with debugging mode
なお maia$maia におけるコマンドプロンプトである。

以下では "#" は僕のコメント

<- Tversion tag 0 msize 8192 version '9P2000'
-> Rversion tag 65535 msize 8192 version '9P2000'
# 最初にクライアントが認証サーバーに自分のバージョンを知らせる。
# それに基づいて認証サーバーが交渉バージョンを決めている
<- Tauth tag 0 afid 0 uname arisawa aname <nil>
-> Rauth tag 0 qid (0000000000000066 0 A)
# クライアントは認証サーバーに対してチケットをリクエストしている。
# クライアントが自分の名前を名乗り、aname(attach name) を希望する
# 一般にサーバーは複数の木を持っている。典型的には main と other である。
# このケースでは指定していないので default の main を希望したことになる。
<- Tversion tag 0 msize 8192 version '9P2000'
-> Rversion tag 65535 msize 8192 version '9P2000'
# 今度はクライアントはファイルサーバーに自分のバージョンを知らせる。
# それに基づいてファイルサーバーが交渉バージョンを決めている
<- Tattach tag 0 fid 0 afid -1 uname arisawa aname
<-4- Tattach tag 0 fid 0 afid -1 uname arisawa aname
# クライアントはファイルサーバーに接続をお願いする
# そのお願いメッセージは factotum に渡される
-4-> Rattach tag 0 qid (0000000000000001 0 d)
-> Rattach tag 0 qid (0000000000000001 0 d)
# 接続の許可が factotum に渡り
# それをクライアントが受け取る
<- Twalk tag 0 fid 0 newfid 1 nwname 1 0:rpc
<-4- Twalk tag 0 fid 0 newfid 1 nwname 1 0:rpc
-4-> Rwalk tag 0 nwqid 1 0:(0000000000000002 0 )
-> Rwalk tag 0 nwqid 1 0:(0000000000000002 0 )
<- Topen tag 0 fid 1 mode 2
<-4- Topen tag 0 fid 1 mode 2
fid mode is 0x2
-4-> Ropen tag 0 qid (0000000000000002 0 ) iounit 0
-> Ropen tag 0 qid (0000000000000002 0 ) iounit 0
<- Tclunk tag 0 fid 0
<-4- Tclunk tag 0 fid 0
-4-> Rclunk tag 0
-> Rclunk tag 0
<- Twrite tag 0 fid 1 offset 0 count 29 'start proto=p9any role=client'
<-4- Twrite tag 0 fid 1 offset 0 count 29 'start proto=p9any role=client'
-4-> Rwrite tag 0 count 29
-> Rwrite tag 0 count 29
<- Tread tag 0 fid 1 offset 29 count 4096
<-4- Tread tag 0 fid 1 offset 29 count 4096
-4-> Rread tag 0 count 2 'ok'
-> Rread tag 0 count 2 'ok'
<- Twrite tag 0 fid 1 offset 31 count 5 'read '
<-4- Twrite tag 0 fid 1 offset 31 count 5 'read '
-4-> Rwrite tag 0 count 5
-> Rwrite tag 0 count 5
<- Tread tag 0 fid 1 offset 36 count 4096
<-4- Tread tag 0 fid 1 offset 36 count 4096
-4-> Rread tag 0 count 40 'phase in state 'read offer' want 'write''
-> Rread tag 0 count 40 'phase in state 'read offer' want 'write''
<- Twrite tag 0 fid 1 offset 76 count 6 'write '
<-4- Twrite tag 0 fid 1 offset 76 count 6 'write '
-4-> Rwrite tag 0 count 6
-> Rwrite tag 0 count 6
<- Tread tag 0 fid 1 offset 82 count 4096
<-4- Tread tag 0 fid 1 offset 82 count 4096
-4-> Rread tag 0 count 13 'toosmall 4096'
-> Rread tag 0 count 13 'toosmall 4096'
<- Tread tag 0 fid 0 offset 0 count 4096
-> Rread tag 0 count 58 'p9sk1@local p9sk1@local p9sk1@local p9sk1@poe dp9ik@local\0'
# ここに示された 'p9sk1@local' などはサーバーの factotum に含まれていた情報である。
# これらは arisawa の authdom と proto である。
<- Twrite tag 0 fid 1 offset 95 count 64 'write p9sk1@local p9sk1@local p9sk1@local p9sk1@poe dp9ik@local\0'
# クライアントは同じ内容をサーバーに送っているように見えるが、
# 実際には factotum に送られたのであろう
<-4- Twrite tag 0 fid 1 offset 95 count 64 'write p9sk1@local p9sk1@local p9sk1@local p9sk1@poe dp9ik@local\0'
# サーバーに同じ内容を送った。確認か?
-4-> Rwrite tag 0 count 64
-> Rwrite tag 0 count 64
<- Tread tag 0 fid 1 offset 159 count 4096
<-4- Tread tag 0 fid 1 offset 159 count 4096
-4-> Rread tag 0 count 2 'ok'
-> Rread tag 0 count 2 'ok'
<- Twrite tag 0 fid 1 offset 161 count 5 'read '
<-4- Twrite tag 0 fid 1 offset 161 count 5 'read '
-4-> Rwrite tag 0 count 5
-> Rwrite tag 0 count 5
<- Tread tag 0 fid 1 offset 166 count 4096
<-4- Tread tag 0 fid 1 offset 166 count 4096
-4-> Rread tag 0 count 58 'needkey role=client proto=p9sk1 dom=local user? !password?'
-> Rread tag 0 count 58 'needkey role=client proto=p9sk1 dom=local user? !password?'
# factotum を通じて、パスワーとが要求された
# サーバーが authdom と proto を選ぶにあたって、(サーバー側の) factotum の内容と、# {/lib/ndb/local} に書かれている {authdom} の値を参照したはずである
!adding key: role=client proto=p9sk1 dom=local
user[arisawa]:
password:
!
<-4- Tattach tag 0 fid 0 afid -1 uname arisawa aname
-4-> Rattach tag 0 qid (0000000000000001 0 d)
<-4- Twalk tag 0 fid 0 newfid 2 nwname 1 0:ctl
-4-> Rwalk tag 0 nwqid 1 0:(0000000000000007 0 )
<-4- Topen tag 0 fid 2 mode 1
fid mode is 0x1
-4-> Ropen tag 0 qid (0000000000000007 0 ) iounit 0
<-4- Tclunk tag 0 fid 0
-4-> Rclunk tag 0
<-4- Twrite tag 0 fid 2 offset 0 count 71 'key role=client proto=p9sk1 dom=local user=arisawa !password=xxx'
# xxx の部分には実際のパスワードの先頭3文字が表示されている
-4-> Rwrite tag 0 count 71
<-4- Tclunk tag 0 fid 2
-4-> Rclunk tag 0
<- Twrite tag 0 fid 1 offset 224 count 5 'read '
<-4- Twrite tag 0 fid 1 offset 224 count 5 'read '
-4-> Rwrite tag 0 count 5
-> Rwrite tag 0 count 5
<- Tread tag 0 fid 1 offset 229 count 4096
<-4- Tread tag 0 fid 1 offset 229 count 4096
-4-> Rread tag 0 count 15 'ok p9sk1 local\0'
-> Rread tag 0 count 15 'ok p9sk1 local\0'
<- Twrite tag 0 fid 0 offset 58 count 12 'p9sk1 local\0'
-> Rwrite tag 0 count 12
<- Twrite tag 0 fid 1 offset 244 count 5 'read '
<-4- Twrite tag 0 fid 1 offset 244 count 5 'read '
-4-> Rwrite tag 0 count 5
-> Rwrite tag 0 count 5
<- Tread tag 0 fid 1 offset 249 count 4096

以上でよく理解できたのか?

なぜチケットを貰うときに、パスワードが要求されなかったの? 不思議である。

よくわからない部分もあるが、認証されるための条件がはっきりしたことは大きな成果である。
認証に失敗する場合、問題点を探すときには(パスワードや認証プロトコルなどの自明な条件を別にすれば)

に注意を払う必要がある。特に
が大切である。

9P のソースコード

Plan9 の kernel は小さいのだが、それでも kernel のソースコードを追いかけるのは辛いかも知れない。幸い 9P を扱った unix のプログラムがあるので、それから始めたら良いのではないだろうか?

一番簡単なのは unix 用の 9P サーバーの u9fs.c がある。非常に簡単な 9P サーバーである。勉強するならこれもなかなか良い。なにしろこのファイル1つで完結している。u9fs に関する僕の記事は http:/u9fs/ にある。参考になるかも知れない。

次に簡単なのは Plan9port の 9p.c であろう。9p.c はクライアントとして 9P サーバーにアクセスする。1つのファイルで完結せずライブラリの中に重要部分が隠れているのと、認証を請け負う factotum の部分が隠れているのが難点ではあるが... srv.c も良いかも知れない。