彷徨えるフジワラ

年がら年中さまよってます

/etc/networks 設定でハマる

ソフトウェア界隈で 10 年以上も飯を食っていれば、"-n" 指定無しの "netstat -r" 応答が遅い原因として、ホスト名/ネットワーク名解決のコストぐらいは思い付く様になる。では、/etc/nsswitch.conf や /etc/networks を設定した上で応答が遅い場合は?

当然、設定が間違っている⇒試行錯誤というお定まりのコースに orz

SUN がウェブ上で公開している管理者向けマニュアルでの /etc/networks のサンプルでは:

eng   192.168.9 #engineering
acc   192.168.5 #accounting
prog  192.168.2 #programming

という感じの記述。予想通りというか、20世紀からこうだったので、特に違和感は無い。

…………でも動かない。
あちこち彷徨っていると networks(4) の末尾に:

The network number in networks database is the host address shifted to the right by the number of 0 bits in the address mask. For example, for the address 24.132.47.86 that has a mask of fffffe00, its network number is 803351.

という記述が*1。え?こんな面倒なことをしなければいけなかったっけ?> SVR4

ま、とりあえず、やってみましょうか。対象のネットワークは 192.168.56.0/24 だから:

# 192.168.56.0/24
# ⇒ 0xc0.0xa8.0x38.0x00/24
# ⇒ 0xc0a838
hoge-net 12625976

こんな感じかな?

…………やっぱり動かない。

さぁ、このあたりから俄然意地になってきますよ(笑)。

OpenSolaris ソースの usr/src/cmd 配下で、ファイル名/ディレクトリ名に netstat を含むものを探すと、そのものズバリな netstat.c を発見。

ネットワーク名解決には getnetbyaddr(3SOCKET) を使うであろう事から、実際に処理している箇所は当該ソース中の pr_net() 関数であることを突き止める。

static char *
pr_net(uint_t addr, uint_t mask, char *dst, uint_t dstlen)
{
........
        net = addr & mask;
        while ((mask & 1) == 0)
                mask >>= 1, net >>= 1;
        np = getnetbyaddr(net, AF_INET);
        if (np && np->n_net == net)
                cp = np->n_name;
        else {
                /*
                 * Look for subnets in hosts map.
                 */
                hp = getipnodebyaddr((char *)&addr, sizeof (uint_t),
                                     AF_INET, &error_num);
                if (hp)
                        cp = hp->h_name;
        }
........

getnetbyaddr(3SOCKET) での解決に失敗した場合、getipnodebyaddr(3SOCKET) による名前解決が実施されている(else ブロック部分)ので、/etc/nsswitch.conf で networks を files 限定にしていても、ipnodes に files 以外が列挙されていれば外部に問い合わせに行ってしまう模様。この時点では getnetbyaddr(3SOCKET) が失敗する理由は不明だけど、とりあえず「"netstat -r" の応答が悪い」挙動の原因は判明。

getnetbyaddr(3SOCKET) が上手く機能していないのかと思い、以下の様な呼び出しを含む急造のプログラムで名前解決を試みてみる。

ネットワークアドレス 0xc0a83800(192.168.56.0 の 16 進化) を右シフトして0xc0a838 にしているのは、pr_net() での getnetbyaddr(3SOCKET) 呼び出しに先立ってビットシフトしている部分に倣ったもの。

getnetbyaddr(0xc0a838, AF_INET);

…………上手く行った!ってことは、pr_net() で getnetbyaddr(3SOCKET) に渡す引数が不適切なのか?

DTrace を使って pr_net() の呼び出し引数を確認してみる。以下の実行例で、dtrace のコマンド行が複数行に折り返されているのは、あくまで視認性向上のためなので、実際に入力する場合は注意のこと。

$ dtrace -n 'pid$target:netstat:pr_net:entry{
    printf("%s(0x%08x, 0x%08x)", probefunc, arg0, arg1);
}'  -c 'netstat -r -f inet'
dtrace: description 'pid$target:netstat:pr_net:entry ' matched 1 probe
........
CPU     ID     FUNCTION:NAME
  0  60283      pr_net:entry pr_net(0x00000000, 0x00000000)
  0  60283      pr_net:entry pr_net(0x0038a8c0, 0x00ffffff)
........

あれ?"0x0038a8c0" って、"0.56.168.192" のこと?あ!バイトオーダーが混在してるんじゃないの?

pr_net() の引数=getnetbyaddr(3SOCKET) の引数が現状のようにネットワークバイトオーダー=MSB first、所謂ビックエンディアン(big endian)で指定されるのが適切だとしたら、先述した pr_net() 中の以下の処理で:

        net = addr & mask;
        while ((mask & 1) == 0)
                mask >>= 1, net >>= 1;

ネットワークマスクを元にホスト部分に相当するビットだけ右シフトする、という処理は、ネットワークバイトオーダーとホストバイトオーダーが異なる x86 のようなアーキテクチャでは、明らかに不適切な処理と言える。

で、先の急造プログラムのようにホストバイトオーダーを引数にした getnetbyaddr(3SOCKET) 呼び出しが成功する一方で、ネットワークバイトオーダー(x86 の場合はホストバイトオーダーと異なるバイトオーダー)での呼び出しが失敗する、ということは、ひょっとして /etc/networks の解析処理(or 突合処理)でのバイトオーダーの扱いが逆転してしまっているのでは?

以上の推測を元に、/etc/networks を以下のように LSB first で記述してみた。

foo-net 56.168.192

…………上手く動いた!

うーん、本当に上記の記述が公式のものなら:

  • 「/etc/networks 記述はホストバイトオーダー依存」の旨をマニュアル等で明記すべきでは?
  • 設定ファイルがホストバイトオーダー依存というのは如何なものか?

という感じだなぁ。第一、先述したようにネットワーク値のシフト処理は明らかにバイトオーダーを無視しているから、どちらかというと SPARCx86 ポーティングにおけるポカな気が。

っつーか、なんで今まで直っていなかったんだろ?/etc/networks なんて普通に利用するだろ?と思ったけど、イマドキ本当にネットワークアドレスを管理したい状況なら DNS なり NIS なり LDAP なりを使うから、file 記述の問題はイマイチ顕在化しないのかな?

ちなみに getnetbyaddr(3SOCKET) のマニュアルのバイトオーダーに関する記述って、細かく間違えまくっている気が……。

The inet_network() function returns a  value in  host  byte  order  
that  is aligned based upon the input string. For example:

            Text                          Value
 "10"                          0x0000000a
 "10.0"                        0x00000a00
 "10.0.1"                      0a000a0001 0x000a0001
 "10.0.1.28"                   0x0a000180 ⇒ 0x0a00011c

ついでに、「これは MSB first アーキテクチャの場合です」と言及すべきだと思うなぁ。

*1:問題が解決してから思うに、ここで言う「The network number in networks database」は、内部格納形式/問い合わせの際の指定に関して述べているだけで、/etc/networks の記述形式について述べたものでは無い気が…