Help us understand the problem. What is going on with this article?

名前解決サービスを自作する

はじめに

名前解決サービスって?

皆さんが今お使いのネットワーク(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を含んだプログラム全体は下記リポジトリに置きました。

https://github.com/soramimi/libnss-example

たった1個のC言語のソースファイルにたった2個の関数があるだけです。2つ目の関数は1つ目の関数を呼んでいるだけなので、実質、1つの関数しかありません。

main.c
#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.cMakefileに含まれるexampleをすべてhogehogeに置換します。2つの関数名もそれぞれ_nss_hogehoge_gethostbyname_r_nss_hogehoge_gethostbyname2_rになります。そして、関数で受け取ったnameに返答すべきIPアドレスがあれば、struct in_addr addrにアドレスを設定して、構造体に書き込み、NSS_STATUS_SUCCESSを返します。

コンパイルしてインストールしたら、/etc/nsswitch.confhosts:の定義に、新しく作ったライブラリの名前を追加します。

soramimi_jp
C++とQtが好き。電子工作もやる。第一種情報処理技術者と第二種電気工事士を持ってる。ワンチップマイコンのファームウェアからPCのデスクトップアプリまで。PCより大規模なシステムは守備範囲外。うちの子かわいい(ドール)。40代独身おっさん(´・ω・`)
http://www.soramimi.jp/
AI-medical-service
近未来の内視鏡医療を実現する医療ベンチャー
https://www.ai-ms.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした