首先,这个文档不错:https://lfckop.github.io/gethostbyname-and-DNS/
下午验证商户的时候发现一个奇怪的问题,商户本地没绑host,但是仍然只能访问到一个ip,后来排查是因为对方机器本地有nscd进程在跑(这里不得不吐槽,不是自己的机器找人执行命令真是难),中间有一个比较有意思的事情,ping的时候只能ping到一个ip,但是dig能跑出两个,这个其实比较容易知道是什么原因导致的,但是这里就有一个比较有意思的点可以去探究的:
我们都清楚或者说知道DNS解析的大概流程,也大概知道可能会有host绑定、缓存等等的情况出现,但是细想一下好像都说不清具体在代码层面Linux系统是怎么去做这件事情的,所以不妨手动做一下测试,看看究竟是怎么做的。
我们从ping开始,首先ltrace跟踪一下ping的代码执行过程:ltrace ping -c 1 baidu.com 2>&1
|
|
至于为啥我要执行grep get,只是知道以前写echoserver这种tcp应用的时候会用到gethostbyname而已,哈哈。
不过这里是没有这个函数的,但是我注意到了有这么一个函数:getaddrinfo,man一下看到了
|
|
其实就是gethostbyname和getservbyname的结果封装了一下,只是gethostbyname被认为是一个已经过期的函数,正好我这个系统是centos7的,ping的版本也比较高,默认用了getaddrinfo,这里我们忽略其中的差异,直接用gethostbyname来做实验,先写一串代码:
|
|
编译一下,用./gethostbyname “intl.alipay.com” 就能直接运行,于是下面开始做实验。
实验过程
- 常规使用
|
|
这里可以看到gethostbyname(也就是ping)的具体执行流程,具体来说分为:
- 读/etc/resolv.conf文件,将dnsserver读入到内存中;
- 连接本地/var/run/nscd/socket(即有nscd在跑的时候就会有nscd的socket,AF_LOCAL说明连接是本地socket),连接通的话直接从nscd缓存请求,如果两次不通则进入3;
- 读/etc/nsswitch.conf文件,寻找其中的hosts: files dns myhostname配置,其中files表示只从hosts文件取数据,dns表示执行DNS请求拿数据,这里表示优先从hosts拿,拿不到就执行DNS请求
- 读/etc/host.conf文件,其中
|
|
- 最后如果hosts中无信息或未配置可以从hosts中取数据,那就发起DNS请求获取结果。
其中需要关注: - 条件必须要顺序满足才能生效:即如果要在步骤4中执行host文件查找,步骤3一定要支持host。但是步骤3执行host,不需要步骤4支持host
- /etc/host.conf中配置order bind,hosts && /etc/hosts中绑定198.11.148.201 intl.alipay.com
|
|
- /etc/nsswitch.conf中配置hosts: files && /etc/hosts不绑定host时
|
|
- nscd开启,相关的配置如下
配置:
|
|
其中hosts cache代表是host文件的缓存配置,具体每个参数的含义可以参考这里:https://linux.die.net/man/5/nscd.conf 这里代表的意思是host中配置的,每15s(10+5,不知道为啥要两个值分开这样设计?) reload一次,reload 3次仍然无新连接的话就从缓存中删除。但是查文档没有看到没在host文件中的域名的缓存策略。
下面是相关的实验:
- /etc/hosts中绑定205.204.107.201 intl.alipay.com 12345678910111213141516171819202122232425262728293031323334353637[root@izj6c3cqwumhn7qc8it7wiz c]# strace ./gethostbyname "intl.alipay.com" 2>&1 | grep -vE "mmap|munmap|mprotect|brk"execve("./gethostbyname", ["./gethostbyname", "intl.alipay.com"], [/* 26 vars */]) = 0access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=50151, ...}) = 0close(3) = 0open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\35\2\0\0\0\0\0"..., 832) = 832fstat(3, {st_mode=S_IFREG|0755, st_size=2127336, ...}) = 0close(3) = 0arch_prctl(ARCH_SET_FS, 0x7f3a4277d740) = 0getpid() = 16555open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=108, ...}) = 0read(3, "nameserver 100.100.2.138\nnameser"..., 4096) = 108read(3, "", 4096) = 0close(3) = 0uname({sysname="Linux", nodename="izj6c3cqwumhn7qc8it7wiz", ...}) = 0socket(AF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3connect(3, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = 0sendto(3, "\2\0\0\0\r\0\0\0\6\0\0\0hosts\0", 18, MSG_NOSIGNAL, NULL, 0) = 18poll([{fd=3, events=POLLIN|POLLERR|POLLHUP}], 1, 5000) = 1 ([{fd=3, revents=POLLIN|POLLHUP}])recvmsg(3, {msg_name(0)=NULL, msg_iov(2)=[{"hosts\0", 6}, {"\310O\3\0\0\0\0\0", 8}], msg_controllen=20, [{cmsg_len=20, cmsg_level=SOL_SOCKET, cmsg_type=SCM_RIGHTS, [4]}], msg_flags=MSG_CMSG_CLOEXEC}, MSG_CMSG_CLOEXEC) = 14close(4) = 0close(3) = 0socket(AF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3connect(3, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = 0sendto(3, "\2\0\0\0\4\0\0\0\20\0\0\0intl.alipay.com\0", 28, MSG_NOSIGNAL, NULL, 0) = 28poll([{fd=3, events=POLLIN|POLLERR|POLLHUP}], 1, 5000) = 1 ([{fd=3, revents=POLLIN|POLLHUP}])read(3, "\2\0\0\0\1\0\0\0\20\0\0\0\0\0\0\0\2\0\0\0\4\0\0\0\1\0\0\0\0\0\0\0", 32) = 32readv(3, [{"intl.alipay.com\0", 16}, {"\315\314k\311", 4}], 2) = 20close(3) = 0fstat(1, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0write(1, "Hostname: intl.alipay.com, was r"..., 60Hostname: intl.alipay.com, was resolved to: 205.204.107.201) = 60exit_group(0) = ?+++ exited with 0 +++
确实是走的nscd的缓存去请求dns的(仍然是连接nscd的socket,只是这是一个本地请求,从这一句中可以看出来:connect(3, {sa_family=AF_LOCAL, sun_path=”/var/run/nscd/socket”}, 110) )
请求后状态:
|
|
第一段是因为手动清理过缓存的原因,实际上应用启动时会首次加载,这里由于清理过缓存,需要请求触发加载。
第一段到第二段中间耗时是21s,这个值是怎么来的,官方文档中没有说明,暂时也先不看了,有空看下代码实现。二三四五中间是耗时15s,是符合预期的,第五次的时候intl域名的缓存就被清理掉了,要想加载就只能请求触发。
而不在hosts中的cache,就比较诡异了,看一下:
|
|
实在没找出啥规律,后面再找找文档研究下吧。唯一确定的是在访问后确实缓存住了,重新访问的时候是直接从缓存取的,商户的请求每分钟都有十几笔,所以缓存也不会过期。