我遇到了间歇性连接问题。一个 ipv6 链接本地地址会自动添加到我的 /etc/resolv.conf 中,这似乎导致 libc 的解析器无法解析。我想知道如何阻止该地址插入那里或找到合适的解决方法。
我的设置:我有一个 Ubuntu 14.04 桌面部署设置,其中有 ipv4 和 ipv6。它只有一个有线连接(没有 wifi),连接到运行 OpenWrt 的家用路由器的 LAN 端口。桌面的网络由 NetworkManager 负责,它运行自己的 dnsmasq 本地副本。/etc 中的所有网络管理器文件都是“库存”,我没有动过它们。
当我通过网络管理器重置网络时,一切都正常(但只持续了几分钟)。我的 resolv.conf 工作配置如下:
user@foo:/$ cat /etc/resolv.conf
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by
resolvconf(8)
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 127.0.1.1
search lan
我的路由器(192.168.1.1 或 fe80::beef)也运行 dnsmasq 的一个副本,并配置为在 v4 上向其 dhcp 客户端通告 192.168.1.1(本身)。在 v6 上,它会定期发送路由器通告消息(icmpv6.type == 134),其中包含 fe80::beef 的递归 DNS 服务器选项。路由器的 dnsmasq DNS 服务正在监听两个地址:.1.1 和 ::beef(路由器的 LAN 桥接 IPv6 链接地址)。
# working dns server. ran from the desktop.
user@foo:/$ dig google.com +time=1 @fe80::beef > /dev/null ; echo $?
0
任何时候,如果我转到 NetworkManager 中的“连接信息”,我的 Ipv4 中的主 DNS 和路由器都会设置为 182.168.1.1。NetworkManager GUI 在“ipv6”部分标题下不显示任何信息 - 但我的网卡会收到 ipv6 地址(slaac 和有状态 dhcpv6 地址),我可以使用 查看ip addr show
。
问题:通过网络管理器重置我的网络(切换“启用网络”)并等待(即等待直到下一个路由器广告消息,我怀疑)后,一个新的条目进入/etc/resolv.conf
:
user@foo:~$ cat /etc/resolv.conf
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver fe80::beef
nameserver 127.0.1.1
search lan
问题是,一旦发生这种情况,一些用户空间工具(包括 Firefox 和 google-chrome)将无法解析(非缓存)域名。
据我所知,使用链接本地地址需要明确提及链接范围。以下跟踪显示了在connect
没有链接范围的情况下如何失败(默认 scope_id 为 0)。
user@foo:~$ strace ping google.com
execve("/bin/ping", ["ping", "google.com"], [/* 73 vars */]) = 0
...
stat("/etc/resolv.conf", {st_mode=S_IFREG|0644, st_size=220, ...}) = 0
socket(PF_INET6, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET6, sin6_port=htons(53), inet_pton(AF_INET6, "fe80::beef", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = -1 EINVAL (Invalid argument)
close(3) = 0
socket(PF_INET6, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET6, sin6_port=htons(53), inet_pton(AF_INET6, "fe80::beef", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = -1 EINVAL (Invalid argument)
close(3) = 0
write(2, "ping: unknown host google.com\n", 29ping: unknown host google.com) = 29
exit_group(2) = ?
+++ exited with 2 +++
DNS 失败,但是其他方面我可以连接:
user@foo:~$ ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=60 time=7.77 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=60 time=7.81 ms
....
user@foo:~$ ping6 2607:f8b0:400a:808::200e # google.com AAAA
PING 2607:f8b0:400a:808::200e(2607:f8b0:400a:808::200e) 56 data bytes
64 bytes from 2607:f8b0:400a:808::200e: icmp_seq=1 ttl=57 time=7.94 ms
64 bytes from 2607:f8b0:400a:808::200e: icmp_seq=2 ttl=57 time=7.86 ms
...
在 resolv.conf 中的地址末尾添加范围(%eth0)可以解决此问题:
nameserver fe80::beef%eth0
nameserver 127.0.1.1
search lan
但当然,下一次这个变化就会被消除。
有什么办法可以:
- 强制仅使用 ipv4 来查询 DNS(我认为我不会很快运行仅使用 ipv6 的设置)
- 在解析器中指定默认接口范围(即“%eth0”)
- 将路由器的 dnsmasq(或 radvd 或 rdnss)更改为不是向 DNS 公布其 ipv6 地址(仅限其 ipv4)
编辑:修复尝试失败
如果我将/etc/resolv.conf
符号链接移动到/etc/resolv.conf.old
并编写我自己的静态文件/etc/resolv.conf
,其中仅包含本地 dnsmasq 服务器 ip(名称服务器 127.0.1.1),我会发现该文件仍被其他添加了“搜索”行的东西修改。
user@foo:~$ cat /etc/resolv.conf # my new file, not the symlink
# Edited by hand to avoid using the ipv6 link local scopeless address
# check resolv.conf.old to see normal file
#nameserver fe80::beef
nameserver 127.0.1.1
search lan
过了一会儿:
user@foo:~$ cat /etc/resolv.conf # my new file prefixed by something
search lan.
# Edited by hand to avoid using the ipv6 link local scopeless address
# check resolv.conf.old to see normal file
#nameserver fe80::beef
nameserver 127.0.1.1
search lan
重启后:
user@foo:~$ cat /etc/resolv.conf # eh. same line added again.
search lan.
search lan.
# Edited by hand to avoid using the ipv6 link local scopeless address
# check resolv.conf.old to see normal file
#nameserver fe80::beef
nameserver 127.0.1.1
search lan
所以,除非我开始使用 chattr +i 和其他技巧来阻止该脚本(或任何其他脚本)接触 /etc/resolv.conf,否则我觉得这个半静态选项并不干净。当这些文件发生更改或记录更改时,可追究责任,这将是一个优点。syslog 什么都没有。
注意:为了隐藏一些私人信息,我删除了上面的地址后缀和主机名
答案1
我正在回答我自己的问题,并提供解释和补丁以供参考。
问题出在软件包 提供的 dhcp6c 脚本中wide-dhcpv6-client
。dhcp6c
它在我的系统上作为守护进程运行(由 启动/etc/init.d/wide-dhcpv6-client
),每隔几分钟发送一次 DHCPv6 请求。我认为wide-dhcpv6-client
ubuntu 桌面中默认没有安装。它的配置 ( /etc/wide-dhcpv6/dhcp6c.conf
) 设置为/etc/wide-dhcpv6/dhcp6c-script
在需要进行更新时调用脚本。此脚本存在错误:
对于链路本地地址,它会忽略范围
如果
/etc/resolv.conf
不受 resolvconf 管理(即不是符号链接),则会导致search X
附加行而不检查行是否已经存在(然后文件会不断增长)。
假设您有一个由 dhcp6c** 监控的单个接口,/etc/wide-dhcpv6/dhcp6c-script
使用以下补丁修补该文件将解决问题:
--- /etc/wide-dhcpv6/dhcp6c-script.orig 2016-09-04 17:12:35.405042056 -0700 +++/etc/wide-dhcpv6/dhcp6c-script.new 2016-09-04 22:57:05.213169351 -0700 @@ -6,20 +6,48 @@ [ -f /etc/默认/宽-dhcpv6-客户端 ] && . /etc/默认/宽-dhcpv6-客户端 +# 猜测用作 scope_id 的接口。理想情况下,dhcp6c 将传递 +# 接收 DHCPv6 回复的接口的名称。 +范围=“” +对于 $INTERFACES 中的 IFACE;执行 + 范围=“$IFACE” +休息; +完成 + +# 精确行匹配。grep 会将域名句点解释为特殊 +# 字符。简单循环避免依赖其他非核心工具 +# 命令(sed、awk 等)。 +有线(){ + 本地电话 + 当读取行时;执行 + 如果 [ “$line” = “$1” ]; 然后 + 返回 0 + 菲 + 完成 + 返回 1 +} + + 如果 [ -n “$new_domain_name” -o -n “$new_domain_name_servers” ]; 那么 old_resolv_conf=/etc/resolv.conf new_resolv_conf=/etc/resolv.conf.dhcp6c-new rm -f $new_resolv_conf 如果 [ -n "$new_domain_name" ]; 那么 - 回显搜索 $new_domain_name >> $new_resolv_conf + has_line "搜索 $new_domain_name" < $old_resolv_conf || { + 回显“搜索 $new_domain_name”>> $new_resolv_conf + } 菲 如果 [ -n "$new_domain_name_servers" ]; 那么 对于 $new_domain_name_servers 中的名称服务器;执行 + 如果 [ -n "$scope" -a "${nameserver##fe80::}" != "$nameserver" ]; 然后 + 名称服务器="$名称服务器%$范围" + 菲 + # 无需添加已经存在的名称服务器 — res = $(grep“名称服务器$名称服务器”$old_resolv_conf) - 如果 [ -z "$res" ]; 那么 - 回显名称服务器 $nameserver >> $new_resolv_conf - 菲 + has_line "名称服务器 $nameserver" < $old_resolv_conf || { + 回显“名称服务器 $nameserver”>> $new_resolv_conf + } 完毕 菲
**上述代码将从 中的接口列表中选择第一个接口/etc/default/wide-dhcpv6-client
,并将其后缀为以 开头的任何名称服务器fe80::
。这与 中所做的类似/etc/dhcp/dhclient-enter-hooks.d/resolvconf
。它并不理想,特别是如果您有多个接口。dhcp6c 不会在环境中传递接口的名称,因此我们只能猜测。(错误记录:https://bugs.launchpad.net/ubuntu/+source/wide-dhcpv6/+bug/1620221)
如果您有多个网卡并且需要它更加强大,您可以配置/etc/wide-dhcpv6/dhcp6c.conf
根据接口名称启动不同的脚本副本,并在脚本中对接口名称进行硬编码。
在我弄清楚问题之前,我发现了两种解决方法,它们虽然不能解决问题,但却可以避免症状。
解决方法 1:禁用 dnsmasq 本地解析器。
当 NetworkManager 的 dnsmasq 服务被禁用时,resolv.conf 中的条目会正确填充。所以我失去了它的好处。不完美。
按照步骤如何禁用网络管理器使用的 DNS?.另请参阅安全和性能影响列表禁用本地解析器。
重新启动 NM 后,链接本地地址已正确添加范围:
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 192.168.1.1
nameserver fe80::beef%eth0
search lan
我不太清楚为什么这样做有效,但我怀疑这是因为 NetworkManager 和 dhcp6c 都写入了 resolvconv,并且更新脚本倾向于 fe80::beef%eth0(由 NetworkManager 传递)而不是没有范围的相同地址(由 dhcp6c 传递)。
解决方法 2:配置 OpenWrt 以将附加的 dhcpv6 服务器作为 RDNSS 的一部分进行宣传。此服务器将优先于 /etc/resolv.conf 中的链接本地服务器。我觉得这个解决方案比解决方法 #1 略好一些,因为它保留了本地解析器(至少对于 ipv4 而言),但它更复杂。
使用 IPv6 ULA-Prefix 设置 OpenWrt,例如
fd00:cafe::/48
。(Menu -> Network -> Interfaces -> bottom of page
)。路由器将在fd00::cafe::1
局域网上获取本地 ipv6。(我在 OpenWRT 15.05.1 chaos calmer 上尝试过这个)。指示 odhcpd(在该版本的 openwrt 上提供 dhcpv6 服务)也将 IP 作为(附加的)DNS 服务器进行宣传。odhcpd(版本 2015-11-19-01d3f...)提供了一个名为 dhcp.lan.dns 的配置标志(标志描述)。在 luci UI 中,它位于 下
Menu -> Network -> Interfaces -> LAN (click edit) -> DHCP Server section -> IPv6 Settings tab -> Announced DNS Servers
。将路由器的 ULA 地址添加到字段中。为了对称,我还将路由器的 ipv4 子网地址添加到 dns 服务器列表 (192.168.1.1)。
有了这个,odhcpd 会将这两个地址作为 RA 的一部分进行宣传,但对我们有利的是,NM 只会将 ULA 地址写入 resolv.conf。链路本地地址永远不会添加到 resolv.conf:
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver fd00:cafe::1
nameserver 127.0.1.1
search lan
查看 wireshark 跟踪,当以这种方式配置时,来自 openwrt 的 RA 消息将列出两个 dns 服务器地址,但其 DHCPv6 回复只有一个fd00:cafe::1
,因此避免了 dhcp6c 脚本中的错误。
如果你在网络上有一台 unix 主机不是运行本地解析器(例如如果你在 ubuntu 上应用了解决方法 1 和 2),resolv.conf 将拥有所有 IP:
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 192.168.1.1
nameserver fd00:cafe::1
nameserver fe80::beef%eth0
search lan
libc 解析器中的默认算法是依次尝试 resolv.conf 中的每个条目,每个条目都超时。因此,如果出现问题,同一个 DNS 服务器的多个别名可能会导致额外的延迟。就我而言,这很可能意味着我的整个路由器都宕机了,而不仅仅是它的 DNS 服务器,所以这不是问题。但您的情况可能会有所不同。
额外的我使用以下脚本调试对 resolvconf 所做的更改。这使我能够找出哪个守护进程发送了错误的更改:
#!/bin/bash
# logs all interactions to /sbin/resolvconf into syslog
# this script is named /sbin/resolvconf, temporarily.
# moved resolvconv binary to resolvconf.real, temporarily.
BIN=/sbin/resolvconf.real
LOGGER=/usr/bin/logger
ppid () { ps -p ${1:-$$} -o ppid= 2>/dev/null | sed 's/ //g'; }
PROC=$$
"$LOGGER" "resolvconf.wrapper args: $@"
for ((i=0; i<4; i++)); do
PROC=$(ppid $PROC)
if [[ "$PROC" == 0 || "$PROC" == "" ]]; then
break;
fi
if [[ "$i" -eq 0 ]]; then
"$LOGGER" "resolvconf.wrapper invoked by: pid=$PROC $(/bin/ps -p $PROC -o command=)"
else
"$LOGGER" "resolvconf.wrapper child of: pid=$PROC $(/bin/ps -p $PROC -o command=)"
fi
done
# peek at stdin (which contains the config)
if [[ "$1" == "-a" ]] && [[ -n "$2" ]]; then
tmp=$(/bin/mktemp)
while read REST; do
logger "resolvconf.wrapper feeding: $REST"
echo $REST >> "$tmp"
done
exec < "$tmp"
rm "$tmp"
fi
exec -a /sbin/resolvconf "$BIN" "$@"
有了这个,syslog 包含如下块:
Sep 3 02:32:29 calm logger: resolvconf.wrapper invoked by: pid=12610 sh -c /sbin/resolvconf -a NetworkManager
Sep 3 02:32:29 calm logger: resolvconf.wrapper child of: pid=11910 NetworkManager
Sep 3 02:32:29 calm logger: resolvconf.wrapper child of: pid=1 /sbin/init
Sep 3 02:32:29 calm logger: resolvconf.wrapper feeding: # Generated by NetworkManager
Sep 3 02:32:29 calm logger: resolvconf.wrapper feeding: domain base
Sep 3 02:32:29 calm logger: resolvconf.wrapper feeding: search base base.
Sep 3 02:32:29 calm logger: resolvconf.wrapper feeding: nameserver 127.0.1.1
...
Sep 3 02:14:23 calm logger: resolvconf.wrapper gparent: pid=1553 /usr/sbin/dhcp6c -Pdefault eth0
Sep 3 02:14:23 calm logger: resolvconf.wrapper invoked by: pid=15926 /bin/sh /etc/wide-dhcpv6/dhcp6c-script
Sep 3 02:14:23 calm logger: resolvconf.wrapper args: -a eth0
Sep 3 02:14:23 calm logger: resolvconf.wrapper feeding: search base.
Sep 3 02:14:23 calm logger: resolvconf.wrapper feeding: nameserver fd00:cafe::1