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;
};参考資料
Anatomy of a Linux DNS Lookup https://zwischenzugs.com/2018/06/08/anatomy-of-a-linux-dns-lookup-part-i/
Domain name resolution https://wiki.archlinux.org/index.php/Domain_name_resolution
Tracing Linux Hostname Resolution https://www.kickflop.net/blog/2011/01/02/tracing-linux-hostname-resolution/
Linux Programmer's Manual RESOLV.CONF(5) http://man7.org/linux/man-pages/man5/resolv.conf.5.html
getaddrinfo - ライブラリコールの説明 https://kazmax.zpp.jp/cmd/g/getaddrinfo.3.html
6.4 Protocol-Independent Nodename and Service Name Translation - RFC 2553 https://tools.ietf.org/html/rfc2553#section-6.4
Last updated