... 以补偿我们无法控制的损坏的 DNS 服务器。
我们的问题:我们在各种(主要是 IPv4 专用)站点部署嵌入式设备来收集传感器数据。一些站点的网络维护不善,例如配置错误或损坏的 DNS 缓存和/或防火墙要么完全忽略 AAAA 查询,要么以损坏的答复(例如错误的源 IP!)来响应它们。作为设施部门的外部供应商,我们几乎无法影响(有时不情愿的)IT 部门。他们近期修复 DNS 服务器/防火墙的可能性微乎其微。
对我们的设备的影响是,每次使用 gethostbyname() 时,进程都必须等到 AAAA 查询超时,此时某些进程的连接尝试已经完全超时。
我正在寻找解决方案...
- 系统范围内。我无法单独重新配置数十个应用程序
- 非永久性且可配置。我们需要在 IPv6 修复/推出时(重新)启用它。重启即可。
- 如果解决方案需要替换核心库(如 glibc),则替换库包应可从已知维护良好的存储库(例如 Debian Testing、Ubuntu universe、EPEL)获得。自行构建不是一种选择,原因太多了,我甚至不知道从哪里开始,所以我根本不会列出它们……
最明显的解决方案是配置解析器库,例如通过 /etc/{解决,切换器,盖}.conf 不查询 AAAA 记录。no-inet6
建议使用resolv.conf 选项这里将会确切地我正在寻找的。不幸的是,它没有实现,至少在我们的系统上没有实现(Debian 7 上的 libc6-2.13-38+deb7u4;Ubuntu 14.04 上的 libc6-2.19-0ubuntu6.3)
那么怎么办呢?人们在 SF 和其他地方发现了以下建议的方法,但它们都不起作用:
- 完全禁用 IPv6,例如在 /etc/modprobe.d/ 中将 ipv6 LKM 列入黑名单,或者
sysctl -w net.ipv6.conf.all.disable_ipv6=1
。(出于好奇:为什么解析器在禁用 IPv6 的情况下要求 AAAA?) - 从 /etc/resolv.conf 中删除
options inet6
。它本来就不存在,inet6
现在只是默认启用。 - 在 /etc/resolv.conf 中设置
options single-request
。这仅确保 A 和 AAAA 查询按顺序完成,而不是并行完成 - 在 /etc/gai.conf 中进行更改
precedence
。这不会影响 DNS 查询,只会影响如何处理多个回复。 - 使用外部解析器(或运行绕过损坏的 DNS 服务器的本地解析器守护程序)会有所帮助,但公司的防火墙政策通常不允许这样做。而且它可能会导致内部资源无法访问。
其他丑陋想法:
- 在本地主机上运行 DNS 缓存。将其配置为转发所有非 AAAA 查询,但使用 NOERROR 或 NXDOMAIN 响应 AAAA 查询(取决于相应 A 查询的结果)。不过,我不知道有哪个 DNS 缓存可以做到这一点。
- 使用一些巧妙的 iptables u32 匹配,或者 Ondrej Caletka 的iptables DNS 模块匹配 AAAA 查询,以便要么 icmp 拒绝它们(解析器库会如何反应?),要么将它们重定向到以空的 NOERROR 响应所有内容的本地 DNS 服务器。
请注意,SE 上有类似的相关问题。我的问题有所不同,因为它详细说明了我正在尝试解决的实际问题,因为它列出了明确的要求,因为它将一些经常建议的无效解决方案列入黑名单,并且它不针对单个应用程序。以下这次讨论,我发布了我的问题。
答案1
不要再使用gethostbyname()
。你应该改用getaddrinfo()
,而且早就该用了。手册页甚至会警告你这一点。
gethostbyname*()、gethostbyaddr*()、herror() 和 hstrerror() 函数已过时。应用程序应改用 getaddrinfo(3)、getnameinfo(3) 和 gai_strerror(3)。
下面是一个用 C 编写的快速示例程序,演示了查找仅有的一个名称的记录,以及一个显示仅有的记录查找已通过网络进行。
具体来说,如果您只想进行 A 记录查找,则需要设置ai_family
为AF_INET
。此示例程序仅打印返回的 IP 地址。getaddrinfo()
有关如何进行传出连接的更完整示例,请参阅手册页。
在里面Wireshark 捕获,172.25.50.3 是本地 DNS 解析器;捕获是在那里进行的,因此您还可以看到其传出的查询和响应。请注意仅有的请求了 A 记录。未进行过 AAAA 查找。
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netdb.h>
#include <stdio.h>
int main(void) {
struct addrinfo hints;
struct addrinfo *result, *rp;
int s;
char host[256];
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;
s = getaddrinfo("www.facebook.com", NULL, &hints, &result);
if (s != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
exit(EXIT_FAILURE);
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
getnameinfo(rp->ai_addr, rp->ai_addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST);
printf("%s\n", host);
}
freeaddrinfo(result);
}
答案2
如有疑问,请转至源代码!那么,让我们看看...获取主机名()看起来很有趣;这恰恰描述了我们所看到的情况:首先尝试 IPv6,如果没有得到满意的答案,则返回 IPv4。这个RES_USE_INET6
标志是什么?追溯一下,它来自res_setoptions()。这是resolv.conf
读入的地方。
而且……我真是没主意了。我完全不清楚RES_USE_INET6
如果不是的话,那要如何设置resolv.conf
。
答案3
您可以使用 BIND 作为本地解析器,它有一个过滤 AAAA 的选项:
https://kb.isc.org/article/AA-00576/0/Filter-AAAA-option-in-BIND-9-.html
答案4
您是否尝试过设置 PDNS-recursor,将其设置在 /etc/resolv.conf 中并拒绝其中的“AAAA”查找?使用类似query-local-address6=