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/ping
telnet
ssh
cpu
これらは CS を通じて NDB を使う
ndb/query
ndb/csquery
ndb/dnsquery
ndb/ipquery
ndb/dnsdebug
ndb/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
の一部分
dom
dom=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。
ipnet
ipnet=local
ipnet
の値としてドメイン名が選ばれているが、他の名前を使ってもよい。この値はネットワークの識別子である。ここのレコードでは属性 ip
、ipmask
、ipgw
が必須である。ここではその他に、ntp サーバー、認証サーバー、プロトコルが指定されているが、これらは sys を含むレコードに個別に書いてもよい。(その場合には override される)dns
では recursive name server を指定する。ipnet
の中の dns
の値は DHCP で宣伝される。dnsdomain
は、いわゆる探索ドメインである。これは Plan9 ネットワークの中で使用されている。ipnet
における auth
や fs
は ndb/ipquery
コマンドによる属性探索の対象となっており、システムの初期設定に利用されている。
sys
sys=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 台構成である。
ar
ar.aichi-u.ac.jp
plan9.aichi-u.ac.jp
を兼ねている。hera
hera.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パターンで分類している。
database
dom
ipnet
sys
そして、次の優先順位で考える
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
はホストオーナーによる書き込みを可にすることpoolcheck
refresh
restart
stats
/lib/ndb/dnsstats
に状態を書き出す。/lib/ndb
はホストオーナーによる書き込みを可にすることtarget
testing
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 タイマーをセットする必要があるだろう。