Skip to content

Commit f485f94

Browse files
committed
ops/network-service/dns: glibc NSS
1 parent b2aa1ca commit f485f94

File tree

3 files changed

+106
-2
lines changed

3 files changed

+106
-2
lines changed

docs/ops/debug.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,7 @@ curl: (6) Could not resolve host: www.example.com
699699

700700
许多程序会直接使用系统 C 运行时库的 [`getaddrinfo()`][getaddrinfo.3] 等函数获取 DNS 解析的结果。但是 `dig` 不会。这可能导致 `dig` 运行无误,但是使用 C 运行时库的程序因为其他原因无法正常解析的情况。
701701

702-
可以使用 `getent hosts www.example.com` 命令来测试 C 运行时库的 DNS 解析情况。
702+
可以使用 `getent hosts www.example.com` 命令来测试 C 运行时库的 DNS 解析情况。在 glibc 下,该命令会根据 `/etc/nsswitch.conf` 调用对应的 NSS 模块。详情可参考 [DNS 部分对 NSS 的介绍](./network-service/dns.md#nss)。
703703

704704
#### ip
705705

docs/ops/network-service/dns.md

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ glibc 会使用一套复杂的逻辑来决定如何解析用户提供的域名
9595

9696
##### nscd
9797

98-
[nscd][nscd.8](Name Service Cache Daemon)是 glibc 提供的用于缓存 DNS 等结果的服务。如果你在 Debian 下尝试对使用 glibc DNS 查询的程序 `strace` 的话,你会发现 glibc 会尝试连接 `/var/run/nscd/socket`
98+
[nscd][nscd.8](Name Service Cache Daemon)是 glibc 提供的用于缓存 DNS、用户信息等结果的服务。如果你在 Debian 下尝试对使用 glibc DNS 查询的程序 `strace` 的话,你会发现 glibc 会尝试连接 `/var/run/nscd/socket`
9999

100100
```text
101101
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
@@ -115,8 +115,110 @@ close(3) = 0
115115

116116
因此这里也不推荐使用 nscd。
117117

118+
如果需要清理 nscd 的缓存,可以使用 `nscd -i` 命令。
119+
118120
##### NSS
119121

122+
NSS 模块是 glibc 提供的一套插件机制,用于从不同的数据源获取名称解析结果。相关模块的配置在 `/etc/nsswitch.conf` 文件中。glibc 会根据这个配置加载 NSS 模块(`/lib/libnss_xxx.so``xxx` 为模块名,如 `files`),然后调用模块中的接口来获取名称解析结果。
123+
124+
以下是 Debian 13 容器的默认配置:
125+
126+
```text title="/etc/nsswitch.conf"
127+
passwd: files
128+
group: files
129+
shadow: files
130+
gshadow: files
131+
132+
hosts: files dns
133+
networks: files
134+
135+
protocols: db files
136+
services: db files
137+
ethers: db files
138+
rpc: db files
139+
140+
netgroup: nis
141+
```
142+
143+
这里与 DNS 相关的配置是 `hosts` 一行,以上配置表示:
144+
145+
1. `files` 模块会解析 `/etc/hosts` 文件的内容,查看是否能够解析。
146+
2. 如果 `files` 模块没有解析出结果,那么就使用 `dns` 模块进行 DNS 查询。
147+
148+
另一种非常常见的配置是安装了 `systemd-resolved` 的场景。那么 `hosts` 可能会变成这样:
149+
150+
```text
151+
hosts: files myhostname resolve [!UNAVAIL=return] dns
152+
```
153+
154+
其中 [myhostname][nss-myhostname.8] 负责解析本机的主机名,[resolve][nss-resolve.8] 模块则会通过 `systemd-resolved` 的 Unix socket(`/run/systemd/resolve/io.systemd.Resolve`)来进行解析。
155+
156+
`[!UNAVAIL=return]` 表示,除非(`!``resolve` 模块不可用(例如 `systemd-resolved` 没有运行),否则就直接返回,不再继续使用后面的 `dns` 模块。这样设置下,如果 `systemd-resolved` 出现故障,那么系统仍然可以回退到直接使用 DNS 服务器进行解析。而如果只是域名不存在,那么就不会继续使用 `dns` 模块,避免了不必要的 DNS 查询。
157+
158+
可以使用 `getent` 测试 NSS 的解析结果,例如 `getent hosts example.com``getent passwd` 等。同时可以使用 `-s` 参数来指定使用的 NSS 模块,用于调试,例如:
159+
160+
```shell
161+
getent -s files hosts example.com
162+
```
163+
164+
就(一般来说)会返回空,因为其只会用 `files` 模块来解析 `example.com`,如果 `/etc/hosts` 中没有相关的记录,那么就不会有结果。
165+
166+
!!! note "NSS 的返回状态"
167+
168+
NSS 模块可能会返回以下几种状态:
169+
170+
- `SUCCESS`:解析成功。
171+
- `NOTFOUND`:没有找到对应的记录。
172+
- `UNAVAIL`:模块(永久)不可用。
173+
- `TRYAGAIN`:模块(暂时)不可用,可以重试。
174+
175+
默认配置相当于 `[SUCCESS=return !SUCCESS=continue]`。除了 `return` 和 `continue` 之外,还有 `merge`:
176+
177+
```text
178+
group: files [SUCCESS=merge] sss
179+
```
180+
181+
这样的话,如果某用户在本地(`files`)属于组 A,在 sssd(`sss`)中属于组 B,那么最终该用户就会同时属于组 A 和组 B。
182+
183+
!!! note "为什么解析本机还需要 `myhostname` 模块?"
184+
185+
一个约定俗成的做法是,将主机名放在 `/etc/hostname` 文件,而在 `/etc/hosts` 中添加相关的映射:
186+
187+
```text
188+
127.0.0.1 myhost
189+
```
190+
191+
不过,如果 `/etc/hosts` 里面忘写了/忘改了对应的条目,那么就可能会出现非预期的行为。例如,如果忘记添加 `localhost`,那么有些程序就可能会因为无法解析 `localhost` 而出现问题。
192+
193+
systemd-hostnamed 服务则负责管理系统的主机名——静态的主机名(static hostname)仍然在 `/etc/hostname` 中,用户可读的主机名(pretty hostname,比如说 "Xiao Ming's Computer" 或者 "我的电脑" 这种有空格、特殊字符,甚至汉字的名字)等存储在 `/etc/machine-info` 中,同时其也会记录从网络(例如 DHCP)获取的主机名(transient hostname)。而 `myhostname` 模块就是 systemd-hostnamed 提供的 NSS 模块,确保系统主机名总是可以被正确解析,请看下面的例子:
194+
195+
```console
196+
$ getent -s myhostname hosts localhost
197+
::1 localhost
198+
$ # hostnamed 能获取网络接口的地址
199+
$ getent -s myhostname hosts myhost
200+
fd36:cccc:bbbb:aaaa:aaaa:aaaa:aaaa:aaaa myhost
201+
2001:da8:d800:aaaa:aaaa:aaaa:aaaa:aaaa myhost
202+
fe80::aaaa:aaaa:aaaa:aaaa:aaaa myhost
203+
fe80::bbbb:bbbb:bbbb:bbbb:bbbb myhost
204+
$ getent -s myhostname hosts 127.0.0.1
205+
127.0.0.1 localhost
206+
$ # hostnamed 中,127.0.0.2 对应主机名,127.0.0.1 对应 localhost
207+
$ getent -s myhostname hosts 127.0.0.2
208+
127.0.0.2 myhost
209+
```
210+
211+
!!! note "glibc、NSS 与静态链接"
212+
213+
如果有尝试对访问网络(使用了 NSS 的)C 程序进行静态链接(`-static`)的话,那么你可能会看到:
214+
215+
```
216+
/usr/bin/ld: /tmp/cchbUcHT.o: in function `main':
217+
example.c:(.text+0x2a): warning: Using 'gethostbyname' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
218+
```
219+
220+
这是因为 NSS 是动态加载(`dlopen`)的,如果静态链接之后扔到别的机器上,那么对应的 NSS 模块可能就不存在或者不兼容,从而导致程序无法运行。
221+
120222
#### musl
121223

122224
## 服务端 {#server}

includes/man.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ Do not link to a "generic" man page for these commands -->
9292
[logrotate.8]: https://linux.die.net/man/8/logrotate
9393
[mount.8]: https://man7.org/linux/man-pages/man8/mount.8.html
9494
[nscd.8]: https://man7.org/linux/man-pages/man8/nscd.8.html
95+
[nss-myhostname.8]: https://man7.org/linux/man-pages/man8/nss-myhostname.8.html
96+
[nss-resolve.8]: https://man7.org/linux/man-pages/man8/nss-resolve.8.html
9597
[sg_unmap.8]: https://linux.die.net/man/8/sg_unmap
9698
[sg_write_same.8]: https://linux.die.net/man/8/sg_write_same
9799
[smartctl.8]: https://linux.die.net/man/8/smartctl

0 commit comments

Comments
 (0)