はじめに
名前解決サービスって?
皆さんが今お使いのネットワーク(LAN)には、「example」という名前のPCは接続されているでしょうか? 臨時使用の仮想マシンを作る際に foo とか hoge の様な名前のマシンを構築することはあるかもしれませんが、普通はそんなコンピューター名にはしないと思います。また、/etc/hosts
に「example」という名前に関する定義はあるでしょうか? たとえば、127.0.0.1 example
と書かれていれば、ping example
を実行すると、自マシンからのping結果が返ってくるはずです。
ここから先は、「example」という名のホストは存在せず、/etc/hosts
にもそのような定義はなく、ping example
を実行すると、ping: unknown host example
という結果が表示される前提で話を進めます。
ホスト名からIPアドレスを検索することを名前解決といいます。代表的なものとしては、「hostsファイル」や「dnsリゾルバ」などがあります。何に対して問い合わせるかは自動的に決定されます。それはnsswitch(Name Service Switch)という機能によって提供されます。まず、この設定ファイルを覗いてみましょう。/etc/nsswitch.conf
を表示してみます。
soramimi@alice:~$ cat /etc/nsswitch.conf
# /etc/nsswitch.conf
#
# Example configuration of GNU Name Service Switch functionality.
# If you have the `glibc-doc-reference' and `info' packages installed, try:
# `info libc "Name Service Switch"' for information about this file.
passwd: compat
group: compat
shadow: compat
gshadow: files
hosts: files mdns4_minimal [NOTFOUND=return] dns
networks: files
protocols: db files
services: db files
ethers: db files
rpc: db files
netgroup: nis
中程に、hosts:
という定義があり、その先頭がfiles
となっています。続いてmdns4
またはmdns4_minimal
、そしてdns
と書かれています。Sambaがインストールされた環境であれば、wins
というのもあるかもしれません。
先頭のfiles
は、最初に/etc/hosts
の定義から検索することを指定します。mdns
系はOSの構成によっては無いかもしれませんが、最近のOSでは標準で利用できるケースが多いようです。これは、mDNS(Multicast DNS)といって、macOSで標準となっている名前解決機能です。Windowsでも、Appleから提供されている「Bonjour for Windows」をインストールすると利用できますし、そもそも Windows 10 では標準でmDNSに対応しているそうなので、正しく設定されていれば、ping [ホスト名].local
のように実行すると、名前解決に成功し、pingの結果が表示されると思います。最後のdns
は、インターネットの名前解決のために、DNSサーバに問い合わせを行う指定です。
ホスト名とIPアドレスが予め決まっているのであれば、/etc/hosts
に定義するのが最も簡単で普通のやり方です。hosts以外のファイル形式やデータベースなどを利用して、プログラムによって動的に名前解決を行いたい場合は、名前解決ライブラリを自作してnsswitch.conf
に登録すれば、好きなようにできます。
作ります
本記事ではC言語を利用して名前解決ライブラリを開発します。その実体は共有ライブラリ(shared object)です。
名前解決機能は、OSの動作の根幹と言っても過言ではありませんから、OSに組み込むにはroot権限が必要ですし、プログラムに欠陥があれば、場合によっては重大なセキュリティーホールとなりえますので、そこは注意してください。
ソースコード
Makefileを含んだプログラム全体は下記リポジトリに置きました。
たった1個のC言語のソースファイルにたった2個の関数があるだけです。2つ目の関数は1つ目の関数を呼んでいるだけなので、実質、1つの関数しかありません。
#include <string.h>
#include <nss.h>
#include <netdb.h>
#include <stdlib.h>
#include <errno.h>
#include <arpa/inet.h>
#define ALIGN(idx) do { \
if (idx % sizeof(void*)) \
idx += (sizeof(void*) - idx % sizeof(void*)); /* Align on 32 bit boundary */ \
} while(0)
enum nss_status _nss_example_gethostbyname_r(const char *name, struct hostent *result, char *buffer, size_t buflen, int *errnop, int *h_errnop)
{
size_t idx, astart;
if (strcmp(name, "example") == 0) {
char const *host = "127.0.0.1";
*(char**)buffer = NULL;
result->h_aliases = (char**) buffer;
idx = sizeof(char*);
strcpy(buffer + idx, name);
result->h_name = buffer + idx;
idx += strlen(name) + 1;
ALIGN(idx);
result->h_addrtype = AF_INET;
result->h_length = sizeof(uint32_t);
struct in_addr addr;
inet_pton(AF_INET, host, &addr);
astart = idx;
memcpy(buffer+astart, &addr.s_addr, sizeof(uint32_t));
idx += sizeof(uint32_t);
result->h_addr_list = (char**)(buffer + idx);
result->h_addr_list[0] = buffer + astart;
result->h_addr_list[1] = NULL;
return NSS_STATUS_SUCCESS;
}
*errnop = EINVAL;
*h_errnop = NO_RECOVERY;
return NSS_STATUS_UNAVAIL;
}
enum nss_status _nss_example_gethostbyname2_r(const char *name, int af, struct hostent *result, char *buffer, size_t buflen, int *errnop, int *h_errnop)
{
if (af != AF_INET) {
*errnop = EAGAIN;
*h_errnop = NO_RECOVERY;
return NSS_STATUS_TRYAGAIN;
} else {
return _nss_example_gethostbyname_r(name, result, buffer, buflen, errnop, h_errnop);
}
}
動作の仕組み
やっていることは、問い合わせの名前が「example」だったとき、その結果として「127.0.0.1」を返す、ただそれだけです。nsswitch特有の構造体に結果を格納する処理は一見面倒そうですが、この部分は先人の成果を丸パクリしただけとなっています。私が参考にした元プログラムはこれです。
コンパイル
コンパイルします。
soramimi@alice:~/develop/libnss-example$ make
gcc -g -O2 -Wall -Wpointer-arith -fPIC -c -o main.o main.c
gcc -g -O2 -Wall -Wpointer-arith -shared -Wl,-soname,libnss_example.so.2 -Wl,-z,defs -o libnss_example.so.2 main.o
main.c
をコンパイルして、libnss_example.so.2
を作成しています。共有ライブラリ(shared object)なので、.so
という拡張子がついています。最後の.2
というのは、APIバージョンの様なものと考えてください。これ以外にすると動かないので、必ず.2
を最後に付けます。
インストール
次に、インストールします。
soramimi@alice:~/develop/libnss-example$ sudo make install
install -m755 -d /usr/lib/
install -m644 libnss_example.so.2 /usr/lib/libnss_example.so.2
単純に/usr/lib/
の中にコピーしているだけです。
設定ファイルの変更
/etc/nsswitch.conf
を編集します。hosts:
の定義の最後にexample
を追加したら完了です。
...
hosts: files mdns4_minimal [NOTFOUND=return] dns example
...
実行します
pingを実行して、結果が返ってくることを確認します。
soramimi@alice:~$ ping example
PING example (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.034 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.033 ms
64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.029 ms
自分用に改造するなら
自前の名前解決ライブラリを作る場合、たとえば、libnss_example.so.2
ではなくlibnss_hogehoge.so.2
にするのであれば、main.c
とMakefile
に含まれるexample
をすべてhogehoge
に置換します。2つの関数名もそれぞれ_nss_hogehoge_gethostbyname_r
と_nss_hogehoge_gethostbyname2_r
になります。そして、関数で受け取ったname
に返答すべきIPアドレスがあれば、struct in_addr addr
にアドレスを設定して、構造体に書き込み、NSS_STATUS_SUCCESS
を返します。
コンパイルしてインストールしたら、/etc/nsswitch.conf
のhosts:
の定義に、新しく作ったライブラリの名前を追加します。