昨夜梦里惊魂,1997年,安阳市文峰中路老口腔医院门口那个卖冰糖葫芦的老人,他死了。1997年我刚上初中,他已经是老人了,我上学路上老是碰到他,却从没有买过他一个冰糖葫芦…现在,他死了,那个卖洗衣膏的人也死了。
诶,冰糖葫芦诶开口味诶…
沧海烧成酒,烫胸口,一口口都是愁。狂饮高歌,爽快唱!
上周末,我想再写一篇关于IPv6自动配置的文章,但是周日下午疯子开车带我去我和室友暂住了小半年的小屋子里拉行李,就没有了时间,也就只能大致理一个思路。在这深夜,我补全剩余的。正好今天晚上盯盘,把这篇文章完成。
早在2012年,我就开始扯IPv6的自动配置,比方说这篇:
闲谈IPv6-典型特征的一些技术细节: https://blog.csdn.net/dog250/article/details/8169984
也说明,从那个时候开始,我就已经在写 “IPv6闲谈” 系列文章了。
我觉得这个要继续下去,而且我也是一个理想味儿十足的现实主义患者,所以,光说不练假把式,于是本文将给大家带来一种不一样的感觉,本文是一个夹杂着形而上说辞的Howto,不伦不类,但可以看透是非。
我喜欢将所有理论摆平,但是本文中,我将展示一个可以实际操作的过程。
请阅读。
IPv6的地址不好记忆 一直是IPv4卫道士们的一个槽点,然而谁让你去记忆IPv6地址了?
IPv6庞大到天文数字的地址数量,天生就是为 万物互联 而生的,如若真的每一粒沙子都有一个IPv6地址,试问哪个人可以靠记忆去手敲地址进行配置,就算不靠疲惫的大脑来记忆,如果把这些地址和配置打印在A4的纸上,实际上最大的图书馆都装不下,那么存在计算机存储介质上呢?你试着算算看它们将占据多大的存储空间,以及读取它们需要多久的时间?
别指望靠人去配置了!随着技术的发展,人是越来越靠不住的,机器才可以。
于是,IPv6的一个杀手特性引起了人们的注意,即 自动配置!
来自RFC4862的福音,请先阅读:
RFC 4862-IPv6 Stateless Address Autoconfiguration: https://tools.ietf.org/html/rfc4862
有人说,IPv4不也有DHCP吗?是的,但是它们的本质却是不同的。DHCP需要你去配置 另一个协议的服务 ,即你需要搭建DHCP服务器,你需要DHCP客户端,比如在Windows上,你就要开启dhcp client服务,天啊,这也是个服务!
换句话说,DHCP是IPv4协议本身之外的东西。而IPv6自动配置却是其内在的东西,它是IPv6协议标准的一部分。一个IPv6终端,只要简单的开机,它就会自动获得IPv6地址。
广泛点说,IPv4的Zeroconf不也是一种标准吗?zeroconf详见:
Zero-configuration networking : https://en.wikipedia.org/wiki/Zero-configuration_networking
嗯,看起来是啦,但还是不一定。
事实上,这种事一直在上演。Anycast这种Trick不也是在IPv4网络被实践了很多年吗?互联网领域内这种没有门槛的算法之外的技术,只有想不到,没有做不到,这不是粒子对撞机。
XXX古已有之 这种说辞到处可以说,我们早已习惯,但是看实质,那还真的不一样。
当使能IPv6自动配置时,一个节点在从获得自动配置的地址开始,到地址不再可用的这段时间,成为该地址的一个生命周期,在时间轴上,我将其列如下:
我们在Linux系统中间隔几秒连续两次查看同一个网卡的同一个自动配置的地址(下图中的自动配置的地址来自于我的常规配置,不必较真儿这是为什么,下面全部讲清楚):
在Prefered和Invalid之间,即Prefered时间已经退到了0,而Invalid时间还有剩余,此时的地址就处在Deprecated状态,该状态的地址可以继续作为目标地址,但是不建议作为源地址,除非它的scope更加合适,详见RFC3484:
Rule 2: Prefer appropriate scope.
If Scope(SA) < Scope(SB): If Scope(SA) < Scope(D), then prefer SB
and otherwise prefer SA. Similarly, if Scope(SB) < Scope(SA): If
Scope(SB) < Scope(D), then prefer SA and otherwise prefer SB.
Rule 3: Avoid deprecated addresses.
The addresses SA and SB have the same scope. If one of the two
source addresses is “preferred” and one of them is “deprecated” (in
the RFC 2462 sense), then prefer the one that is “preferred.”
关于Anycast这个话题,详见:
闲谈IPv6-Anycast以及在Linux/Win7系统上的Anycast配置 :https://blog.csdn.net/dog250/article/details/88071601
如果想要彻底理解IPv6自动配置以及通信的过程,这里有必要联系邻居发现再说一下Anycast。
IPv6的邻居发现和IPv4的ARP有大不同。
仔细看RFC4861中的协议格式:
RFC4861中对O位的解释是:
Override flag. When set, the O-bit indicates that
the advertisement should override an existing cache
entry and update the cached link-layer address.
When it is not set the advertisement will not
update a cached link-layer address though it will
update an existing Neighbor Cache entry for which
no link-layer address is known. It SHOULD NOT be
set in solicited advertisements for anycast
addresses and in solicited proxy advertisements.
It SHOULD be set in other solicited advertisements
and in unsolicited advertisements.
【 注意,邻居请求协议中没有O位! 】
明确表明, 针对Anycast地址的邻居请求,不会覆盖已经有的邻居条目 ,这意味着,一个Anycast set中的路由器,谁的通告先到达请求者,请求者就将谁设置为邻居,后面再来的通告不会覆盖已经有的邻居项。
我们再看看专门对Anycast邻居发现的约束:
Anycast addresses - Anycast addresses identify one of a set of
nodes providing an equivalent service, and multiple nodes on
the same link may be configured to recognize the same anycast
address. Neighbor Discovery handles anycasts by having nodes
expect to receive multiple Neighbor Advertisements for the
same target. All advertisements for anycast addresses are
tagged as being non-Override advertisements. A non-Override
advertisement is one that does not update or replace the
information sent by another advertisement. These
advertisements are discussed later in the context of Neighbor
advertisement messages. This invokes specific rules to
determine which of potentially multiple advertisements should
be used.
理解了这个,就可以明白为什么可以把Anycast地址设置位默认网关而不会出现邻居表抖动了。
我们回顾一下IPv4的ARP。
ARP其实不算是标准的IP协议,它只能说是IP协议的辅助协议,它位于IP协议之下。比如它通信的时候,不必封装标准IPv4协议头。但是IPv6的邻居发现则不同。
IPv6的邻居发现封装于ICMPv6报文中,而ICMPv6则必然立于IP协议之上,它是由标准的IPv6协议头封装的,于是,事情就统一了,好一个统一,帅得很!
也就是说,哪怕发送一个邻居发现报文,即ICMPv6报文,也要封装一个IPv6协议头。但是自动配置之前,主机节点并不知道自己的IP地址,在先有鸡还是先有蛋的困局下,于是链路本地地址就派上了用场。IPv6链路本地地址是一块使能了IPv6协议的网卡与生俱来的!我们知道,IPv6自动配置中,链路本地地址是关键,它不像IPv4时代必须在后来见招拆招般规定一个保留地址段,比如169.254/16用于无地址通信。IPv6扫除了IPv4年代大部分Zeroconf机制存在的必要性!
既然ICMPv6邻居发现协议也是一个普通的IPv6报文,那么如何解析Anycast地址的MAC呢?
如果把Anycast地址配置为默认网关,那么在发包到外网时,势必需要解析它的MAC地址,然而Anycast节点是一个Set而不是一台特定的主机,所以,基于 最短度量 ,IPv6采用 先到先得,不覆盖 的原则来解析,即 离请求主机最近的Anycast Set中的路由器肯定最先回复邻居通告 ,然后忽略掉后来的那些更远的通告。这对正向主动的发包过程是很好理解的,但是如果返回路径的包从另一个Anycast Set中的路由器过来呢?
该路由器R1不是正向数据包出发时经由的那台路由器R0,它要解析主机的MAC地址,于是它发送邻居请求给主机。
看到这里,我想很多人都会有个疑问,如果路由器R1发送邻居请求,它是Anycast Set的一员,自然也有Anycast地址,那么它的邻居请求被主机H接收到之后,R1的MAC地址会不会冲掉原始的R0邻居项呢?
Anycast-R0的MAC会不会被Anycast-R1的MAC地址替换?你说呢?
答案是不会! Why?
因为关于Anycast,有一个原则,即 Anycast地址不要作为源地址! 因此,在路由器R1发出的针对H的邻居请求的IPv6协议头部中,源地址并不是这个Anycast地址,而是路由器R1的相关网卡的链路本地地址,详情还是要参看RFC3484关于IPv6报文源地址的选择细节,这里只要关注,IPv6邻居发现报文是一个ICMPv6报文,它也是一个普通的IPv6协议封装的报文!
皮鞋湿而不胖,请接着看。
IPv6真正标识了网络,这点和IPv4有着本质的不同。
IPv6路由器管理的是网段,而不是主机。IPv6路由器靠 邻居发现 仅仅管理到满足它 知道这个邻居属于自己的一个网段 这样的程度即可!这点是靠类似IPv6 EUI-64映射机制实现的。
也就是说,IPv6的主机标识由主机自己来生成和维护! IPv6实现了OSI模型的地址形式,将网络路由器/路由器的寻址和路由器/主机之间的寻址区分了开来:
嗯,真正的Internet!
但这会出现一个问题,且往下看。
在IPv4网络中,如果一个节点换了IP地址,那基本上没人会猜测到两个IP地址之间的关联,比如我的笔记本电脑在家的时候,它的地址是192.168.1.101,当我把这台电脑带到了一个星巴克,它可能会被分配另一个地址172.16.2.33,没人知道这两个地址标识的是一台设备!
但是,这在IPv6中却有问题。
简单起见,我姑且把EUI-64先等同于MAC地址前导16个0组成,比如你的MAC地址是08:00:27:ff:26:e6,那么EUI-64生成的主机标识则是00:00:08:00:27:ff:26:e6,反正你就知道 能通过你的MAC唯一算出你的主机标识 就好了。
假设我家里的IPv6段是2001:111:222::/64,我的笔记本MAC地址是08:00:27:ff:26:e6,那么我的笔记本电脑的地址就是2001:111:222::00:00:08:00:27:ff:26:e6/64。
如果我把这台电脑拿到了星巴克,虽然水网段前缀发生了变化,但是由于MAC地址没有变化,因此 新IPv6地址的低64位是不变的!!
考虑到MAC地址的唯一性以及其和网卡厂商,售卖点等信息的关联性,如果你买这个电脑时恰好用了微信支付或者支付宝,你相当于实名买了这台电脑,那么只要你带着这台电脑并且使用,通过下面的线索就能定位你的隐私:
接入点搜集的邻居信息–>你的MAC地址–>支付记录以及MAC地址序列号的出货记录–>你是谁。
或者说,如果有人知道你的MAC地址,他就有可能知道你在什么时间去过什么地方…
所以说,某些时候,我们不希望使用EUI-64机制来生成主机标识信息,相反我们更希望使用随机一点的值。这就是所谓IPv6临时地址。
只要你启用了IPv6临时地址,那么当你收到路由器推送下来的前缀信息时,除了根据EUI-64机制生成一个常规的IPv6主机标识并和前缀拼形成一个IPv6地址之外,还会使用随机算法生成一个随机主机标识并和前缀拼接形成一个随机的临时IPv6地址。
一般而言,这个随机的地址生命周期比较短,比如说一天时间。它一般作为源地址和远程服务器通信,通过你的偏好配置,你可以在通信发生时让协议栈 优选临时地址作为源地址。 详见RFC3484:https://tools.ietf.org/html/rfc3484#section-5 中的rule 7。
关于临时地址的更多详情,参阅RFC是最标准的做法:
RFC4941-Privacy Extensions for Stateless Address Autoconfiguration in IPv6: https://tools.ietf.org/html/rfc4941
理解了以上IPv6的地址特性,现在进入重头戏。
这个小节将step-by-step演示IPv6的自动配置是如何玩的。为了简单起见,我的拓扑如下:
下面我要两台机器轮流做路由器和需要被自动配置的节点主机,大家可以同时领略两种不同操作系统配置的不同风味。
为了系统净化,我特意将两台设备都进行了重置。
首先你要学会netsh的使用,它非常简单和方便。值得一提的是,我没有专门学习netsh,我看完RFC后,配置Windows路由器时,一路help下来的,就成功了。让我感觉这非常人性化,和Cisco命令行很像,秒杀Linux的bash命令行或者iproute2。
我们首先用它来为 “本地连接 3” 这块网卡添加一个IPv6地址:
C:\>netsh int ipv6 add addr "本地连接 3" 2007:777:666::555/64
然后效果如下:
就简单add了一个地址,结果出现了这么多地址,应有尽有,我来分别简单解释一下:
由于我手工配置的地址是 2007:777:666::555/64 ,那么其网络前缀自然就是 2007:777:666::/64 ,这个也是该Windows机器作为路由器将来要自动配置给其网内主机的地址段。
说一下最后这个Anycast地址,为什么会生成这个地址,不是说路由器才有这个地址吗?
是的,当然是只有路由器才有,所以说我已经把Windows机器变成了一台路由器。如何变的呢?非常简单,执行下面的命令即可:
C:\>netsh int ipv6 set int "本地连接 3" forwarding=enable
是的,不要去regedit设置注册表了,就着干就行!netsh,不二选择!
为了让本链路上的主机将自己设置为默认网关,还需要下面的命令:
C:\>netsh int ipv6 set int "本地连接 3" advertisedefault=enable
好了,此时此刻,我们的Windows主机已经是一台路由器了,并且按照相关的RFC规范,路由器需要的功能已经应有尽有,最后还差一步,即如何让这个Windows路由器播报自己的前缀从而给发出路由器请求的节点推送网络前缀呢?
非常简单,只要下面的命令即可:
C:\>netsh int ipv6 set route 2007:777:666::555/64 "本地连接 3" publish=yes
再次强调,netsh一定要多玩!
至此,Windows上的配置已经基本完毕。最后看一下“本地连接 3”的总配置:
此时,我的Linux主机的enp0s9网卡要开启了!确认以下的配置是正确的:
# 接收路由器通告
net.ipv6.conf.enp0s9.accept_ra = 1
# 接受通告路由器作为默认网关
net.ipv6.conf.enp0s9.accept_ra_defrtr = 1
# 接受前缀通告,这是自动配置之关键
net.ipv6.conf.enp0s9.accept_ra_pinfo = 1
值得注意的是,上述的配置会在自动配置过程中 被改变 !
这主要是由/sbin/NetworkManager搞的。如果一个主机频繁收到不同的路由器通告,很容易出问题,所以最好是自己维护一个状态机,只有在确认当前的自动分配的地址已经超时而Invalid时,才会重新开启接收路由器通告。
NetworkManager在收到路由器通告后,会将相关网卡的accept_ra等配置参数给禁止掉,事情不容小觑。
不过我建议,初学者可以先干掉这个NetworkManager,不要被它诡异的行为影响。毕竟这主要是在演示原汁原味的纯正IPv6自动配置,而不是真的要用它。
好了,在我killall -9 NetworkManager之后,事情明朗了。
此外,在IPv6中,路由器和主机的角色在配置上是 互斥的 。这点尤其注意。比如,当你将节点配置成路由器时,它将不再接收其它的路由器通告(除非强制配置,但不建议)。
此时此刻,同时开启抓包!且看:
[root@localhost ~]# ifconfig enp0s9 up
等待几秒:
[root@localhost ~]# ip -6 add ls dev enp0s9
4: enp0s9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qlen 1000
# 自动配置的地址,由EUI-64生成并拼接主机标识
inet6 2007:777:666:0:a00:27ff:fed0:3f5c/64 scope global mngtmpaddr dynamic
# 注意这些时间!
valid_lft 2591963sec preferred_lft 604763sec
inet6 fe80::a00:27ff:fed0:3f5c/64 scope link
valid_lft forever preferred_lft forever
[root@localhost ~]#
[root@localhost ~]# ip -6 ro ls dev enp0s9
2007:777:666::/64 proto kernel metric 256 expires 2591890sec
fe80::/64 proto kernel metric 256
# 推下来的路由通告中的默认路由指向Windows的本地链路地址!
default via fe80::5db6:1b75:9d6f:bcbc proto ra metric 1024 expires 1690sec
自动配置完美完成,Linux主机的IPv6地址为:
2007:777:666:0:a00:27ff:fed0:3f5c/64
我们来看一下抓包文件,看看发生了什么:
注意那个Router lifetime字段。RFC4861的6.2.3节这样说:
6.2.3. Router Advertisement Message Content
…
A router might want to send Router Advertisements without advertising
itself as a default router. For instance, a router might advertise
prefixes for stateless address autoconfiguration while not wishing to
forward packets. Such a router sets the Router Lifetime field in
outgoing advertisements to zero.
我们实际抓包试试看,比如在disable和enable forwarding开关,仅仅保留publish开关时,看看Windows路由器发送的路由器通告有什么区别:
就这么点点不同而已,说明了Windows确实遵循了RFC规范。
自动配置完毕,来发个包试试看呗。
Linux主机不是接受Windows路由作为默认网关了吗:
::/0 fe80::5db6:1b75:9d6f:bcbc UGDAe 1024 0 0 enp0s9
下一跳是Windows路由器的本地链路地址。如果这是IPv4,在通信时,通信源地址根据和网关的最长前缀匹配原则,肯定会选择为enp0s9的本地链路地址,但是IPv6却不是这样,它严格按照RFC3484来选择源地址,我们来试试看:
[root@localhost ~]# telnet 3333:2222:1111::123 80
Trying 3333:2222:1111::123...
Rule 5: Prefer outgoing interface.
现在让我们来总结一下。
全程配置使用netsh这个非常好用的Windows工具。
接下来该对换角色了。
这次Linux作为路由器,而Windows作为主机。为此,我重置了两台机器的既有配置。切记,一定要关闭Windows的路由器转发以及路由器通告功能,它才能作为普通主机接收自动配置。
现在开始配置Linux路由器。这个很简单,打开forwarding即可:
[root@localhost ~]# sysctl -w net.ipv6.conf.all.forwarding=1
net.ipv6.conf.all.forwarding = 1
开启这个路由转发功能后,Anycast地址自然生成,这个和Windows一样,遵循RFC的规范行事。接下来配置路由器通告。
在Linux中,这个可以由radvd守护进程完成。如果有人非要抬杠说 既然路由器通告是IPv6自带的一部分,为何不在内核实现,而要专门由一个用户态守护进程支持,这个和DHCP不也一样么? 关于这点,我觉得, 本来就没有什么内核态,用户态之说 由于路由器通告配置比较复杂,拥有很多的参数,而内核态又不被建议被搞复杂,只能做在用户态咯,这都无所谓。如果非要抬杠,给我两天带薪休假时间,我给你做进内核去。
言归正传,在我们的CentOS上,radvd并不需要编译源码,它比较成熟,直接yum install即可:
yum install radvd
然后大致看一下manual:
RADVD(8) RADVD(8)
NAME
radvd - router advertisement daemon for IPv6
…
按照配置文件的建议,自行大约摸配置即可。或者说,你看一遍radvd.conf的manual就可以玩转了,非常之详细:
[root@localhost ~]# man radvd.conf
限于篇幅,我也不想复制粘贴,我给出一个我自己的一个配置文件,位于/etc/radvd.conf:
interface enp0s9
{
AdvSendAdvert on;
MinRtrAdvInterval 30;
MaxRtrAdvInterval 100;
prefix 2001:dddd:1:0::/64
{
AdvOnLink on;
AdvAutonomous on;
AdvRouterAddr off;
AdvValidLifetime 120; # 120秒过期
AdvPreferredLifetime 100; # 100秒后不再建议使用
};
route 2006:222:666::444/128 # 推送这个主机路由
{
};
};
下面开始配置过程,我感觉虽然配置上看起来比Windows配置简单,但是并没有Windows的直观。
先给enp0s9配置一个和radvd通告的前缀一致的IPv6地址:
[root@localhost ~]# ip -6 a add dev enp0s9 2001:dddd:1:0::123/64
好了,现在开启radvd服务(同时开启tcpdump/wireshark抓包):
[root@localhost ~]# service radvd start
Redirecting to /bin/systemctl start radvd.service
[root@localhost ~]#
OK,这时看看Windows主机接收得如何,show一下address:
完美!该有的地址都有了。再看看默认路由有没有推送下来,必须推下来了的:
对了,还有一条主机路由呢,我在radvd.conf里配置的那个:
2006:222:666::444/128 via $MEMeme
我们看看抓包,这一切背后发生了什么?看看Linux的路由器通告就好了:
一切全部浮到了水面上。IPv6的自动配置就是这么玩的。
这次,我竟然用Windows做路由器,拿Linux做靶子。也不奇怪,自从我第一眼看到netsh,我就喜欢它了,它竟然什么东西都能help出来,这一点让我回忆起了我熟悉的Cisco命令行。
我经常拿netsh和Linux iproute2对对比,我一直倾向于在心理上希望iproute2更胜一筹,但事实上,我觉得netsh玩的更爽!
所以,在我演示这个IPv6自动配置这么重要的特性的过程中,Windows的netsh让我感觉好舒服。
好玩吗?我觉得比较好玩,但是游戏结束了。
我的感觉,IPv6才是真正的互联互通网络协议,它真的是太方便了!真的是比IPv4方便多了。
即便是路由器上要手工配置的那个全局地址,那个被写在了DNS节点AAAA里面的地址,它也是有章可循的,它的128比特地址里通过一些比特就知道它所处的路由位置,这些就是天然聚类的收益!
不过不要指望去靠某个人手工维护这些。
周末或者下周,我想聊聊关于IPv6编程的一些问题,敬请期待!
这是一个寂静的雨后之夜,在今天早上,我发了一个朋友圈,关于一个知乎上的问题 “为什么东亚人活得这么累” 其中有一个回答,我是很赞同的,这个回答是这样的:
然后,这是我的评价:
我是同意这个地缘学解释的,这也是我的解释。
我总是拿着家里那个标注地形地貌的地球仪给疯子和小小看,“你们看,小时候老师给我们讲中国地大物博,资源丰富,其实现在看来,那是骗人的。看看美国,欧洲,中南美,大部分都是海拔很低,地形平坦,有丰富植被以及水域覆盖,而我们中国,这种地方仅限于从山东往南,经过浙江,沿海到广东一带,往西止于河南郑州,湖北武汉连线,东北资源可以处于西伯利亚和季风要冲,异常寒冷。这怎么跟欧美比。在地球仪上看,我们和非洲中南部非常类似。。。”
就是这么简单的道理,资源贫乏,但却令人遗憾地培育了水稻这种作物,因此便在资源匮乏地带养活了海量的人口,造成了一种现象,那就是“不管干什么事,拿人堆就行了”这种劳动密集型观念,而劳动密集观念其实是个“延长劳动时间”等价的,都是线性效应而不是指数效应。唉:-(
不用扯那些现代经济学术语装逼,再复杂的现象的本质往往道理都是最简单的。动不动就外汇储备什么,扯淡!中国古代人难道就不苦逼吗?不苦逼怎么会有“头悬梁锥子扎屁股,吃得苦中苦成为人上人,愚公移山,精卫填海,铁杵磨针,书中自有BMW”这种苦逼至上价值观?!
再看看欧洲古代中世纪,落后是落后,但人少,没人干活自然要想一些在中国看来是奇技淫巧的技巧,再往前就是罗马帝国后期了,那时有大量奴隶劳动力,也是拿人堆,但苦逼的是奴隶而不是普通老百姓啊。
欧洲文明中最苦逼和中国最像的就是罗马打赢迦太基后到屋大维内战结束那一段100多年的时间了,到很快结束了,毕竟高卢,北非,近东环地中海这片资源太丰富了!
又能怎么样呢?我一直以来都不喜欢知乎,在知乎里面学不到知识,到处都是装逼,抬杠,编那些啰里八嗦的故事,最烦的就是分割线,傻逼一样的存在。好的答案没几个,却让读者浪费了大量的时间,标榜高大上却处处都是垃圾!
反正都是一样。
浙江温州皮鞋湿,下雨进水不会胖!