2017 年 11 月 26 日,中共中央办公厅和国务院办公厅印发了《推荐互联网协议第六版(IPv6)规模部署行动计划》,并发出通知,要求各地区各部门结合实际认真贯彻落实。这条新闻传达了一个很重要的信息:这个是推进中国 IPv6 发展的战略总动员令。
本文将会从以下几个方面进一步介绍 IPv6,包括有:
1.IPv6 的基本概念
2.IPv6 在 Linux 操作系统下的实现
3.IPv6 的实验
4.IPv6 的过渡技术介绍
5.IPv6 在 Linux 平台下 socket 编程应该注意的问题
6.实现简易版 TGW 支持 IPv6 雏形 demo
值得说的是,目前我们接触得比较多的主流操作系统内核,已经很好地支持 IPv6 协议栈,例如:
Windows: windows 7、windows 8.x、windows 10,默认开启 IPv6
Linux: 内核 2.6.x、内核 3.x、内核 4.x 已经支持 IPv6(需要手动开启)
IOS:IOS9 开始已经支持 IPv6 Only,2016 年苹果已经强制要求 app 必须支持 IPv6
本文提到的 IPv6 节点,没有特殊说明,一般指的是纯 IPv6 节点(IPv6 Only),也就是只支持 IPv6 协议栈;IPv4 节点,是指纯 IPv4 的节点,也就是只支持 IPv4 协议栈;如果节点支持 IPv6 和 IPv4 双栈,会指明是双栈节点。
IPv6 的基本概念
众所周知,32 位的 IPv4 地址已经耗竭,IPv6 采用 128 位的地址长度拥有更大的地址空间。首先我们先来认识一下 IPv6 到底长成什么样子。
初识 IPv6
图 1 IPv6 数据报文
上图是我们最熟悉的 ping 的 IPv6 版本 ICMPv6。可以看到,IPv6 数据报文和 IPv4 有很大的差别:
数据链路层(L2)的 type 字段标识为 0x86dd,表示承载的上层协议是 IPv6
IPv4 对比:type 字段为 0x0800
IPv6 的头部字段,和 IPv4 差别巨大(可以猜测到,IPv6 和 IPv4 无法兼容)
IPv6 的报文头部格式如下:
图 2 IPv6 报文头部(该图片来自互联网)
IPv6 报文头部更精简了,字段更少了,对比起 IPv4,有以下几个地方值得注意:
IPv6 报文头部是定长(固定为 40 字节),IPv4 报文头部是变长的。这个意味着,写代码处理 IPv6 数据报文的效率会提高很多:)
IPv6 中 Hop Limit 字段含义类似 IPv4 的 TTL。
IPv6 中的 Traffic Class 字段含义类似 IPv4 中的 TOS(Type Of Service)。
IPv6 的报文头部取消了校验和字段。取消这个字段也是对 IPv4 协议的一个改进。当 IPv4 报文在网路间传输,每经过一个路由器转发就是修改 TTL 字段,就需要重新计算校验和,而由于数据链路层 L2 和传输层 L4 的校验已经足够强壮,因此 IPv6 取消这个字段会提高路由器的转发效率。值得一提的是,在 IPv6 协议下,传输层 L4 协议 UDP、TCP 是强制需要进行校验和的(IPv4 是可选的)。
IPv6 报文头部中的 Next Header 字段表示“承载上一层的协议类型”或者“扩展头部类型”。这里的含义与 IPv4 有很大的差别,需要加以解释:
当 IPv6 数据报文承载的是上层协议 ICMPv6、TCP、UDP 等的时候,Next Header 的值分别为 58、6、17,这个时候和 IPv4 报文头部中的 Protocol 字段很类似。
当不是以上 3 种协议类型的时候,IPv6 报文头部紧接的是扩展头部。扩展头部是 IPv6 引入的一个新的概念,每个 IPv6 的数据报文可以承载 0 个或多个扩展头部,扩展头部通过链表的形式组织起来。当 IPv6 数据报文承载着扩展头部的时候,Next Header 的数值为扩展头部的类型值。
为什么要引入扩展头部这个概念,这里也是 IPv6 对 IPv4 改进的一个方面,用扩展头部取代了 IPv4 的可选项信息,精简了 IPv6 的头部,增强了 IPv6 的扩展性。有同学会不会有疑问,IPv6 的分片数据报文怎么处理?其实就是使用了 IPv6 扩展头部。我们来抓一个 UDP 分片报文来看看。
图 3 IPv6 分片报文
当发送一个分片 IPv6 数据报文的时候,IPv6 使用的是扩展头部的形式组织各个分片的信息,如图 IPv6 报文头部 Next Header 字段值为 44 表示存在扩展头部,扩展头部是 IPv6 分片数据信息。
对比 IPv4,分片信息是记录在 IPv4 报文头部的分片字段中。
IPv6 的扩展头部类型有很多种,除了上述的分片头部,还有路由头部、逐跳可选头部等,具体的可以参考 RFC2460。
本章主要介绍了 IPv6 的一些很直观的认识,下面逐渐介绍 IPv6 上的基本知识和概念。
IPv6 的地址语法
一个 IPv6 的地址使用冒号十六进制表示方法:128 位的地址每 16 位分成一段,每个 16 位的段用十六进制表示并用冒号分隔开,例如:
一个普通公网 IPv6 地址:2001:0D12:0000:0000:02AA:0987:FE29:9871
IPv6 地址支持压缩前导零的表示方法,例如上面的地址可以压缩表示为:
2001:D12:0:0:2AA:987:FE29:9871
为了进一步精简 IPv6 地址,当冒号十六进制格式中出现连续几段数值 0 的位段时,这些段可以压缩为双冒号的表示,例如上面的地址还可以进一步精简表示为:
2001:D12::2AA:987:FE29:9871
又例如 IPv6 的地址 FF80:0:0:0:FF:3BA:891:67C2 可以进一步精简表示为:
FE80::FF:3BA:891:67C2
这里值得注意的是,双冒号只能出现一次。
IPv6 地址的号段划分和前缀表示法
IPv6 拥有 128 位巨大的地址空间,对于那么大的空间,也不是随意的划分,而是使用按照 bit 位进行号段划分(与鹅厂内部一些的 64 位 uin 改造放号的 zone 划分算法)。
IPv6 的地址结构如下图:
图 4 IPv6 地址结构
例如 RFC4291 中定义了 n=48, m=16,也就是子网和接口 ID 与各占 64 位
IPv6 支持子网前缀标识方法,类似于 IPv4 的无分类域间路由 CIDR 机制(注意:IPv6 没有子网掩码 mask 的概念)。使用“IPv6 地址/前缀长度”表示方法,例如:
2001:C3:0:2C6A::/64 表示一个子网
而 2001:C3:0:2C6A:C9B4:FF12:48BC:1A22/64 表示该子网下的一个节点地址。
可以看到,一个 IPv6 的地址有子网前缀+接口 ID 构成,子网前缀由地址分配和管理机构定义和分配,而接口 ID 可以由各操作系统实现生成,生成算法后面的章节会介绍。
IPv6 的地址类型
IPv6 地址分三种类型
1、单播,对应于 IPv4 的普通公网和私网地址
2、组播,对应于 IPv4 的组播(多播)地址
3、任播,IPv6 新增的地址概念类型
IPv6 没有广播地址,用组播地址实现广播的功能。实际上我们工作和生活最可能最多接触的就是单播地址,接下来本文重点会讲解单播地址的种类。组播和任播地址有兴趣的同学自行查阅相关 RFC 和文献。
IPv6 单播地址
注意,大家如果在网上搜索 IPv6 的地址,可能都是千篇一律的把所有“出现过”的单播地址介绍出来,其实有一些单播地址类型已经在相关的 RFC 中被废除或者不建议使用,而本节会指出这类地址。同时,在介绍单播地址的时候,尽量与 IPv4 中对应的或者相类似的概念做对比,加深理解。
IPv6 单播地址有以下几种:
1、全球单播地址
图 5 IPv6 全球单播地址结构
前缀 2000::/3,相当于 IPv4 的公网地址(IPv6 的诞生根本上就是为了解决 IPv4 公网地址耗尽的问题)。这种地址在全球的路由器间可以路由。
2、链路本地地址
图 6 链路本地地址结构
前缀 FE80::/10,顾名思义,此类地址用于同一链路上的节点间的通信,主要用于自动配置地址和邻居节点发现过程。Windows 和 Linux 支持或开启 IPv6 后,默认会给网卡接口自动配置一个链路本地地址。也就是说,一个接口一定有一个链路本地地址。如下图:
图 7 Linux 下查看链路本地地址
图 8 Windows 下查看链路本地地址
值得说的是,每个接口必须至少有一个链路本地地址;每个接口可以配置 1 个以上的单播地址,例如一个接口可以配置一个链路本地地址,同时也可以配置一个全球单播地址。
注意,很容易会把链路本地地址和 IPv4 的私网/内网地址对应起来,其实链路本地地址对应于 IPv4 的 APIPA 地址,也就是 169.254 开头的地址(典型场景就是 windows 开启自动获取地址而获取失败后自动分配一个 169.254 的地址)。而 IPv4 私网对应于 IPv6 的什么地址,后面会介绍。
特别地,在 IPv6 socket 编程中,可以使用链路本地地址编程通信,但是需要增加一些额外的参数(这是一个小坑),在后面介绍编程的章节会介绍。
3、唯一本地地址
图 9 唯一本地地址结构
前缀 FC00::/7,相当于 IPv4 的私网地址(10.0.0.0、172.16.0.0、192.168.0.0),在 RFC4193 中新定义的一种解决私网需求的单播地址类型,用来代替废弃使用的站点本地地址。
可能看到这里,有同学会跳出来说:IPv6 不是为了解决 IPv4 地址耗尽的问题吗,既然 IPv6 的地址空间那么大,可以为每一个网络节点分配公网 IPv6 的节点,那为什么 IPv6 还需要支持私网?这里需要谈谈对 IPv6 下私网支持的认识。
在 IPv4 中,利用 NAT 技术私网内的网络节点可以使用统一的公网出口访问互联网资源,大大节省了 IPv4 公网地址的消耗(IPv6 推进缓慢的原因之一)。另一方面,由于默认情况下私网内节点与外界通信的发起是单向的,网络访问仅仅能从私网内发起,外部发起的请求会被统一网关或者防火墙阻隔掉,这样的网络架构很好的保护了私网内的节点安全性和私密性。可以设想一下,如果鹅厂内部每台办公电脑都配置了 IPv6 的公网地址上网,是多么可怕的事情,每台办公电脑都会面临被黑客入侵的威胁(肉鸡真多)。
因此,在安全性和私密性要求下,IPv6 中同样需要支持私网,并且也需要支持 NAT。在 Linux 内核 3.7 版本开始加入对 IPv6 NAT 的支持,实现的方式和 IPv4 下的差别不大(Linux 内核代码中变量和函数的命名几乎就是 ctrl+c 和 ctrl+v 过来的-_-||)。
4、站点本地地址
前缀 FEC9::/48,以前是用来部署私网的,但 RFC3879 中已经不建议使用这类地址,建议使用唯一本地地址。大家知道有这么一回事就可以了。网上还有很多文章还提到这种地址,但是没有说明这种地址已经不再使用。
5、特殊地址:回环地址
0:0:0:0:0:0:0:1 或::1,等同于 IPv4 的 127.0.0.1
6、过渡地址:内嵌 IPv4 地址的 IPv6 地址
就是在 IPv6 的某一些十六进制段内嵌这 IPv4 的地址,例如 IPv6 地址中 64:ff9b::10.10.10.10,此 IPv6 地址最后 4 个字节内嵌一个 IPv4 的地址,这类地址主要用于 IPv6/IPv4 的过渡技术中。
一、IPv4 兼容地址
0:0:0:0:0:0:w.x.y.z 或::w.x.y.z(其中 w.x.y.z 是点分十进制的 IPv4 地址)。但在 RFC4291 中已经不推荐使用这类地址,大家知道有这么一回事就可以了。
二、过渡地址:IPv4 映射地址
0:0:0:0:0:FFFF:w.x.y.z 或::FFFF:w.x.y.z(其中 w.x.y.z 是点分十进制的 IPv4 地址),用于 IPv6 地址表示 IPv4 地址。主要用于某些场景下 IPv6 节点与 IPv4 节点通信,Linux 内核对这类地址很好地支持,在后面编程和内核分析的章节会分析使用过程。
三、过渡地址:特定过渡技术地址
6to4 地址、ISATAP 地址、Teredo 地址主要用于对应的过渡技术的地址,在后面介绍过渡技术的时候会介绍。
IPv6 接口 ID 生成算法
从前面的介绍中可以看出,IPv6 单播地址是由前缀(64 位)+接口 ID(64 位)组成。接口 ID 的生成算法主要有以下几种:
1、根据 RFC4291 定义,接口 ID 可以从 EUI-64 地址生成。
详细算法可以查看 regli 同学的 PPT 第 14 页。
2、为了可以具备某种程度的匿名信,接口 ID 可以使用一个随机分配的,windows 操作系统默认就是使用这种生成算法,Linux 下也是默认开启这个算法。
3、使用状态化的自动配置技术分配,例如 DHCPv6 分配。
4、手工配置。
IPv6 地址配置
前面对 IPv6 的地址、前缀、接口等等做了介绍,接下来就是要介绍一个接口如何配置 IPv6 地址。IPv6 一个比 IPv4 更厉害的方面,就是可以自动配置地址,甚至这个配置过程不需要 DHCPv6(在 IPv4 中是 DHCPv4)这样的地址配置协议。最典型的例子就是,只要开启了 IPv6 协议栈的操作系统,每个接口就能自动配置了链路本地地址,这个是和 IPv4 最重要的区别之一。
IPv6 的地址配置有以下几种:
1、只要开启了 IPv6 协议栈,接口自动分配链路本地地址。
2、无状态自动配置地址(RFC2462),后面会有实验演示。
3、有状态自动配置地址,例如 DHCPv6。
4、手动配置。
IPv6 的域名解析
由于 IPv6 的地址扩展为 128 位,比 IPv4 的更难书写和记忆,因此 IPv6 下的 DNS 变得尤为重要。IPv6 的的 DNS 资源记录类型为 AAAA(又称作 4A),用于解析指向 IPv6 地址的完全有效域名。下面是一个示例:
Hostipv6.example.wechat.com IN AAAA 2001:db8:1::1
IPv6 下的域名解析可以认为是 IPv4 的扩展,详细可以查看 RFC3596.
Linux 内核 IPv6 架构简析
本文后面主要的分析都是基于 Linux,会有涉及关于 Linux 内核对 IPv6 的实现。主要是因为,现在 IPv6 的参考资料不多,除了与 IPv6 相关的 RFC 之外,还有少数可以参阅的 IPv6 国外文献,而 Linux 内核一直都与跟随着 IPv6 的协议更新和变化,Linux 内核 IPv6 的实现是十分重要的参考材料之一。而且从事后台开发工作主要也是在 Linux 平台下,熟悉 Linux 下 IPv6 的实现也是为以后的工作做知识储备。
PS:客户端开发的同学可以参考各自平台的文档…
Linux 在很早之前就已经开始支持 IPv6,目前我们接触最多的 Linux 内核版本都很好地支持 IPv6,同时也是支持 IPv4/IPv6 双栈体系。在 Linux 操作系统中,IPv4 是默认必须开启,IPv6 是可选编译和配置开启。
例如在编译内核的时候,需要选择 IPv6 编译选项才支持 IPv6
图 10 Linux 内核编译支持 IPv6
当开启支持 IPv6 的 Linux 的内核网络双栈的结构,如下图:
图 11 Linux 内核双栈架构
Linux 内核中,IPv6 协议栈与 IPv4 协议栈并行关系。IPv6 和 IPv4 完全是两套不一样的代码实现。IPv6 完整的协议栈逻辑模块包括:
1、网络层 IPv6,核心逻辑:IPv6 路由子系统
2、传输层 TCP/UDP 实现:TCPv6、UDPv6
3、控制报文协议 ICMPv6,这里值得一提的是 ICMPv6 在 IPv6 协议中的地位十分重要。
ICMPv6 不仅提供了与 ICMPv4 相同的服务诊断功能,例如报告数据包的错误和提供简单的 echo 服务,ICMPv6 是 IPv6 中邻居发现协议的重要组成部分,用于管理链路上的点到点的通信。
4、邻居子系统的实现:邻居发现协议 NDP(对应于 IPv4 里面的 ARP 协议)
5、其他高级实现(IPv6 NAT、IPv6 隧道、iPv6 IPSec 等)
由于我们平时的开发工作在应用层,以上 1-4 是将会接触得最多。
IPv6 实验
本章我们通过实验,加深对 IPv6 的认识。这里的实验没有使用真实现网的 IPv6 接入点(目前国内绝大部分接入点都是教育网),而实验的目的主要是观察 IPv6 的数据包结构、IPv6 的路由配置等,所以决定自己通过搭建中间路由器、应用服务器的方式做实验,便于抓包和代码分析。
客户端:windows 7
路由器:中间路由器使用自己编译和搭建的 Linux 系统(内核 2.6.32.27)
应用服务器:Ubuntu16.04LTS 版本。
为什么要使用自己编译的 Linux 作为路由器?因为 IPv6 的实践类能参考的文献比较少,而 Linux 内核的 IPv6 模块是最重要的参考资源之一,在实践中遇到问题可以使用打 LOG 和分析代码的方法解决。
1、无状态自动配置地址实验
IPv6 地址的获取是最重要的环节之一。本实验使用开源的无状态自动配置服务 radvd 进行实验。
图 12 IPv6 无状态自动配置
图 13 IPv6 无状态自动配置报文分析
无状态自动配置过程:
1、由链路上的主机向链路发起“路由请求”报文,这个报文是以组播协议发送,寻找链路上最合适的路由器。
2、路由器收到请求会返回“路由通告”报文,报文里面带着本链路的地址前缀信息主机将接收到的前缀和自身的接口 ID,组成完整的新地址。
3、主机尝试使用新地址发起地址重复检测,检测链路上是否有其他主机也是这个地址,如果有,就停止使用该地址;如果没有,就启用这个新地址。
可以看到无状态自动配置过程十分简易(对比 DHCPv4 和 DHCPv6 来说),实际上,无状态自动配置可以单独组网使用,也可以配合有状态自动配置一般会配合使用,加强网络节点管理。涉及自动配置和地址检测等更多细节,可以查阅 RFC1971、RFC4861。
2、IPv6 静态路由配置实验
本次实验主要是了解 windows 和 linux 的静态路由配置。
图 14 IPv6 典型的网络拓扑
由于各自的网络前缀(网段)不一致,在不使用默认路由的情况下,我们尝试配置路由让客户端可以访问到服务器。
一、Windows 7 配置静态路由:
去往服务器的 2001:db8:5::/64 网段的路由
图 15 Windows 配置 IPv6 路由
二、路由器 1 配置
图 16 Linux 下配置 IPv6 路由
三、路由器 2 配置
图 17 Linux 下配置 IPv6 路由
四、服务器静态路由配置
图 18 服务器配置 IPv6 路由
五、结果
图 19 客户端访问服务器
客户端可以顺利 ping 通服务器。可以看到,IPv6 下的路由配置,无论是 windows 还是 linux,与 IPv4 的配置差别不大,熟悉 IPv4 各个平台路由配置的同学可以很快上手 IPv6 的路由配置。
评论