解决某些主机商的VPS分配了IPv6段但是额外添加的IPv6地址不通的问题

事件背景

最近折腾IPv6的小鸡,一些主机商给小鸡分配了一个IPv6段,一般是/64,意味着有数不尽的公网IPv6地址
我心想着不折腾利用一下浪费了。于是看了一下手中的小鸡,除了刚卖出去的Dmit,剩下的去掉搬瓦工这样的隧道地址,不少廉价小鸡也有一个段的IPv6。一个网站绑定一个公网IPv6?一个容器绑定一个公网IPv6?远程组网给客户端分配IPv6?或者搞多IP节点?
先不管怎么玩,首先得保证添加的IPv6能通。但就这个前提就一波三折。

相对开放的IPv6环境

RareCloud这家的VPS,给的/64段,它家的IPv6网关收到外网的包,会主动发起NS请求,
只要是这个段上的IPv6地址,随时添加随时能用,刚添加外网就能ping通,一切顺利。

如果你的VPS属于RareCloud这种情况,就不必往下看了,下面是处理添加了IPv6,只有主地址通,其它地址不通的情况。

有限制的IPv6环境

但是myprepaidserver和veloxmedia就不行了,新添加的IPv6,外面ping不通。
起初我以为我搞错了,服务商其实只是“刚好”给我分配了::1这个地址,并不是给了我整个段。
直到我不甘心,把myprepaidserver的IPv6的地址
xxxx:xxxx:xxxx:xxxx::1修改为了xxxx:xxxx:xxxx:xxxx::2,发现xxxx:xxxx:xxxx:xxxx::2能通了。
这说明,这个小鸡确实能用这个段上的其它地址,为什么绑定多个就不行呢?
不甘心,我又添加了::1,::3,::4,::5,::6,::7,::8,::9 ,然后没管它,过段时间发现::9也通了原来的::1::2也是通的,
但是前面的3456却不通。这就勾起我好奇心了,同样添加的IPv6地址,是什么决定哪些能通哪些不能通的呢?

问题分析

我的直觉告诉我,这个问题涉及到IPv6的ND协议,它的作用和IPv4的ARP协议差不多,就是根据IP地址查物理地址,以及判断使用这个IP地址的主机是否存在。
直接上tcpdump,然后抓取IPv6的ICMP包,因为IPv6的ND协议是通过ICMP来的,
我新添加了一个IP:xxxx:xxxx:xxxx:xxxx::6666
结果发现当外部网络ping它的时候,没有收到任何NS(Neighbor Solicitation)包,反倒收到其它乱七八糟的邻居发来的NS包。这就有点奇怪了,
按理说,如果外部网络的ping包到达了VPS的网关,网关要把这个包发到我的VPS上,得先通过NS判断这个IPv6地址是存在以及查询对应的物理地址,但是我却没收到这个NS请求,那网关根本就不知道我的存在啊,那自然就不通了。
但为什么有些地址是通的呢?
然后我就尝试使用xxxx:xxxx:xxxx:xxxx::6666这个地址往外ping,

ping -I xxxx:xxxx:xxxx:xxxx::6666 www.google.com

结果依然卡住没有任何回复(-I 参数是使用指定的接口或者IP作为源地址,以免它使用默认IP或者主IP)
但是!当我准备ctrl+C中断的时候,突然通了!与此同时,外网也能ping通xxxx:xxxx:xxxx:xxxx::6666了!
然后再去看tcpdump的抓包,发现前面的ping都是发出去了,没有任何回复,
直到几十秒后,VPS发出了NS包,看了一下,是查询网关地址xxxx:xxxx:xxxx::1,然后网关回复了NA(Neighbor Advertisement),接着就收到ping的回复了,所以这个VPS发起的NS是很关键的事情。现在一切都解释得通了,为什么有时候绑定的IPv6能通有时候不能,有时候一开始不通,过一段时间又通了。

复盘过程

一开始,VPS默认绑定了一个IP也就是xxxx:xxxx:xxxx:xxxx::1
因为只有这1个地址,所以这个VPS对外所有的IPv6通信都通过这个地址,
那就意味着它在把对外的数据包发给网关的时候,会发起NS先查询网关的物理地址,而这个NS请求的源地址一定是xxxx:xxxx:xxxx:xxxx::1
虽然这个时候网关是被动回应NA的一方,但它也同时就知道了xxxx:xxxx:xxxx:xxxx::1的存在,在下次有数据包目的地址为xxxx:xxxx:xxxx:xxxx::1时,就知道转发给谁了。

后来,我给VPS添加了多个地址:

xxxx:xxxx:xxxx:xxxx::3
xxxx:xxxx:xxxx:xxxx::4
xxxx:xxxx:xxxx:xxxx::5
xxxx:xxxx:xxxx:xxxx::6
xxxx:xxxx:xxxx:xxxx::7
xxxx:xxxx:xxxx:xxxx::8
xxxx:xxxx:xxxx:xxxx::9

但是网关并不知道这些地址存在,按照正常情况,网关接收到数据包,但是不确定里面的目标地址是否存在自己的网络里时,应该主动发起NS,试探性询问网络里有没有主机拥有这个地址,但是某些服务商为了安全因素或者其它什么原因,禁用了主动发起NS。所以当这种类型的商家的IPv6网关接收到外部的包,发现里面的地址没见过的时候,就直接丢弃了。这就是新添加的IPv6,外面ping不通的原因。
那为什么偶尔,新添加的IPv6又能通呢,是因为系统在对外请求的时候,并不一定固定使用某一个IP地址作为源地址,可能会轮询。
比如上面添加了::1::9这9个地址,可能对外访问的时候,会选择其中一个,某些IP运气好刚好选中了,就有了在网关那“露面”的机会。

通俗解释

上面看不懂无所谓,下面通俗解释一下:

一个小区(网络)里面,住着很多户,
每一户(每一个VPS)里面可能住着多个人(绑定了多个IPv6)。

有个名叫RareCloud的小区,门卫(网关)比较外向。
有一天,外面寄来了一个包裹,收件人是张三(目标IPv6),
门卫一看:“张三?我好像认识又好像不认识,先喊一嗓子试试!”
于是他拿着大喇叭往小区里一喊:“张三张三!谁是张三!有没有人是张三!”(NS请求)
然后1栋1单元5楼2号(MAC地址)的张三探出脑袋回应:“我在这!”(NA回复)
然后保安就知道张三住在1单元5楼2号,于是就把包裹送到他家了,同时它把张三和他的单元楼层记下来(邻居表),下次来了张三的包裹就直到送哪里去了。
后来张三家里多了张四张五(添加多个IPv6),但是好在门卫每次收到没见过的人的包裹,都会像之前一样“喊一声”,
所以张四张五的包裹也能顺利收到。

========================================

但是另一个名叫Veloxmedia的小区,门卫王麻子很“内向”,
有一天,外面寄来了一个包裹,收件人是李四
这个门卫不知道李四是谁,也不知道他到底在不在小区里面,又不好意思喊(不主动发起NS请求)
所以就干脆把包裹扔了。
直到过了一会,李四要出门了(对外请求),但是出门之前要先去门卫那
李四只知道要出门必须要先去王麻子那(网关),但是不知道他在哪,
于是李四喊了一声:“我是李四王麻子你在哪里!?”(NS请求)
王麻子回应:“哦哦哦,我在大门口坐着的”(NA回应)
并且王麻子看了一眼声音的来源,发现李四住在2单元2楼2号(MAC地址)。于是偷偷记下来(邻居表)
于是李四才知道门卫和大门在哪,高兴出门去了。
后来,王麻子从外面收到了收件人是李四的包裹,查了一下以前的记录,知道他在2单元2楼2号,
于是就顺利把包裹送到了李四家里。

后来李四家里凭空多出来了李五李六李七李八(绑定了多个IPv6)。
有一天门卫王麻子收到了收件人是李五的包裹,他只知道李四在2单元2楼2号,
但是李五是谁又不认识了,于是又把包裹丢了。
偏偏李五李六是死宅,从来没对外露面(作为源地址对外发送数据)
所以很长一段时间,门卫都不知道李五李六等人的存在。
直到有一天,李四家李七出门了
(可能是系统自动选择的,在多IP场景下,系统出站使用的IP是哪一个我不太了解具体规则,但是可以手动指定,例如使用ping-I参数,curl--interface参数等)
门卫终于认识了李七,才知道原来李七李四是一家的,下次收到李七的包裹就知道送哪里了。

问题解决

知道问题原因了,那解决思路很简单,
就是让VPS主动以绑定的每个IPv6地址作为源地址,分别发送一次NS请求,让网关“记住”它们的存在,
对于上面的Veloxmedia小区的故事,就是让李四的全家人,都去门卫那露个面。

我在网上找了很久,都没有找到合适的工具主动发送NS,找到一个nssend,但是只有旧版的debian才有对应的软件包。
所以就干脆自己手戳吧。

项目以及使用方法

项目地址:https://github.com/kkqy-go/nser
使用golang写的,写起来比C语言快,让AI补充了一下注释和README,本来是一个小功能,怎么快怎么来。
可以自己编译,或者下载release里编译好的程序,编译好的程序名字会带有系统和架构,下面的使用说明默认以nser作为程序名。

以Veloxmedia为例,它的IPv6就属于必须要主动向网关发送NS以后网关才转发这个地址的包的类型
我使用的debian 13。
假设Veloxmedia给我分配的IPv6段是aaaa:bbbb:cccc:dddd::1/64
顺便普及一下IPv6的一个基础知识,以免有的人搞不明白为什么有::,::是简写,如果地址里有一段全是0,就可以缩成::
aaaa:bbbb:cccc:dddd::1aaaa:bbbb:cccc:dddd:0000:0000:0000:0001是等价的

注意:不同系统环境有差异,你的网卡可能不是eth0,要自己用ifconfig查一下,要把下面的aaaa:bbbb:cccc:dddd换成你的实际前缀

临时添加IP:

ip addr add aaaa:bbbb:cccc:dddd::2/64 dev eth0
ip addr add aaaa:bbbb:cccc:dddd::3/64 dev eth0
ip addr add aaaa:bbbb:cccc:dddd::4/64 dev eth0
ip addr add aaaa:bbbb:cccc:dddd::5/64 dev eth0
ip addr add aaaa:bbbb:cccc:dddd::6/64 dev eth0
ip addr add aaaa:bbbb:cccc:dddd::7/64 dev eth0
ip addr add aaaa:bbbb:cccc:dddd::8/64 dev eth0
ip addr add aaaa:bbbb:cccc:dddd::9/64 dev eth0

永久添加IPv6得修改/etc/network/interfaces

auto lo
iface lo inet loopback

# The primary network interface
allow-hotplug eth0
iface eth0 inet static
        ...
iface eth0 inet6 static
        accept_ra 0
        address aaaa:bbbb:cccc:dddd::1/64
        gateway aaaa:bbbb:cccc::1
        # 添加到这里
        up ip addr add aaaa:bbbb:cccc:dddd::2/64 dev eth0 
        up ip addr add aaaa:bbbb:cccc:dddd::3/64 dev eth0
        up ip addr add aaaa:bbbb:cccc:dddd::4/64 dev eth0
        up ip addr add aaaa:bbbb:cccc:dddd::5/64 dev eth0
        up ip addr add aaaa:bbbb:cccc:dddd::6/64 dev eth0
        up ip addr add aaaa:bbbb:cccc:dddd::7/64 dev eth0
        up ip addr add aaaa:bbbb:cccc:dddd::8/64 dev eth0
        up ip addr add aaaa:bbbb:cccc:dddd::9/64 dev eth0

你想添加更多也可以,就/64而言可是从aaaa:bbbb:cccc:dddd:0000:0000:0000:0000aaaa:bbbb:cccc:dddd:ffff:ffff:ffff:ffff足足2^64次方个地址可以用!
我这里举例,只添加了8个IPv6地址,
这个时候从外网ping,大概率很多是不通的(可能个别是通的,原因上面已经讲过了)。
这个时候使用以下参数运行一下nser:

./nser -gateway -iface eth0

做完这一步,上面添加的IPv6全都能通了。

这个是nser最简单的用法,就是让nser遍历eth0上的所有IPv6地址,然后以每个IPv6地址分别发送一次NS请求到网关地址,以让网关加入邻居表。
当然你也可以指定-src-dst手动发送,但对于一般情况,直接用上述参数就行了。

只要网关记住你了,应该就是一直有效的,
实测网关会周期性对邻居表里的条目发起NS请求确认主机是否还存在(这个时候它又会发送NS了,说明它只对有缓存的记录才会主动发起NS),
如果存在,会继续延长时间。所以不必担心会不会突然又不通了。
但是如果后面,你的VPS因为故障或者其它原因离线时间比较长,可能会网关会“忘记”你,所以下次启动还得使用nser给网关“露个面”。

其它说明

也许这个帖子并不能解决你的问题,因为你的IPv6 ping不通也许是其它问题:
1.比如防火墙
2.比如网关、路由配置错误
3.比如环境压根没有IPv6

  1. ...
    并不一定对症下药,但也许能帮到某些遇到一样问题的人。

后话

折腾是乐趣,折腾的结果也许并没有太多用处,但是体会的是学习过程,通过解决这个问题,我把IPv6的ND协议搞明白了。
肯定很多朋友看到这里觉得一个一个添加IPv6太麻烦了,能不能把整个段绑定上去,或者有没有办法给docker里的容器分配不同的IPv6,
这次打了这么多字累了,这个留到下一次讲了。
(主要是debian 13软件源里的ndppd有bug,所以需要自己编译和替换,关于ndppd,这两天也积累了不少槽点)

标签: none

添加新评论