本文想聊一下IPv6的移动性支持。
IPv4的移动支持非常垃圾,非常复杂,不断修订也没出个所以然来。这不禁让人想看看IPv6的方案, 天然支持移动IP!
要理解IPv6如何天然支持移动IP,有两个前置知识点。这两个前置知识点一旦理解,移动IP就是自然而然的事情了。
这两个前置知识点就是关于两类IPv6扩展头的理解。
<>IPv6路由扩展头
这个和IPv4的源路由选项类似,但是更加强大。
IPv6的路由扩展头是作为固定长度IPv6协议头的一个next header存在的,其实也就是 IPv6协议的上层协议
,你可以理解为它和TCP的地位等同。既然如此,如果想要途径路由器处理路由扩展头,那么就必须让IPv6协议看起来这个报文是发给它自己的,也就是说,目标IPv6地址必须是它自己,这样它才能
本地接收 该报文,进而去处理后面的next header。
数据就是发给你的,你能不处理吗?什么?你没有注册路由扩展头的处理函数?好吧,那你就是没有按照标准完备实现IPv6!
这一点和IPv4的选项完全不同。IPv4选项就是在IP层处理的,所以不需要目标IP地址是它自己。也正因为如此,IPv4从来不保证中间节点会不会处理IP选项。
IP4可以选择性的忽略或者拒绝源路由选项的处理,毕竟它只是一个选项而已,然而IPv6不了,它是在网络层逐跳接力处理路由扩展头的,IPv6固定的头部以及靠next
header链式组织的扩展头使得它必须逐跳三层接力。之所以这么做是合适的,这也得益于IPv6取消了IP校验,这节省了大笔的计算开销,事物都是内在关联的。
我们下面用一幅图展示一下IPv6的路由扩展头是如何工作的:
非常有意思。
我们过一会儿再看如何用它来支持IPv6终端在通信过程中的移动的,现在看另外一个扩展头选项。
<>IPv6地址扩展头
这个选项比路由扩展头要简单得多。它事实上只是 携带一个IPv6地址给接收端 而已。它完成的逻辑是:
对于发送端:
* 定义地址A和地址B:
地址A:正在通信中使用的源地址,即家乡地址,也是旧的地址;
地址B:即将变成的新的源地址,即转交地址,由新的网络分配。
* 将地址B替换IPv6头中源地址字段,地址A保存在地址扩展头选项里;
对于接收端:
* 解出地址扩展头里的地址A。
* 用地址A替换IPv6报文头里的源地址字段。
图示如下:
好了,OK,我们现在可以看看如何利用上面两个超级简单超级帅的扩展头让通信节点肆意移动了。考虑下面的场景:
移动节点与远端服务器D建立了一个TCP连接!然而N移动了,更换了IP地址!
我们假设移动节点N使用源IP地址S0与D进行通信,在通信期间,N跑远了,失去了和原来基站的联系,到达一个新的地方,获取了新的地址S1,此时如何让既有的和D建立的TCP连接不断开呢?
非常简单!
首先,N需要将自己IP地址更改这件事告诉D,这就是用上述地址扩展头的作用。注意,当N发送这个数据包的时候,在TCP层看来,依然使用源地址S0,只不过在IP层,IPv6地址扩展头处理的时候,按照上述的步骤将源地址进行了替换,这一点对于上层是透明的。
这个数据包到达接收端,地址扩展头的接收处理流程将IPv6头中的源地址又替换回了S0,同时保存了S1,作为备用。在TCP接收端看来,它并不知道下面发生了这些替换,在它看来,数据就是S0发来的。
接下来,D需要发送返回包了,在D的TCP层的五元组看来,它需要发送给S0,也确实是用S0计算了伪头校验。接下来数据发送到IP层的时候,经过路由扩展头的处理,将S0作为只有唯一途经点的这个序列封装到路由扩展头,目标地址被替换成了S1。
当数据包到达S1的时候,事实上它已经到达了节点N,处理路由扩展头的时候,用S0替换IPv6头中的目标地址字段,交给上层处理。
是不是很简单呢?
但是这对IPv6的移动IP,才仅仅是个开始!
行文至此,我敢肯定移动IPv6的底层原理我已经说明白了,但是更为复杂的是如何部署这一切。几个问题必须要得到解决:
* 如何配置自己的家乡地址?
* 如何获取新的转交地址?
* 服务器如何确定一个携带地址扩展头的数据包是真实可信的?
* 安全性如何解决?
* …
正确的流程应该是:
【
强调一点,在IPv6的世界,即使N已经离开了“它的家乡”,它也依然可以轻松联系到它的家乡代理,因为在IPv6世界,所有节点都是完全对等互联互通的,不再需要打洞,不再有NAT的概念…
】
这一切又会涉及到方方面面复杂的配置,但并不属于本文的范围,因为说太复杂了就不好玩了,本文讲的就是移动IPv6技术的精髓,最简单的基本原理。
所以,我避开了RFC6275,避开了一大堆的ICMP消息…
但是,RFC6275是必读的:
Mobility Support in IPv6: https://tools.ietf.org/html/rfc6275
<https://tools.ietf.org/html/rfc6275>
如果你想获得观感,我推荐一篇文章:
Mobile IPv6 with Linux :https://www.linuxjournal.com/magazine/mobile-ipv6-linux
<https://www.linuxjournal.com/magazine/mobile-ipv6-linux>
如果你想亲自试一试,那么,下载mip6d是个不错的建议:
[root@localhost ~]# yum install mip6d
然后就是花半个小时时间,去看一下其manual咯。
当然,本文也没有涉及Home Agent,仿佛不需要任何外部设备的帮助,通信双方就可以自行处理移动性一般。确实如此!
我不喜欢各种Agent的介入,我也不喜欢支持移动IP的各种隧道,我认为它们复杂化了问题。也许,移动IP本来就是一个错误,包括移动IPv6!为什么不让会话层去解决连接问题呢?为什么TCP就一定要代入一个三层的伪头来进行校验呢?
以上的吐槽全部基于一个假设,那就是IP地址标识的Internet节点是静态的,IP路由若想正确工作,它就必须假设节点的IP地址是固定的。但是这种假设本身正确吗?
IPv6旨在 “连接一切!” 并且在技术上它确实做到了,这个连接一切绝不仅仅是一句类似于腾讯这种互联网公司喊出的商业宣传口号,而是名至实归的。
连接一切的IPv6当然包括连接不断移动的节点咯。是不是Intenet的根基-IP路由机制也要来个翻天覆地的变化呢?
谁知道呢?
涉及到实现,我这里不想多扯。还是以Linux为例,我这里给出xfrm框架封装扩展头的代码.
* 封装地址扩展头: static int mip6_destopt_output(struct xfrm_state *x, struct sk_buff
*skb) { struct ipv6hdr *iph; struct ipv6_destopt_hdr *dstopt; struct
ipv6_destopt_hao*hao; u8 nexthdr; int len; skb_push(skb, -skb_network_offset(skb
)); iph = ipv6_hdr(skb); nexthdr = *skb_mac_header(skb); *skb_mac_header(skb) =
IPPROTO_DSTOPTS; dstopt = (struct ipv6_destopt_hdr *)skb_transport_header(skb);
dstopt->nexthdr = nexthdr; hao = mip6_padn((char *)(dstopt + 1), calc_padlen(
sizeof(*dstopt), 6)); hao->type = IPV6_TLV_HAO; BUILD_BUG_ON(sizeof(*hao) != 18)
; hao->length = sizeof(*hao) - 2; len = ((char *)hao - (char *)dstopt) + sizeof(
*hao); memcpy(&hao->addr, &iph->saddr, sizeof(hao->addr)); spin_lock_bh(&x->lock
); memcpy(&iph->saddr, x->coaddr, sizeof(iph->saddr)); spin_unlock_bh(&x->lock);
WARN_ON(len != x->props.header_len); dstopt->hdrlen = (x->props.header_len >> 3)
- 1; return 0; }
* 解析地址扩展头: static int ipv6_destopt_rcv(struct sk_buff *skb) { struct
inet6_skb_parm*opt = IP6CB(skb); __u16 dstbuf; struct dst_entry *dst = skb_dst(
skb); if (!pskb_may_pull(skb, skb_transport_offset(skb) + 8) || !pskb_may_pull(
skb, (skb_transport_offset(skb) + ((skb_transport_header(skb)[1] + 1) << 3)))) {
IP6_INC_STATS_BH(dev_net(dst->dev), ip6_dst_idev(dst), IPSTATS_MIB_INHDRERRORS);
kfree_skb(skb); return -1; } opt->lastopt = opt->dst1 = skb_network_header_len(
skb); dstbuf = opt->dst1; if (ip6_parse_tlv(tlvprocdestopt_lst, skb)) { skb->
transport_header+= (skb_transport_header(skb)[1] + 1) << 3; opt = IP6CB(skb);
opt->nhoff = dstbuf; return 1; } IP6_INC_STATS_BH(dev_net(dst->dev),
ip6_dst_idev(dst), IPSTATS_MIB_INHDRERRORS); return -1; }
* 封装第二类路由扩展头: static int mip6_rthdr_output(struct xfrm_state *x, struct
sk_buff*skb) { struct ipv6hdr *iph; struct rt2_hdr *rt2; u8 nexthdr; skb_push(
skb, -skb_network_offset(skb)); iph = ipv6_hdr(skb); nexthdr = *skb_mac_header(
skb); *skb_mac_header(skb) = IPPROTO_ROUTING; rt2 = (struct rt2_hdr *)
skb_transport_header(skb); rt2->rt_hdr.nexthdr = nexthdr; rt2->rt_hdr.hdrlen = (
x->props.header_len >> 3) - 1; rt2->rt_hdr.type = IPV6_SRCRT_TYPE_2; rt2->rt_hdr
.segments_left = 1; memset(&rt2->reserved, 0, sizeof(rt2->reserved)); WARN_ON(
rt2->rt_hdr.hdrlen != 2); memcpy(&rt2->addr, &iph->daddr, sizeof(rt2->addr));
spin_lock_bh(&x->lock); memcpy(&iph->daddr, x->coaddr, sizeof(iph->daddr));
spin_unlock_bh(&x->lock); return 0; }
* 解析第二类路由扩展头: // 太长了,粘贴没有意义,自行查阅Linux内核函数: static int ipv6_rthdr_rcv(struct
sk_buff*skb) { ... }
浙江温州皮鞋湿,下雨进水不会胖!
热门工具 换一换