Plan9 は分散OSである。
Unix/Linux ではネットワーク関係のファイルがシステムの中でバラバラな場所に散乱している。そのことが理解(全体像の把握)を困難にしている。例えば /etc/hosts, /etc/resolv.conf, などなど。他にサーバー用には DHCP や BIND など。
Plan9 では /lib/ndb/local が全てを仕切っていると考えてよい。
ファイルサーバーを除けばディスクレスシステムが想定されており、使用されている CPU の種類も異なることも想定されている。
そうした複雑なシステムを制御しているのである。
それでは /lib/ndb/local は複雑なシンタックスになっているかと言えば、極めて単純な構造を持つ、属性と値の集まりにすぎない。
NDB ファイルの具体的な例がシステムの以下の場所に存在する。
/lib/ndb/local/lib/ndb/common/lib/ndb/auth
/ndb/cs # CS(connection server)/ndb/dns # DNS server/net/cs/net/dns
ip/pingtelnetsshcpu
これらは CS を通じて NDB を使う
ndb/queryndb/csqueryndb/dnsqueryndb/ipqueryndb/dnsdebugndb/inform
以下に NDB のシンタックスを解説するが、実際に許容されるシンタックスはここに述べるよりも遥かに緩やかである。
しかし、マニュアルに解説されている範囲と、使用例の範囲で使うのが無難である。
ここでは推奨されるシンタックスを解説する。
NDB ファイルは(一般に)複数のレコードから構成される。また1つのレコードは(一般に)複数のタプルの集まりである。
タプル(tuple)とは属性(attribute)とその値(value) の組みを言う。
レコードは行の先頭から始まる。
レコードの実例:
sys=hebe dom=hebe.local ether=6805ca0a0bf2 ip=192.168.0.6 # IPV4 ip=2402:6b00:22cd:bf80::6 # IPv6
図1: レコードの例
これで1つのレコードを形成する。
hebe.local" となっている。ローカルドメインの意味で ".local" としていたのであるが現在では止めた方が良い。現在では ".local" は mDNS (multicast DNS) の対象となっているドメインとされている。hebe もそうであるが mDNS をサポートしていない。その場合(最近では)、Mac からのアクセスが5秒ほど遅れる。"dom=hebe.local" の行を削除するか、あるいはインターネットで使われるドメイン形式dom=hebe.nyx.link
dom=xxxx.local
xxxx は sysname) の全てにおいて当てはまる。
1つのレコードの中のタプルの順序は気にしなくてもよい。ただし、同じ属性名のタプルが複数が現れる場合、全てが利用されることもあるし、先頭の1つだけが利用されることもある。検索ツールに依存する。
従って図1の代わりに例えば
ether=6805ca0a0bf2 sys=hebe dom=hebe.local ip=192.168.0.6 ip=2402:6b00:22cd:bf80::6
図2: レコードの例
と書いてもよい。
タプルは
属性=値
=" の左に空白をおいてはならない。
ここでは属性は全て英字で構成される文字列になっているが、実際には非常に広い範囲の文字列が許される。
値を空文字(長さ0の文字列)にしたい場合には
属性=
属性=""
属性
例えば /lib/ndb/common の中には
tcp=rexec port=512 restricted=と書かれている。これは
tcp=rexec port=512 restrictedと同じであるが、意識的に "
=" を付けている。
値が空白を含む場合や記号 "#" で始まる場合には二重引用符で括る必要がある。例
author="Kenji Arisawa"
記号 "#" はコメント記号である。引用符の外で有効である。
コメント記号は行の先頭に書くか、記号の前に空文字を置くのが良い。
注意
foo=#
#" はコメントであるがfoo=bar#
#" は foo の値の一部となる。この一貫性を欠いた紛らわしさは実際の利用においては避けるべきである。foo= #
foo="bar#"
レコードの中では空行は使わないほうが良い。使ってもエラーにはならないが意図したとおりの結果が得られないかもしれない。本来は空行はスキップすべきだと思われるが、そうにはなっていない。
NDB のシンタックスは非常に緩くできている。このファイルはブート時にも参照されるので、シンタックスエラーで停止しないように配慮されているのだろう。
検索は属性名とその値を指定して行う。また合致したレコードの中の指定した属性名の値だけを取り出せる。
マニュアル ndb(6) には
Within tuples, pairs on the same line bind tighter than pairs on different lines.と書かれている。しかし、レコード内のタプルは同じ行に書こうが、異なる行に書こうが関係ないように思える。
NDB は Plan 9 のネットワークデータベースとして設計されているのであるが、シンプルさ故に広い適応範囲を持っている。また可読性も優れている。
ネットワークに関する Plan 9 の設定は全て NDB を通じて行なえるようになっている。
シェルスクリプトとのインタフェースが充実している。
/lib/ndb/local
初めて Plan9 を試す場合には、分散 OS としてではなく、単体の Plan9 端末を構築するだあろう。この場合
dns server をリゾルバーモードで起動する。この場合、dns はキャッシュ能力だけではなく full-service resolver としての能力を持つ。
ndb/dns -r
ここでは最初に HGW(home gate way) が提供する DNS サーバー(正しくは DNS proxy)を使う例から始める。
database= file=/lib/ndb/common file=/lib/ndb/local dom= dns=router ipnet=local ip=192.168.0.0 ipmask=255.255.255.0 ipgw=192.168.0.1 ntp=ntp.jst.mfeed.ad.jp proto=tcp sys=router dom=router.local ip=192.168.0.1 sys=maia dom=maia.local ether=6805ca00fc34 ip=192.168.0.3 ip=2402:6b00:22cd:bf80::3
database では読み取る NDB ファイルを指定する。/lib/ndb/common にはポート番号とそのサービスポートなど、システムの構成に依存しない情報が含まれている。
dom= は dom="" と同じであり、DNS root を意味する。dns で recursive dns server を指定している。サブネットで他のサーバーが指定されていない限り、ここで指定した DNS サーバーが使用される。
ipnet でローカルネットワークの3つの基本情報を ip、ipmask、ipgw で指定する。
ipnet の値はローカルネットワークの識別子である。ここでは local を使ったが他の名前でもよい。
ntp でタイムサーバーを指定する。
proto では ipnet の中で使用する通信プロトコルを指定するが、ここは他に Plan9 でしか使われない il が指定できるのみであり、実際上は tcp に固定されるはずである。
sys でホスト名(Plan9 では sysname と呼ばれている)を指定する。
このレコードでは dom 及び ip が必須になるだろう。
maia は Plan9 端末の名前である。Plan9 では ether の値を手掛かりに sysname が決定されるので、ether は必須になるはずである。IP アドレスは ip で指定する。表現形式によって IPv4 と IPv6 が区別される。
ここでは HGW を dns として使用したが、Google のパブリック DNS サービスを利用する方法もあるだろう。その場合には
dom= dns=google-public-dns-a.google.com dns=google-public-dns-b.google.com dom=google-public-dns-a.google.com ip=8.8.8.8 dom=google-public-dns-b.google.com ip=8.8.4.4
でこの部分を置き換えればよいはずである。(パブリック DNS サービスは汚染されている危険性が高いので、好んで使うサービスではない)
由緒正しい書き方をすれば、この部分は
dom= ns=A.ROOT-SERVERS.NET ns=B.ROOT-SERVERS.NET ns=C.ROOT-SERVERS.NET ns=D.ROOT-SERVERS.NET ns=E.ROOT-SERVERS.NET ns=F.ROOT-SERVERS.NET ns=G.ROOT-SERVERS.NET ns=H.ROOT-SERVERS.NET ns=I.ROOT-SERVERS.NET ns=J.ROOT-SERVERS.NET ns=K.ROOT-SERVERS.NET ns=L.ROOT-SERVERS.NET ns=M.ROOT-SERVERS.NET
である。
ns で authoritative name server を指定する。A.ROOT-SERVERS.NET などの IP アドレスは /lib/ndb/common に含まれている。
sys で sysname を指定する。いわゆるホスト名である。
dom は domain を表す。
この値は(マニュアルには FQDN となっているが) root を起点とした相対ドメイン名である。つまり末尾に "." を付けない。
dom と一緒に用いる。ns で autoritative name server を指定する。
dns で recursive name server を指定する。dom と一緒に指定することもできるが、ipnet の中で指定した場合には dns の値は dhcpd によって dns として宣伝される。
その他多数の属性が定義されている。詳しくは ndb(6) を見よ。
/lib/ndb/local
ここではファイルサーバーを中心としてシステムが組まれていることを想定する。
以下の例で示す Plan9 分散システムのブレィヤーの名称を
hebe: ファイルサーバー、認証サーバー、DNS サーバー、DHCP サーバーmeg: CPU サーバーmaia: Plan9 端末
hebe はネットワーク上の Unix や Windows などにも DHCP や DNS のサービスを提供する。また DHCP は CPU サーバーや端末に対してブートに必要な情報を提供する。
hebe での dns の実行は
ndb/dns -s
-s" は UDP port 53 でもサービスを行うことを意味している。ip/dhcpd 192.168.0.100 150
dhcpd は bootp サーバーとして働く。
ファイルサーバー以外は
ndb/dns -r
-r" はリゾルバーモードを表す。従って以下リゾルバーと言う。
実例として、筆者の /lib/ndb/local の一部分を示す。
database= file=/lib/ndb/common file=/lib/ndb/local dom= ns=A.ROOT-SERVERS.NET ns=B.ROOT-SERVERS.NET ns=C.ROOT-SERVERS.NET ns=D.ROOT-SERVERS.NET ns=E.ROOT-SERVERS.NET ns=F.ROOT-SERVERS.NET ns=G.ROOT-SERVERS.NET ns=H.ROOT-SERVERS.NET ns=I.ROOT-SERVERS.NET ns=J.ROOT-SERVERS.NET ns=K.ROOT-SERVERS.NET ns=L.ROOT-SERVERS.NET ns=M.ROOT-SERVERS.NET dom=local soa= refresh=3600 ttl=3600 ns=hebe mbox=arisawa auth=hebe ipnet=local ip=192.168.0.0 ipmask=255.255.255.0 ipgw=192.168.0.1 ntp=ntp.jst.mfeed.ad.jp auth=hebe fs=hebe proto=tcp dns=hebe dnsdomain=local sys=maia dom=maia.local ether=6805ca00fc34 ip=192.168.0.3 ip=2402:6b00:22cd:bf80::3 #bootf=/386/9pcf bootf=/386/9bootpxe sys=meg dom=meg.local ether=001b21d5a3e9 ip=192.168.0.4 ip=2402:6b00:22cd:bf80::4 bootf=/386/9bootpxe # 9front sys=hebe dom=hebe.local ether=6805ca0a0bf2 ip=192.168.0.6 ip=2402:6b00:22cd:bf80::6
/lib/ndb/local の一部分
domdom=local
dom の値は root から見たドメイン名の相対アドレスで、ここのレコードでは属性 soa、refresh、ttl 及び ns が現れている。soa は start of authority (マニュアルには start of area となっている)soa はフラグ的な使い方であるが、値を持つこともあるらしい。refresh と ttl は各々 refresh time および time to live で単位は秒。ns は autoritative name server で、ここでは hebe が指定されている。auth は cpu コマンドなどでファイルサーバーや CPU サーバーにアクセスする際の認証に使われる。authdom を使ってauthdom=local auth=hebe
auth が指定されている場合には authdom での指定の方が優先度が高い注1。
ipnetipnet=local
ipnet の値としてドメイン名が選ばれているが、他の名前を使ってもよい。この値はネットワークの識別子である。ここのレコードでは属性 ip、ipmask、ipgw が必須である。ここではその他に、ntp サーバー、認証サーバー、プロトコルが指定されているが、これらは sys を含むレコードに個別に書いてもよい。(その場合には override される)dns では recursive name server を指定する。ipnet の中の dns の値は DHCP で宣伝される。dnsdomain は、いわゆる探索ドメインである。これは Plan9 ネットワークの中で使用されている。ipnet における auth や fs は ndb/ipquery コマンドによる属性探索の対象となっており、システムの初期設定に利用されている。
syssys=maia
dom の値として maia のドメイン名が指定されている。これは root から見たドメイン名の相対アドレスである。ether はイーサーネットアドレス、ip は IP アドレスである。IPv4 だけでなく IPv6 も指定できる注1。bootf でブートファイルを指定する。ここでは PXE ローダーが指定されている。
ipv6 属性が使われていた。現在もマニュアルにはこの属性名が残っている。
dom と共に用いる。ns で authoritative name server を指定し、refresh と ttl で各々 refresh time および time to live が指定される。単位は秒。retry、expire、serial が指定できる。
また mbox で管理者のメールアドレスを指定する。その場合の形式は
mbox=person mbox=person@machine.dom mbox=person.machine.dom
postmaster.$ns となる。ここに $ns は ns の値である。
ここの部分は DNS にアクセスしたクライアントに知らされる。
LAN の中での実験を見る限り、Plan9 の ndb は寡黙である。
探索に失敗した場合にのみ ns の値と mbox の値がクライアントに知らされる。
LAN の外でのサービスを行う場合には ns や mbox には full domain name が要求されるだろう。
/sys/src/cmd/ndb/dblookup.c
ipnet の値はネットワーク識別子である。(従って自由に名前を選べるが重複は許されない)
ipnet の中の属性とその値は必要に応じて DHCP あるいは BOOTP でクライアントに渡される。その場合、sys の中に同じ属性名があれば、sys の値が優先される。
従って、ipgw なども sys 側においても構わないが、通常は ipnet に置けるものは ipnet に置くだろう。
ファイルシステムをマウントできれば、クライアントは /lib/ndb/local を直接参照すれば構わないので、マウントするまでに必要な情報がブート時にクライアントに渡されると考えてよい。
/etc/resolv.conf の search の値に相当する。
/lib/ndb/local
ここでも筆者の例を基に解説する。
筆者の場合、自宅から大学の研究室の Plan9 システムを使うニーズが存在する。
この場合、認証サーバーが異なる。
また筆者の場合には認証サーバーは正式なサーバーとして大学に登録されていない。従って /lib/ndb/local の中で、名前と IP アドレスを設定しなくてはならない。
現在、大学で動いている Plan9 システムは 2 台構成である。
arar.aichi-u.ac.jpplan9.aichi-u.ac.jp を兼ねている。herahera.aichi-u.ac.jp
目標は
cpu コマンドや 9fs でアクセスできること。
database= file=/lib/ndb/common file=/lib/ndb/local dom= ns=A.ROOT-SERVERS.NET ns=B.ROOT-SERVERS.NET ns=C.ROOT-SERVERS.NET ns=D.ROOT-SERVERS.NET ns=E.ROOT-SERVERS.NET ns=F.ROOT-SERVERS.NET ns=G.ROOT-SERVERS.NET ns=H.ROOT-SERVERS.NET ns=I.ROOT-SERVERS.NET ns=J.ROOT-SERVERS.NET ns=K.ROOT-SERVERS.NET ns=L.ROOT-SERVERS.NET ns=M.ROOT-SERVERS.NET dom=aichi-u.ac.jp auth=hera ipnet=labo ip=202.250.160.0 ipmask=255.255.255.0 ipgw=202.250.160.254 smtp=ar fs=hera auth=hera prot=tcp ntp=smtp.aichi-u.ac.jp sys=ar ip=202.250.160.40 ether=6805ca0301b7 dom=ar.aichi-u.ac.jp bootf=/386/9bootpxe sys=plan9 dom=plan9.aichi-u.ac.jp ip=202.250.160.122 sys=hera ip=202.250.160.71 ether=74d435609637 dom=hera.aichi-u.ac.jp dom=local soa= refresh=3600 ttl=3600 ns=hebe mbox=arisawa auth=hebe ipnet=local ip=192.168.0.0 ipmask=255.255.255.0 ipgw=192.168.0.1 ntp=ntp.jst.mfeed.ad.jp auth=hebe fs=hebe proto=tcp dns=hebe dnsdomain=local sys=maia dom=maia.local ether=6805ca00fc34 ip=192.168.0.3 ip=2402:6b00:22cd:bf80::3 #bootf=/386/9pcf bootf=/386/9bootpxe sys=meg dom=meg.local ether=001b21d5a3e9 ip=192.168.0.4 ip=2402:6b00:22cd:bf80::4 bootf=/386/9bootpxe # 9front sys=hebe dom=hebe.local ether=6805ca0a0bf2 ip=192.168.0.6 ip=2402:6b00:22cd:bf80::6
/lib/ndb/local の一部分
/lib/ndb/local の書き方(スタイル)について
各レコードの最初のタプルはレコード内の何を使ってもよいのではあるが、可読性のためにはルールを決めておいた方が良いであろう。
筆者は次の4パターンで分類している。
databasedomipnetsys
そして、次の優先順位で考える
database 属性を持つ場合には、database を先頭に書くsys 属性を持つ場合には、sys を先頭に書くipnet 属性を持つ場合には、ipnet を先頭に書くdom 属性を持つ場合には、dom を先頭に書く
Plan9 マニュアイルやシステム付属の /lib/ndb/local のサンプルでは、ip が先頭に書かれていることが多いが、賛成できない。現在では IPv6 との関係で複数の ip を同一のホストに割り当てる場合があり、この考え方は破綻する。
ipnet あるいは sys に置く場合と(sys 属性を持たない) dom に置く場合は役割が異なる。
認証ドメイン。auth を指定する。dom で代用可能。(authsrv(2) を見よ)
ipnet に置いて、Plan9 システムで使う smtp サーバーを指定する。
筆者は「お名前.com」で Web 用に幾つかの名前を登録している。
例えば
ar.nyx.link p9.nyx.link
hebe の仮想ホストのアドレスでもある。
さて問題は、これらの IP アドレスがインターネットから見るのと、LAN から見るのとは異なることにある。
インターネットから見えるグローバル IPv4 アドレスは動的に変化する。そして LAN の中からグローバルアドレスでこれらのサーバーにアクセスすると何か良くない予感がする。そこで LAN の中ではローカルアドレス(192.168.0.6) が付与されているかのように見せたいのである。これは実際に可能であって
dom=nyx.link soa= refresh=3600 ttl=3600 ns=hebe dom=ar.nyx.link ip=192.168.0.6 dom=p9.nyx.link ip=192.168.0.6
で解決する。
サーバーをインターネットに公開するためにはネームサーバーへの登録が必要になる。
筆者の場合には家庭内サーバーなので、「お名前.com」のネームサーバーを利用しているが、場合によってはインターネットからアクセスされるネームサーバーを自分で持つ必要があろう。
この場合の(インターネットからアクセスされる) ndb/dns は
ndb/nds -Rs
/lib/ndb/local を環境に合わせて修正が要求されるだろうが、筆者には実験環境がないので正確なことは言えない。
Plan9 で使用される dns 関係のコマンドシンタックスだけを簡単にまとめる。詳しくは ndb(8) を見よ。
ndb/query
ndb/query [ -am ] [ -f dbfile ] attr value [ rattr ]
ndb/ipquery
ndb/ipquery attr value rattr...
ipquery は器用なコマンドで、例えば
ndb/ipquery sys maia ip ipgw auth
ip=192.168.0.3 ip=2402:6b00:22cd:bf80::3 ipgw=192.168.0.1 auth=192.168.0.6
-f dbfile オプションがある。
ndb/csquery
ndb/csquery [ -s ] [ server [ addr... ] ]
ndb/dnsquery
ndb/dnsquery
ndb/dnsdebug
ndb/dnsdebug [ -rx ] [ -f dbfile ] [ [ @server ] domain-name [ type ] ]
ndb/dnstcp
ndb/dnstcp [ -rR ] [ -f dbfile ] [ -x netmtpt ] [ conn-dir ]
ndb/dnsinform
ndb/inform [ -x netmtpt ]
ndb/cs
ndb/cs [ -4n ] [ -f dbfile ] [ -x netmtpt ]
ndb/dns
ndb/dns [ -norRs ] [ -a maxage ] [ -f dbfile ] [ -N target ] [ -x netmtpt ] [ -z program ]
-r resolver only
ndb/dns -r
-s answer domain requests sent to UDP port 53
ndb/dns -s
-R ignore the 'recursive' bit on incoming requests. Do not complete lookups on behalf of remote systems.
ndb/dns -R
/net/dns が dns とのインターフェースになっている。
通常はアプリケーションプログラムから利用される。すなわち /net/dns を通じてリモートの dns サーバーにリクエストを送り、結果を受け取る。Python からの利用例を Appendix に示す。
また /net/dns はローカルな dns サーバに対して特殊なコマンドを送ることもできる。
これに関してはソースコードを見ないと分からない。
例えば
echo restart >> /net/dns
使えるコマンドをまとめると、
debug/sys/log/dns にデバッグ情報が出力される。debug はトグルdump/lib/ndb/dnsdump にキャシュをダンプする。/lib/ndb はホストオーナーによる書き込みを可にすることpoolcheckrefreshrestartstats/lib/ndb/dnsstats に状態を書き出す。/lib/ndb はホストオーナーによる書き込みを可にすることtargettesting
term% cat dnsstats # system hebe # slave procs high-water mark 16 # queries received by 9p 9709 # queries received by udp 6236 # queries answered from memory 13764 # queries sent by udp 5387 # responses arriving within 0.1 s. 3840 # responses arriving within 0.2 s. 1260 # responses arriving within 0.3 s. 227 # responses arriving within 0.4 s. 5 # responses arriving within 0.5 s. 3 # responses arriving within 3.2 s. 0 # queries sent & timed-out 104 # cname queries timed-out 1 # ipv6 queries timed-out 92 # negative answers received 1999 # negative answers w Rserver set 0 # negative answers w bad delegation 0 # negative answers w bad delegation & no answers 0 # negative answers w no Rname set 625 # negative answers cached 1374 hebe%
dnsquery in Python
以下に Python で書かれた dnsquery 関数を紹介する。
要点は
/net/dns を Read/Write モードでオープンする
リクエストの書き方は
ar.aichi-u.ac.jp ip
ar.aichi-u.ac.jp ip 202.250.160.40
import sys
import os
from string import *
def dnsquery(q,name="/net/dns"):
"""
usage:
print dnsquery("ar.aichi-u.ac.jp ip")
print dnsquery("ar.aichi-u.ac.jp ip","/net/dns")
print dnsquery("202.250.160.40 ptr")
"""
f = os.open(name,os.O_RDWR)
t = split(q)
if t[1] == "ptr":
u = t[0].split(".")
q = join((u[3],u[2],u[1],u[0]),".")+".in-addr.arpa ptr"
r = []
try:
os.write(f,q)
os.lseek(f,0,0)
v = os.read(f,256) # "" if not answered
while v:
r = r + [v.split()[2]]
v = os.read(f,256)
except:
pass
os.close(f)
if r == []:
return None
return r
いつまで待ってもデータが読み取れないかも知れない。従って運用にあたっては dnsquery に対して alarm タイマーをセットする必要があるだろう。