大家好,又见面了,我是你们的朋友全栈君。
正则安安每晚每隔三小时必然哭闹,我索性也就不睡了,反正也睡不好,起来泡茶,喝酒,作文。
浙江温州皮鞋?湿,下雨☔️进水不会胖!
杭州,外面依然是寒雨夜,屋里也没开空调,我穿个夏天的短袖,旁边放一杯热茶,喝完了还有昨晚实在喝不下去的汉拿山烧酒,再喝完还有上周菜市场买的米酒…作此文一篇。
在我们check IPv6的基本特征列表时,总是可以看到IPv6对Anycast的支持。说实话,对于很多人而言,这是个比较陌生的概念,对于希望看看Anycast到底是什么样子的人而言,甚至在网上很难搜到关于 如何配置Anycast 的资源。这是比较令人遗憾的。
抛开概念,那么本文尝试从不同的角度来针对Anycast探究一番。
IPv4年代的Anycast
说起Anycast,并不是在IPv6标准中突然出现的概念,一个概念怎么可能突然出现?不可能的。
早在很久很久以前,业界就针对IPv4提出了Anycast的说法,只不过相对而言,IPv6在操作上将其标准化了而已,如果说IPv4年代的Anycast标准只是 建议 ,那么IPv6的Anycast就是规定了些许 MUST , MAY。
建议阅读: RFC1546-Host Anycasting Service:https://tools.ietf.org/html/rfc1546 RFC3513:IPv6 Addressing Architecture :https://tools.ietf.org/html/rfc3513#section-2.6
那么,到底如何理解Anycast?
本质上, Anycast就是将同一个IP地址配置在不同的主机网卡上,然后利用各种选路机制欺骗源主机的一种通信方式。
什么?同一个IP地址配置在不同的主机上,这不是地址冲突了吗?我们记得在初学网络基础的时候,教程上就讲过 IP地址不能冲突! 现在为什么IP冲突变成了一种通信方式了呢?真是只许州官放火,不许百姓点灯啊!
其实不然,我倒是觉得Anycast是内功深到一定程度,自然而然的一个想法。我们简单地从路由说起。
我们知道,IP地址存在的目的就是为了指挥路由器选路,最终将数据包路由到目的地,那么IP地址冲突的结果是什么?
IP地址冲突不是问题,路由冲突才是!! IP地址冲突只有导致路由器的路由冲突(be confusing)的时候才有问题。
比如路由器R上配置的下面的两条路由:
代码语言:javascript复制1.1.1.1 nexthop 2.2.2.2 dev e2
1.1.1.1 nexthop 3.3.3.3 dev e3
请问一个去往目的地1.1.1.1的数据包到达路由器R之后到底是从e2走呢,还是从e3走呢?这就是问题。但是同样的路由,加一个约束就不会有问题:
代码语言:javascript复制1.1.1.1 nexthop 2.2.2.2 dev e2 metric 100
1.1.1.1 nexthop 3.3.3.3 dev e3 metric 10
路由器会毫不犹豫地将去往1.1.1.1的数据包从e3发出!
对于上述的两条路由,你说是IP地址冲突吗?不!并不是。
互联网本身就是一个互相网状连通的连通图,到达同一个目的地的路径不止一条,对于路由器R而言,它只管选路,逐跳转发数据包,它并不关注1.1.1.1这个目标地址到底在哪里。
好了,现在我们知道只要路由不冲突,就没有问题,数据包总是可以特定的路径,逐跳被转发,最终到达目的地。现在,我们看一下这条路是如何形成的,或者说路由器R是怎么知道到达1.1.1.1有两条路可走的。
由于这只是一篇散文,并不是技术文档,所以我并不想引入BGP,AS这些概念,姑且把全球的互联网看作是一张如下图所示的连通图:
然后在这张图标识的网络开始工作时,各个节点开始彼此交换自己携带的子网信息,我们可以将其理解为 路由通告。显然路由器节点P向上游路由器R0通告了子网1.1.1.0/24,意思就是说, “嗨,R0邻居兄弟,如果有到达1.1.1.0/24的包,请交给我” ,同样的信息,路由器P也会告诉它的所有其它邻居,然后路由器R0回复路由器P,放心吧,我知道了,我已经配置上了1.1.1.0/24 nexthop 4.4.4.4 dev e0,并且我也已经将这个消息转给了我的所有邻居,放心吧,它们如果有到达你那里1.1.1.0/24的包,会先交给我的… …
就这样,子网信息,路由信息在整张网上彼此交换,传播,最终在每一台路由器上形成了稳定的路由表:
请注意,A会在两个接口同时收到关于1.1.1.0/24的通告,这并不会造成路由冲突:
- 常规路由协议以及SPF算法均有避环的措施;
- 有意为之的环被视为备份路由,由Metric做优先级取舍或者由ECMP管理。
如果说在这张网中有A和C需要访问1.1.1.1,那么很显然,路径如下:
非常好,道路是通达的,但是并不完美!Why?
这会造成路由器R1和路由器P之间的链路异常拥堵,为什么R0不能分担一部分流量呢?
嗯,我能想到的,TCP/IP标准化协会的那帮人难道能想不到?这就是ECMP的由来。再后来,干脆来个SDN全局统一分发好了…但是这和Anycast没有半毛钱的关系,所以就此打住,我们来看看另外一种引出Anycast的解法。
试想, 如果B节点也通告1.1.1.0/24的路由会怎样?
按照前面的路子,我们知道,B和P均通告相同的路由,这会造成图上所有路由器均会收到来自B和P关于1.1.1.0/24的通告,按照上面的两个基本原则:
- 常规路由协议以及SPF算法均有避环的措施;
- 有意为之的环被视为备份路由,由Metric做优先级取舍或者由ECMP管理。
你猜怎么着?最终成了下面的样子了:
是不是很像在世界互联网上部署了一个天然的负载均衡设施啊!是的!这就是所有的 IP地址冲突导致的结果! 这就是 Anycast 。
其精髓就是: 在路由器看来,它们并不知道不同指向的下一跳最终将数据包导向不同的目的地,它们只是认为这只是通往同一个目的地的不同路径罢了! 简单点说, Anycast之所以得以部署和实现,就是利用了IP协议逐跳寻址的特性!
事实上,Anycast的结果是,相同的IP地址位于不同的主机,因此,它的弊端也是显而易见的。
由于 逐跳的路由收敛 和 端到端的五元组连接 之间并没有同步,因此Anycast并不适合基于端到端连接的TCP应用。
TCP并没有广域范围的连接迁移机制,因此如果路由重新收敛,将会导致连接断开!比如上述的例子,如果A到B之间的线路拥塞或者说断开,那么路由会重新收敛到A-R-R1-P这条路径,A和B的1.1.1.0/24子网的主机之间的TCP连接将会断开,这并不是人们所期望的。
因此,上述描述中的Anycast只适合于一来一回两个包的oneshot式的交互通信,比如DNS!
是的,DNS就是这么部署的,比如我们经常使用的Google DNS 8.8.8.8,它实际上就是Anycast部署在世界不同地方的多台主机,地址全部都是8.8.8.8!
请使劲阅读:https://developers.google.com/speed/public-dns/faq
然后去深撸这个站点,溯源:https://bgp.he.net
至此,我觉得关于Anycast的概念,已经大致陈述清楚了。
下面是IPv6的世界。我们跟踪问题本身以及其解法,跟着RFC来梳理IPv6 Anycast的来龙去脉,其实一切都很清晰。
IPv6的Anycast
重看Anycast在IPv4上的问题,我们知道, 把同一个IP地址配置在不同的主机上,这确实是不妥的,比如占据互联网流量头把交椅的TCP应用就不适合, 既然无法让主子心安理得的承认,那索性在Anycast标准化中就不要这么做就是了。
但是,只要Anycast不是部署在端节点,而是部署在路径节点,比如路由器上,那就是妥妥的。逐跳寻址原则最终导致Anycast部署在路由器上之后,会自然而然地实现ECMP,即多条路径分担同样的端到端通信。
在继续下去之前,这里先说一个观点。那就是 端到端通信多路分担这种机制对TCP是不好的!
又TMD的是TCP,是的,这里,我觉得这不是逐跳寻址的问题,这根本就是TCP的缺陷!缺陷!缺陷! TCP要求在协议层面而不是应用层面按序到达,这就要求所有的字节最好是一路上顺序地排队前进,而多路径会影响顺序同步性,导致乱序。TCP的字节按序到达这个约束会恶化流量高峰期的链路拥塞。 由此而得到的另一个Trick就是Flowlet!它大大增加了端到端拥塞控制的复杂度。 然而我们看看QUIC,它就并没有要求严格的字节按序到达,而是基于窗口的按序到达,这就使得QUIC可以大大利用多路径分担带来的收益。 其实对于TCP而言,不光是链路最好不要多路径,甚至在路由器,交换机这种中间节点,多CPU,多处理卡也不能负载均衡分担同一条TCP流的不同数据包! 有了TCP,几乎所有的负载均衡都得按照流的粒度进行。 TCP真是太恶心了!
好了,我们先不管TCP了,任它腐烂吧!
IPv6对Anycast进行了标准化,首先在RFC3513中,它对Anycast提出了两点约束:
o An anycast address must not be used as the source address of an IPv6 packet. o An anycast address must not be assigned to an IPv6 host, that is, it may be assigned to an IPv6 router only.
有了这两点约束,我们可以知道: 在IPv6中,Anycast不是用来通信的,而是用来寻址的。
紧接着,RFC3513要求 所有的路由器的所有接口 都必须配置一个 必选的Anycast地址:
2.6.1 Required Anycast Address The Subnet-Router anycast address is predefined. Its format is as follows: | n bits | 128-n bits | ±———————————————–±————— | subnet prefix | 00000000000000 | ±———————————————–±————— The “subnet prefix” in an anycast address is the prefix which identifies a specific link. This anycast address is syntactically the same as a unicast address for an interface on the link with the interface identifier set to zero. Packets sent to the Subnet-Router anycast address will be delivered to one router on the subnet. All routers are required to support the Subnet-Router anycast addresses for the subnets to which they have interfaces.
比方说,路由R有两个接口,分别配置了两个IP地址:
代码语言:javascript复制e0: 240e:909:2001::4e3/64
e1: 240e:101:4004::111/64
那么根据RFC的要求,这个路由器上将会生成下面的Anycast地址:
代码语言:javascript复制e0 Anycast: 240e:909:2001::/64
e1 Anycast: 240e:101:4004::/64
为了使得这些Anycast能被访问到,需要添加两条本地主机路由:
代码语言:javascript复制Local 240e:909:2001::/128 dev loopback
Local 240e:101:4004::/128 dev loopback
这里需要解释一点,刚才不是说IPv6的Anycast不是用来通信的吗?那 为什么还要被访问呢?
因为需要邻居解析(IPv6 Ndp)。如果有谁把这个地址设置为下一跳了,那么需要解析这个地址,这就是 被访问!
纳尼?Anycast作为下一跳?
是的,这就是IPv6 Anycast的核心用法,它不是用来标识主机服务让你的应用程序通信,它是用来寻址的:
- Anycast地址被设置为下一跳
- Anycast地址被设置进IPv6路由扩展头以支持源路由
来吧,我们还是举例子的好。
我的局域网为了备份,希望部署两台或者多台路由器做热备,姑且就先两台吧:
假设两台路由器都能接外网,如果跑IPv4协议,那么自然而然的想法就是安装keepalived跑VRRP,这种成熟的东西,想必都可以瞬间完成配置。
但是,我们知道,这两台路由器只有一台是工作状态,另外一台处于backup standby,是不是感觉浪费了资源?如果你想让它们一起工作,那就要:
- 为它们配置不同的IP地址,gw1,gw2;
- 内部局域网一半一半分别配置两个不同的gateway,即gw1,gw2。
万万不能配置成相同的IPv4地址的,因为这种地址冲突会导致交换机的转发表以及ARP表的混乱。当然,对于我个人而言,我是有办法将其配置成相同的IP地址又不会confuse各种表的,但是,配置太复杂了(涉及iptables,ebtables,arptables,arp,iproute2,STP等等)。正如IPv4的Anycast一样,没有什么是IPv4配置不出来的,只是这些大部分都是奇技淫巧般的Trick!玩物丧志!
我们看一下用IPv6会怎样。
不用干别的,如果路由器的实现遵循RFC标准,那么你只需要配置两台路由器不同的同网段地址即可,如下:
代码语言:javascript复制路由器1 e0: 240e:110:1001::fbb2:0a1e:0e59:15eb/64 [低64bit为EUI-64映射而来]
路由器2 e0: 240e:110:1001::eb7c:b2da:7088:3c38/64 [低64bit为EUI-64映射而来]
然后所有主机的默认网关设置成其Anycast地址即可:
代码语言:javascript复制::/0 nexthop 240e:110:1001::
就这么简单!Why?
因为路由器1和路由器2在配置好内网e0的地址后,根据RFC3513,它们会自动生成Anycast地址以及Anycast地址的主机路由以被内网主机邻居解析。
RFC要求各层寻址Anycast地址时以 本层次的路由距离 来度量,那么对于二层链路,其距离默认就是物理距离,光速不变,距离等效于时间,因此会等效为 谁先回复我的邻居请求,谁就是我要寻址的Anycast节点!
是不是天然的负载均衡了呢?炫酷吧!
现在有个不可回避的问题需要解决,那就是 访问互联网服务的正向包和返回包路径不对称问题。
比如,h1主机访问服务器S走的R1作为默认网关,然而S返回h1时,却从R2返回,此时R2解析h1的地址,h1收到R2的解析请求后,会不会更新自己关于Anycast邻居的信息呢?如果是的话,那势必会造成邻居表信息的剧烈抖动啊!
答案是 并不会! Why?
因为IPv6在解析邻居时,ICMPv6协议头里会写清楚下面的信息:
- 自己是不是路由器
- 邻居信息需不需要覆盖
这一点和IPv4的ARP不同,ARP是双向更新的,在回复自己的MAC地址时,同时也更新了自己的ARP表,但在IPv6中,两者分开了:
- 请求你的MAC信息
- 请更新你自己的邻居信息
R2发送给h1的邻居请求,只是请求h1的MAC地址而已,并没有说要h1更新其邻居信息,所以万事大吉:
配置和实现
现在该看看实现了。
很少有资料讲 如何在Linux上配置IPv6的Anycast 的,这一次可能我又占了坑。
其实很简单,只要开启IPv6的转发即可:
代码语言:javascript复制sysctl -w net.ipv6.conf.all.forwarding=1
这个时候,你就会在/proc/net/anycast6看到内容:
代码语言:javascript复制[root@localhost src]# cat /proc/net/anycast6
3 enp0s8 fe800000000000000000000000000000 1
4 enp0s9 fe800000000000000000000000000000 1
4 enp0s9 240e0918800300000000000000000000 1
我在enp0s9上配置了如下的IPv6地址:
代码语言:javascript复制[root@localhost src]# ip -6 addr ls dev enp0s9
4: enp0s9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qlen 1000
inet6 240e:918:8003::3f5/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::eb7c:b2da:7088:3c38/64 scope link noprefixroute
valid_lft forever preferred_lft forever
所以说,什么都不用干,Linux内核自动帮我生成了其对应的Anycast地址,对应RFC3513的2.6.1 Required Anycast Address格式:240e:918:8003::
按照上面一个小节最后的例子,我们知道,这个 240e:918:8003:: 是可以被邻居发现而解析的,而我们知道,IPv6的邻居发现使用的是组播地址,其组播构成规则详见: RFC3513-2.7 Multicast Addresses: https://tools.ietf.org/html/rfc3513#section-2.7
对应组播地址: FF02::1:FF00:0000/104 Solicited-Node Address [RFC3513] -RFC4291对其进行了增强更新
Solicited-Node Address: FF02:0:0:0:0:1:FFXX:XXXX Solicited-node multicast address are computed as a function of a node’s unicast and anycast addresses. A solicited-node multicast address is formed by taking the low-order 24 bits of an address (unicast or anycast) and appending those bits to the prefix FF02:0:0:0:0:1:FF00::/104 resulting in a multicast address in the range FF02:0:0:0:0:1:FF00:0000 to FF02:0:0:0:0:1:FFFF:FFFF
我们针对Linux的如上配置确认一下:
代码语言:javascript复制[root@localhost src]# cat /proc/net/igmp6
1 lo ff020000000000000000000000000001 1 0000000C 0
1 lo ff010000000000000000000000000001 1 00000008 0
...
# 下面这个便是!
4 enp0s9 ff0200000000000000000001ff000000 2 00000004 0
...
将Anycast地址作为默认网关发送数据,最终邻居解析的时候,只要发送到组播地址 ff02::1:FF00:: 就可以解析出该网段上的Anycast地址的MAC地址信息,然后 取第一个到达的作为邻居 即可!
上面关于组播的设置,请看 addrconf_join_anycast 函数:
代码语言:javascript复制static void addrconf_join_anycast(struct inet6_ifaddr *ifp)
{
struct in6_addr addr;
if (ifp->prefix_len >= 127) /* RFC 6164 */
return;
ipv6_addr_prefix(&addr, &ifp->addr, ifp->prefix_len);
if (ipv6_addr_any(&addr))
return;
ipv6_dev_ac_inc(ifp->idev->dev, &addr);
}
其中,ipv6_dev_ac_inc 值得观摩!
配置也配好了,那么我们找两台机器练一练手吧。
这次我部署的另外一个机器是Windows 7系统,顺便玩一下netsh。这台Win7系统机器和我们的Linux Rh7.2直连,拓扑我就不画了,非常简单。我只是把Win7上的地址配置发布出来。
很简单,Win7上配置一个 240e:918:8003::/64* 同网段的IPv6地址:
这个时候,将Win7的默认网关设置成 240e:918:8003::* 这个Linux上使能的Anycast地址,看看如何通信。
按照惯例,ping一下这个 240e:918:8003::* 地址:
不通!
在Linux上抓包,发现是有回复ICMPv6 Echo Reply的,只是说回复的源IP地址不是Win7期望的Anycast地址,而是Linux上enp0s9网卡的地址,这正是印证了 An anycast address must not be used as the source address of an IPv6 packet. 这句话!
我很好奇Linux内核是怎么做到 不让Anycast地址作为源地址的 ,ping不行,TCP的Telnet也不行…其实看一下代码就完全明白了。
先看下Telnet为什么完全就没有SYN-ACK回复:
再看看为什么ping回复的时候用的不是Anycast地址,而是选择了网卡上配置的地址:
这个关于源地址选择的细节,详见RFC3484以及我的前一篇闲谈: Default Address Selection for Internet Protocol version 6 (IPv6) :https://www.ietf.org/rfc/rfc3484.txt 闲谈IPv6-源IP地址的选择(RFC3484读后感) :https://blog.csdn.net/dog250/article/details/87815123
脉络理清了之后,我们来反一下,让Win7主机配置Anycast。
我特意没有按照RFC的规定,去配置了一个非标准的Required Anycast Address,且看:
配置方法如下:
我并没有让低n-bit为全0,竟然成功了,这说明Win7并没有严格按照RFC的规范行事,它完全是手动的。
那么好,我在Linux上去ping这个Win7的Anycast地址:
得到了Win7的回复,然而源地址不是Win7的Anycast地址,却是Win7的物理网卡 本地连接 3 上配置的IPv6地址。这依然印证了 An anycast address must not be used as the source address of an IPv6 packet. 这句话。
只是说,Win7对Anycast地址 并没有严格遵循subnet后面的低bits均为0的约束 ,即Win7没有实现 严格的Required Anycast Address!
不管怎么样,痛则不通,通则不痛,后面也没啥可玩的了。
总结
IPv6的Anycast:
- 以往IPv4的规则在IPv6 RFC约束下依然适用
- 独添了subnet-Anycast,见上文
因为我们可以将Anycast总结为:
- 广义的Global Anycast-作用于IPv4/IPv6
- 狭义的IPv6 subnet Anycast-作用于IPv6
此外,RFC2526又规定了 保留的Anycast地址 用于不同的目的: RFC2526-Reserved IPv6 Subnet Anycast Addresses :https://tools.ietf.org/html/rfc2526 本文不涉及RFC2526的内容,但是提醒注意,仅此而已。
阅读笔记
为了写这篇文章,我深夜快速浏览了RFC标准,因为我要在这些个标准之上才能胡扯形而上吧。
这两个截图体现了IPv6 Anycast的作用,它到底用在哪?我想我在上文呢,已经阐述清楚了。
后记
为Anycast添加一条host路由,让anycast地址可以被设置成网关。
昨晚睡的有点早,这周末又要去公司附近拉一车行李,有点忙。白天基本没空,所以就以安安吃奶闹铃为由,作文一篇。
浙江温州皮鞋湿,下雨进水不会胖。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/142198.html原文链接:https://javaforall.cn