DNS and OS

DNS クエリが生成されるまで

curl や ping, dig などは、ホスト名に対して実行できる。 nsswitch.conf を見て hosts を見て resolve.conf を見て〜という流れ以上の粒度で、実際のところ何がどういう順序で実行され、 DNS クエリとして飛んでいくのかの挙動を追う。

strace からの調査

ping コマンド実行時の strace


# strace -e trace=open -f ping -c 1 yokohei.com
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libcap.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libidn.so.11", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libattr.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
open("/etc/host.conf", O_RDONLY|O_CLOEXEC) = 4
open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 4
open("/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 4
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 4
open("/lib64/libnss_files.so.2", O_RDONLY|O_CLOEXEC) = 4
open("/etc/hosts", O_RDONLY|O_CLOEXEC)  = 4
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 4
open("/lib64/libnss_dns.so.2", O_RDONLY|O_CLOEXEC) = 4
open("/lib64/libresolv.so.2", O_RDONLY|O_CLOEXEC) = 4
PING yokohei.com (54.192.29.155) 56(84) bytes of data.
open("/etc/hosts", O_RDONLY|O_CLOEXEC)  = 4
64 bytes from 54.192.29.155: icmp_seq=1 ttl=242 time=1.02 ms

--- yokohei.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.025/1.025/1.025/0.000 ms
+++ exited with 0 +++

dig コマンド実行時の strace


# strace -e trace=open -f dig yokohei.com +short
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib64/liblwres.so.80", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib64/libdns.so.81", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib64/libbind9.so.80", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib64/libisccfg.so.82", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib64/libgssapi_krb5.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib64/libkrb5.so.3", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib64/libk5crypto.so.3", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib64/libcom_err.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libcrypto.so.10", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib64/libisccc.so.80", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib64/libisc.so.83", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libcap.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libidn.so.11", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib64/libkrb5support.so.0", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libkeyutils.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libresolv.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libz.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libattr.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib64/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/proc/filesystems", O_RDONLY)     = 3
...
open("/usr/share/locale/en/libdns.cat", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en/LC_MESSAGES/libdns.cat", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/root/.digrc", O_RDONLY)          = -1 ENOENT (No such file or directory)
open("/etc/resolv.conf", O_RDONLY)      = 6
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 6
Process 3070 attached
Process 3069 attached
Process 3068 attached
54.192.29.138
54.192.29.155
54.192.29.6
54.192.29.63
[pid  3067] --- SIGTERM {si_signo=SIGTERM, si_code=SI_TKILL, si_pid=3067, si_uid=0} ---
[pid  3068] +++ exited with 0 +++
[pid  3070] +++ exited with 0 +++
[pid  3069] +++ exited with 0 +++
open("/proc/sys/vm/overcommit_memory", O_RDONLY|O_CLOEXEC) = 3
+++ exited with 0 +++

ping の時は nsswitch.conf が呼ばれているが、 dig のときは呼ばれない。 dig は DNS クエリを生成するためのツールなので、 nsswitch.conf を見る必要はない、ということかな。 なお、どちらも resolv.conf は呼んでいる。

nsswitch.conf

いろいろなカテゴリーの「名前」サービスの情報を、どの情報源からどういう順序で取得するかを決めるためのもの。 DNS に関する「名前」は hosts のデータベースとして扱われる。

/etc/nsswitch.conf ---- ... hosts: files dns ... ----

今回の例では、 ping の場合はこれが呼ぶようにマッピングされてた。 なので、 /etc/nsswitch.conf を書き換えて、 hosts: files だけにすると、 DNS 名前解決が使えなくなる。


# ping yokohei.com
ping: unknown host yokohei.com

# ping -c1 localhost
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=255 time=0.018 ms

--- localhost ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.018/0.018/0.018/0.000 ms

逆に hosts: dns にすると hosts にかかれている localhost が引けなくなる。 以下のようなイメージ図。

ping.c

iputils http://www.skbuff.net/iputils/

iputils/ping.c より一部抜粋

hp = gethostbyname(idn);
if (!hp) {
    fprintf(stderr, "ping: unknown host %s\n", target);
    exit(2);
}

iputils/ping6.c より一部抜粋

gai = getaddrinfo(target, NULL, &hints, &ai);
if (gai) {
    fprintf(stderr, "unknown host\n");
    exit(2);
}

ping6 では IPv6 に対応するため getaddrinfo が利用されている。

gethostbyname()

man gethostbyname より抜粋

The domain name queries carried out by gethostbyname() and gethostbyaddr() rely on the Name Service Switch (nsswitch.conf(5)) configured sources or a local name server (named(8)). The default action is to query the Name Service Switch (nsswitch.conf(5)) configured sources, failing that, a local name server (named(8)).

gethostbyname() 自体が、 nsswitch.conf によって設定を決められる。 ソースコードもちゃんと見たい。

getaddrinfo()

gethostbyname はもう古い、いまは getaddrinfo を見よ、とのこと。(by man page)

int getaddrinfo(const char *node, const char *service,
                const struct addrinfo *hints,
                struct addrinfo **res);
...
struct addrinfo {
    int              ai_flags;
    int              ai_family;
    int              ai_socktype;
    int              ai_protocol;
    socklen_t        ai_addrlen;
    struct sockaddr *ai_addr;
    char            *ai_canonname;
    struct addrinfo *ai_next;
};

参考資料

Last updated