medamaの日記

技術的な事をメインにしてみる。

CentOS6 (RedHat6) 名前解決に時間がかかる。

RedHat6でも同じ症状が発生するのだろう。

CentOS6でtelnet www.google.com 80とかすると一定時間の後に接続を開始する。

# telnet www.google.com 80

(数秒間沈黙の後)

Trying 74.125.235.147...
Connected to www.google.com.
Escape character is '^]'.

 

見た感じでは名前解決に時間がかかっているように見える。

そこでpingを飛ばすと。

# ping www.google.com
PING www.l.google.com (74.125.235.146) 56(84) bytes of data.

 

即座にIPが返ってくる。

コマンドによって動作が異なる奇妙な現象・・・。

参照するDNSサーバを変更してみても症状は変わらず。

お決まりのIPv6問題かと思ってv6を無効にしてもダメ。

 

まずはtelnetコマンドが沈黙している前後で何が起こっているかを調べてみる。

 

# strace telnet www.google.com 80

open("/lib64/libnss_dns.so.2", O_RDONLY) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\20\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=27424, ...}) = 0
mmap(NULL, 2117880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f5e6a77d000
mprotect(0x7f5e6a782000, 2093056, PROT_NONE) = 0
mmap(0x7f5e6a981000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4000) = 0x7f5e6a981000
close(3)                                = 0
open("/lib64/libresolv.so.2", O_RDONLY) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\00009\340o6\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=113952, ...}) = 0
mmap(NULL, 2202248, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f5e6a563000
mprotect(0x7f5e6a579000, 2097152, PROT_NONE) = 0
mmap(0x7f5e6a779000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16000) = 0x7f5e6a779000
mmap(0x7f5e6a77b000, 6792, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f5e6a77b000
close(3)                                = 0
mprotect(0x7f5e6a779000, 4096, PROT_READ) = 0
mprotect(0x7f5e6a981000, 4096, PROT_READ) = 0
munmap(0x7f5e6b974000, 84625)           = 0
socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("8.8.8.8")}, 16) = 0
poll([{fd=3, events=POLLOUT}], 1, 0)    = 1 ([{fd=3, revents=POLLOUT}])
sendto(3, "u\357\1\0\0\1\0\0\0\0\0\0\3www\6google\3com\0\0\1\0\1", 32, MSG_NOSIGNAL, NULL, 0) = 32
poll([{fd=3, events=POLLIN|POLLOUT}], 1, 5000) = 1 ([{fd=3, revents=POLLOUT}])
sendto(3, "\350\374\1\0\0\1\0\0\0\0\0\0\3www\6google\3com\0\0\34\0\1", 32, MSG_NOSIGNAL, NULL, 0) = 32
poll([{fd=3, events=POLLIN}], 1, 4999)  = 1 ([{fd=3, revents=POLLIN}])
ioctl(3, FIONREAD, [204])               = 0
recvfrom(3, "u\357\201\200\0\1\0\6\0\4\0\0\3www\6google\3com\0\0\1\0\1"..., 2048, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("8.8.8.8")}, [16]) = 204
poll([{fd=3, events=POLLIN}], 1, 4997)  = 0 (Timeout)

 

注目した部分は赤字にしてある。

やはり名前解決をしようとしてタイムアウトしている。

タイムアウト時間は5秒。

確かに改めてtelnet www.google.com 80の接続までの時間をチェックするとそれぐらいはかかっていることが分かる。

nscdを起動させてみると。

初回名前解決には時間がかかるものの、2回目以降の名前解決では遅延は解消した。

しかし、名前解決するドメイン名が変わるとまた同じことの繰り返しになることが分かった。

あれこれ試して完全に症状が治ったのは、CentOS6自身にbindをインストールし、そのbindを参照させるようにする事。

運用ポリシーとしてlocalにbindは起動しない、としてきたので今更このポリシーを変更するのも抵抗がある。

なので、そもそもどうして時間がかかるのかに立ち戻って調査したところ。

straceでpingとの動作を改めて比較するとtelnetはなぜかlookupを一度タイムアウトしてそこから改めてもう一度lookupし直している。

pingは一度で名前がすぐに引けている。

パケットキャプチャしてDNSリクエストのパケットに違いがあるのかどうか調べようかと思ったがこれは後回しにして。

名前解決の動作は上記straceで赤字にしている/lib64/libnss_dns.so.2が行っているのだろうと思いこれを元に調べたところ以下のサイトにたどり着いた。

 

glibc 2.10 news

http://udrepper.livejournal.com/20948.html

 

DNS NSS improvement

In glibc 2.9 I already implemented an improvement to the DNS NSS module which optimizes the lookup of IPv4 and IPv6 addresses for the same host. This can improve the response time of the lookup due to parallelism. It also fixes a bug in name lookup where the IPv4 and IPv6 addresses could be returned for different hosts.

The problem with this change was that there are broken DNS servers and broken firewall configurations which prevented the two results from being received successfully. Some broken DNS servers (especially those in cable modems etc) only send one reply. For this reason Fedora had this change disabled in F10.

For F11 I’ve added a work-around for broken servers. The default behavior is the same as described above. I.e., we get the improved performance for working DNS servers. In case the program detects a broken DNS server or firewall because it received only one reply the resolver switches into a mode where the second request is sent only after the first reply has been received. We still get the benefit of the bug fix described above, though.

The drawback is that a timeout is needed to detect the broken servers or firewalls. This delay is experienced once per process start and could be noticeable. But the broken setups of the few people affected must not prevent the far larger group of people with working setups to experience the advantage of the parallel lookup.

There are also ways to avoid the delays, some old, some new:

  • Install a caching name server on this machine or somewhere on the local network. bind is known to work correctly.
  • Run nscd on the local machine. In this case the delay is incurred once per system start (i.e., at the first lookup nscd performs).
  • Add “single-request” to the options in /etc/resolv.conf. This selects the compatibility mode from the start.

All of these work-arounds are easy to implement. Therefore there is no reason to not have the fast mode the default which in any case will work for 99% of the people.

 

CentOS6のglibcglibc-2.12-1.47.el6_2.9

ちなみにCentOS5のglibcglibc-2.5-81

workaroundを読むと確かに一致する点が多い。

そこでdelayを回避する方法の中にある /etc/resolv.conf内に

 

options single-request-reopen

 

を追加したところ見事に解決。

やれやれ・・・。