在openwrt上,获取到IPv6地址后,ping知名dns server失败:
# ping 240c::6666
PING 240c::6666 (240c::6666): 56 data bytes
ping: sendto: Permission denied
但是指定接口,或源ip地址后,ping ok:
# ping -I 240e:fa:c6b3:ed00:2e61:4ff:fefd:7282 240c::6666
PING 240c::6666 (240c::6666) from 240e:fa:c6b3:ed00:2e61:4ff:fefd:7282: 56 data bytes
64 bytes from 240c::6666: seq=0 ttl=57 time=30.254 ms
64 bytes from 240c::6666: seq=1 ttl=57 time=29.194 ms
^C
--- 240c::6666 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 29.194/29.724/30.254 ms
查看路由表:
# ip -6 route
default from 240e:fa:c6b3:ed00:2e61:4ff:fefd:7282 via fe80::1 dev eth4094 proto static metric 512
default from 240e:fa:c6b3:ed10::/60 via fe80::1 dev eth4094 proto static metric 512
240e:fa:c6b3:ed00::/64 dev eth4094 proto static metric 256
240e:fa:c6b3:ed10::/64 dev br-lan proto static metric 1024
unreachable 240e:fa:c6b3:ed10::/60 dev lo proto static metric 2147483647 error -101
fe80::/64 dev eth3 proto kernel metric 256
fe80::/64 dev eth4 proto kernel metric 256
fe80::/64 dev eth5 proto kernel metric 256
fe80::/64 dev eth4094 proto kernel metric 256
fe80::/64 dev ath00 proto kernel metric 256
fe80::/64 dev br-lan proto kernel metric 256
fe80::/64 dev ath01 proto kernel metric 256
fe80::/64 dev ath10 proto kernel metric 256
fe80::/64 dev ath11 proto kernel metric 256
有两台带源地址的默认路由,但是没有制定源IP,则没有匹配任何一条路由:
使用ip -6 route get测试,错误码正好是EACCES。
# ip -6 route get 240c::6666
prohibit 240c::6666 from :: dev lo table unspec proto kernel src 240e:fa:c6b3:ed00:2e61:4ff:fefd:7282 metric 4294967295 error -13
问题定位:
编译strace,查看ping的过程:
socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6) = 3
dup2(3, 0) = 0
close(3) = 0
setsockopt(0, SOL_ICMPV6, 1, "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\375\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377", 32) = 0
setsockopt(0, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
setsockopt(0, SOL_SOCKET, SO_RCVBUF, [7280], 4) = 0
setsockopt(0, SOL_RAW, 0x7 /* RAW_??? */, [2], 4) = 0
setsockopt(0, SOL_IPV6, 0x8 /* IPV6_??? */, [1], 4) = 0
rt_sigaction(SIGINT, {0xb6f223b8, [INT], SA_RESTART|0x4000000}, {SIG_DFL, [], 0}, 8) = 0
gettimeofday({1576340049, 676382}, NULL) = 0
sendto(0, "\200\0\0\0\5>\0\0^\310\264D\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 64, 0, {sa_family=AF_INET6, sin6_port=htons(0), inet_pton(AF_INET6, "240c::6666", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = -1 EACCES (Permission denied)
创建的是SOC_RAW类型的套接字,跟踪内核源代码3.14.17:
rawv6_sendmsg()net/ipv6/raw.c
-》ip6_dst_lookup_flow()net/ipv6/ip6_output.c
-》ip6_dst_lookup_tail()net/ipv6/ip6_output.c
-》ip6_route_output()net/ipv6/route.c
在ip6_dst_lookup_tail()中会先调用ip6_route_output匹配路由,在调用ip6_route_get_saddr()设置源地址。但是上述源地址是0,不会匹配两条带源地址的默认路由。导致路由查找失败。
测试最新的OpenWrt版本的内核,没有这个问题,ping可以正常通。比较内核版本的差异,发现Markus Stenberg提交了patch,他的做法是,先匹配一个源地址,再进行路由查找。请查看附件链接。
参考:
http://patchwork.ozlabs.org/patch/468061/
https://vincent.bernat.ch/en/blog/2017-ipv6-route-lookup-linux