早在 20 世纪 90 年代初,人们就意识到 IPv4 地址可能耗尽的问题。于是人们就提出了一个新的 IP 地址版本来解决这个问题,以前在开发阶段这个新的 IP 地址版本被称为 IP 下一代版本(或者称为 IPng),而现在一般称为 IP 协议第六版(或称为 IPv6)。
发展一种新的标准需要时间逐渐部署,因此,在发展新标准的过程中还需要一种解决 IPv4 地址耗尽问题的短期方案。这种短期的解决方案就是网络地址转换(Network Address Translation,NAT),它允许多台主机共享一个或较少的公用 IP 地址。在 NAT 设备上,相对外部公共网络的内部网络使用私有 IP 地址。
NAT 技术在缓解 IPv4 地址耗尽问题方面显然非常成功,并在大多数网络设计中已经成为一个标准部分。因此,至今仍有很多人对发展 IP 协议新版本的必要性提出质疑。但是 NAT 技术的广泛使用把原来具有开放、透明、对等特点的 Internet 变成了看上去更像一个具有客户-服务器(Client-Server)结构的网络的巨大集合。而用户只在外围连接到 Internet 的边缘层,Internet 向他们提供服务。
日益重视使用 IPv6 协议的背后来自于两个基本的推动力:
- 使用诸如移动 IP 协议(Mobile IP)、服务质量保证、端到端的安全、网格计算以及点对点网络互连等核心概念的新型应用,NAT 技术遏制了这些领域的创新,因而摒弃 NAT 技术的唯一手段就是提供充足的并且易于使用的公共 IP 地址
- 拥有众多人口的国家快速的现代化发展,例如中国和印度
IPv6 协议使用 128 位的地址代替 32 位的 IPv4 地址,在可以预见的未来,这个地址数目将可以满足公共 IP 地址的需求。
IPv6 地址
IPv6 地址与 IPv4 地址的不同之处不仅仅在于它们的地址长度不同,在很多方面它们都有所不同。
地址表示法
如下展示了 IPv6 地址的格式:
通常使用点分十进制来表示 IPv4 地址,而 128 位的 IPv6 地址则被分隔成 8 个 16 位段,其中每个 16 位段书写为大小在 0x0000 - 0xFFFF 之间的十六进制的数字表示。并且每个 16 位段之间使用冒号 : 分开。
有两条规则可以简化 IPv6 地址的书写:
- 任何一个 16 位段中 起始的 0 不必写出来(注意末尾的 0 是不能忽略的),任何一个 16 位段如果少于 4 个十六进制的数字,就认为忽略书写的数字是起始的 0
- 任何由全 0 组成的一个或多个 16 位段的单个连续的字符串都可以用双冒号 :: 来表示。注意,仅仅是单个连续不间断的全 0 字符串分段部分能够用一个双冒号 :: 来表示。在一个 IPv6 地址中使用多于一个以上的双冒号会引起含混不清
不像 IPv4 协议的前缀(即地址的网络部分)可以通过点分十进制或十六进制地址掩码标识,或可以通过位计数(bitcount)来标识,IPv6 协议的前缀始终通过位计数的方式来标识。当需要书写一个 IPv6 地址的前缀时,也可以使用和 IPv4 地址一样的书写方式,将所有的主机位设置为 0,例如:
1 | 3ffe:1944:100:a::/64 |
一个全 0 的 IPv6 地址能够简单地写成一个双冒号:::。
IPv6 的地址类型
IPv6 地址存在以下 3 种类型:
- 单播(Unicast)
- 任意播(Anycast)
- 多播(Multicast)
IPv6 地址中没有广播地址,但是 IPv6 地址协议提供了一个包含 全部节点
的多播地址,用来实现与 IPv4 地址协议中广播地址同样的目的。
全球单播地址
单播地址用来表示单台设备的地址。一个全球单播地址是指这个单播地址是全球唯一的。IPv6 单播地址的通用格式如下所示,该格式在 RFC3587 中有详细的描述,并取代和简化了早期的格式版本。
除了极少数的例外,全球 IPv6 地址的接口 ID 字段都是 64 位长度,而子网 ID 字段都是 16 位长度。
标识 IPv6 的地址类型
IPv6 地址起始的一些二进制位指明了该地址的类型。下表列出了目前已经分配的前导位的组合:
|地址类型 | 高位数字(二进制) | 高位数字(十六进制)
|—|—|—|
| 未指定 | 00…0 | ::/128|
| 环回地址 | 00…1 |::1/128|
| 多播地址 | 11111111 | FF00::/8|
| 链路本地单播地址 | 1111111010 | FE80::/10|
| 地区本地单播地址 | 1111111011 | FEC0::/10|
| 全球单播地址 | 001 | 2xxx::/4 或者 3xxx::/4|
| 保留的类型 | 其他所有的|
本地单播地址
全球单播地址是全球范围使用的,也就是说,这样的地址是全球唯一的,并且能够在全球范围内被路由而无需进行更改。IPv6 也拥有链路本地单播地址(link-local unicast address),这种地址是使用范围限定在单条链路上的地址。它的唯一性仅仅限于所在的链路,因此相同的地址也可能存在与另一条链路上,因此这样的地址离开所在的链路是不可路由的。
链路本地单播地址在像邻居发现协议等功能中是很有用的,邻居发现协议只能在单条链路上进行通信。链路本地单播地址也允许链路上的设备直接创建 IPv6 地址和该链路上的其他设备进行通信,而不必给它们分配全球前缀,或者说无需知道所在链路的全球前缀。
除了链路本地单播地址外,IPv6 协议最初还定义了一个地区本地单播地址。地区本地单播地址是指仅仅在一个给定的地区区域内该地址是唯一的,在其它地区区域内的设备可以使用相同的地址。因此一个地区本地单播地址只能在分配该地址的那个地区区域内是可路由的。地区本地单播地址在功能上和 IPv4 中的私有 IP 地址有些类似。
对地区本地单播地址的使用目前仍然存在争议。一种明显的困难是就是这样一个事实:地区区域的定义是含糊的。
任意播地址
一个任意播地址(Anycast address)表示的更像是一种服务,而不是一台设备,并且相同的地址可以驻留在提供相同服务的一台或多台设备中。接收到包含该地址通告的路由器不会知道是由多台不同的设备通告给它的,相反,路由器会假定有多条路由到达相同的目的地,并会选择一条代价最低的路由。
使用任意播地址的好处是,路由器总是选择达到 最近的
或 代价最低的
服务器的路由。因此,提供一些通用服务的服务器能够通过一个大型的网络进行传播,并且流量可以由本地传送到最近的服务器,这样可以使网络中的流量模型变得更有效。而且如果其中一台服务器变得不可用时,路由器能够把路由指向下一台最近的服务器。
任意播地址是根据它们提供的服务功能而定义的,而不是根据他们的格式。而且理论上来说可能是任何范围内的任何一个 IPv6 单播地址。但是,在 RFC 2526 中定义了一个保留的任意播地址的格式。
多播地址
多播地址标识的不是一台设备,而是一组设备:一个多播组。发送给一个多播组的数据包可以由单台设备发起,因此一个多播数据包通常包括一个单播地址作为它的源地址,一个多播地址作为它的目的地址。在一个数据包中,多播地址从来不会作为源地址出现。
IPv6 协议并不像 IPv4 协议那样有一个保留的广播地址,而是有一个保留的包含所有节点的多播组。多点传送实际上是 IPv6 协议的一个基本操作,特别是对于即插即用特性的一些功能。
IPv6 多播地址的格式如下,多播地址起始的 8 位总是全 1,并且后跟的 4 位被指定作为标记位。这些标记位的前 3 位目前没有使用,全部设置为 0,第 4 位用来指出这个地址是一个永久的、公认的地址(设为 0)还是一个管理分配使用的暂时性的地址(设为 1)。接下来的 4 位表示该地址的范围。而最后 112 位用来作为组 ID,标识各个不同的多播组。目前的用法是设置前面 80 位为 0,而只使用后面的 32 位。
嵌入的 IPv4 地址
有几种转换技术,将 IPv4 地址的网络转换成 IPv6 地址的技术,或者另外一种让两者共存的技术:要求 IPv4 地址在 IPv6 地址环境中进行通信。这些不同的技术详细说明了 IPv4 地址如何嵌入到 IPv6 地址中的细节,以及如何实现在 128 位的 IPv6 地址中寻找 32 位的 IPv4 地址的技术。
IPv6 包头格式
IPv6 包头的格式如下:
- 版本(Version):长度为 4 位,用来指出 IP 协议的版本
- 流量类别(Traffic Class):长度为 8 位,相当于 IPv4 协议的 ToS 字段
- 流量签(Flow Label):长度为 20 位,该字段的设置目的是允许为特定的业务流打上标签。也就是说,同一条流的数据包不仅仅始发于相同的源和达到相同的目的地,而且在源和目的地都属于相同的应用。流可以用源地址、目的地址加上源端口和目的端口的组合来确定。但是为了识别端口,路由器必须能够看到 IP 报头的上层(TCP 或 UDP 报头),这就增加了转发处理的复杂性,并可能会影响路由器的性能。在发送一个数据包时,加上合适的流标签,路由器就能识别一个流,而不必进一步地查找数据包头部
- 有效载荷长度(Payload Length):长度为 16 位,用来指定数据包所封装的有效载荷的长度,以字节计数。IPv6 数据包头部长度总是固定的 40 字节,而且通过有效载荷长度就可以得到有效载荷的起始和结尾了
- 下一报头(Next Header):长度为 8 位,指出了跟随该 IPv6 数据包头部后面的报头。在 IPv6 协议中,跟随在该数据包头部后面的报头,可能是一个上层协议报头,也可能是一个扩展的头部,从下一报头这一命名就可以反映出它具有更广泛的功能范围
- 跳数限制(Hop Limit):长度为 8 位,这个字段和 IPv4 协议中的生存时间(TTL)字段在功能上是非常一致的
- 源地址(Souce Address):长度为 128 位,指定数据包的源 IPv6 地址
- 目的地址(Destination Address):长度为 128 位,指定数据包的目的 IPv6 地址
IPv6 包头中不包含校验和字段,这是因为现代传输介质可靠性全面提高,而且上层协议通常携带它们自己的错误校验和恢复机制,因此 IPv6 报头本身的校验和就没有太多价值。
IPv6 扩展报头
由于仅仅携带数据包转发时所必要的信息字段,固定长度的 IPv6 报头变得更加简洁和有效。当需要使用 IP 特性中的某个可选项,例如分段、源路由选择等,IPv6 协议提供了一项可选的功能:扩展报头。在需要这些功能时可以添加在报头之后。
因为这些扩展报头,IPv6 数据包可以在以下两个方面提高效率:
- 数据包仅仅需要传送各自数据包所需要的信息,不需要传送用不到的字段
- 可以通过定义新的扩展报头来增加新的可选功能
每一个扩展报头都像 IPv6 报头一样,有下一个报头字段。因此,每一个报头都会告知是哪一个报头跟在它的后面。每一个扩展报头的功能如下:
- 逐跳可选项:传送必须被转发路径中的每一个节点都检验处理的信息
- 路由选择:通过列出在到达目的地的路径中数据包所要经过的节点列表来提供源路由选择功能
- 分段:指在一个数据包被分段时用来为接收节点重组数据包提供的必要信息。需要注意,在 IPv6 中,只有发起该数据包的节点能够对数据包进行分段,而 IPv6 路由器对数据包不分段。因此发起该数据包的节点要么必须使用路径 MTU 发现(Path MTU Discovery,PMD)来得到该数据包到达目的地的路径上最小的 MTU 值,要么就从不发出大于 1280 字节的数据包。IPv6 协议规定运行 IPv6 的所有链路都必须能够支持最小 1280 字节大小的数据包
- 封装安全有效载荷(Encapsulating Security Payload,ESP):用于有效载荷的加密封装
- 认证报头(Authentication Header,AH):用于数据包必须在源节点和目的节点之间进行认证的情况
- 目的地可选项(Destination Options):用于传送仅仅被目的节点,或者可能是路由选择报头中列出的节点检验处理的信息
如果使用扩展报头的话,在 RFC 1883 中也规定了它们应该出现的顺序。
ICMPv6
和 IPv4 一样,IPv6 也需要一个控制协议来处理错误、交换信息等。IPv6 使用 ICMPv6 来实现这一功能。ICMPv6 在 RFC 2463 中规定。ICMPv6 定义的很多功能都和 IPv4 中的 ICMP 定义相同,但是也存在一些差异。
如下显示了 ICMPv6 报头格式,和 ICMP 一样,ICMPv6 也使用一组类型与代码值的组合来标识不同的 ICMPv6 消息。
ICMPv6 协议除了基本的错误和信息控制功能以外,还包括一些使用 ICMPv6 消息的机制,例如如何使用 ICMPv6 消息实现路径 MTU 发现机制。
邻居发现协议(NDP)
IPv6 协议除了增加地址空间外,一个最显著的特征就是它的即插即用特性。邻居发现协议(Neighbor Discovrey Protocol,NDP)就是使用以下功能来实现这些即插即用特性的协议:
- 路由器发现(Router Discovery):当一个节点连接到一个 IPv6 的链路上时,它能够发现本地的路由器,而不必借助动态主机配置协议(DHCP)
- 前缀发现(Prefix Discovery):当一个节点连接到一个 IPv6 的链路上时,它能够发现分配给该链路的前缀
- 参数发现(Parameter Discovery):节点能够发现它所相连的链路的参数,例如链路 MTU 等
- 地址自动配置(Address Autoconfiguration):节点能够确定它的完整地址,同样也不需要借助 DHCP 协议
- 地址解析(Address Resolution):节点不需要利用地址解析协议就能够发现所连链路上其他节点的链路层地址
- 下一跳确定(Next-Hop Determination):一条链路上的节点能够确定到达目的节点的下一跳链路层节点,或者是本地的目的节点,或者是能到达目的节点的路由器
- 邻居不可达检测(Neighbor Unreachability Detection):节点能够检测到链路上的邻居何时不再可达,在这里邻居可能是其他主机也可能是一台路由器
- 地址冲突检测(Duplicate Address Detection):节点能够检测到它所使用的地址是否已经被所在链路上的其他节点占用
- 重定向(Redirect):对于非连接的目的节点,路由器能够通过重定向消息通知主机存在比它自己更好的下一跳路由。该重定向功能在 IPv4 协议中是 ICMP 基本功能的一部分,但是在 IPv6 协议里被重新定义为邻居发现协议的一部分
NDP 消息通常应该在链路本地的范围内发送,因此封装 NDP 消息的数据包也始终使用 IPv6 链路本地地址,或链路本地范围内的多播地址。为了增加更进一层的安全性,承载所有 NDP 消息的 IPv6 数据包的跳数限制为 255。如果所收到的数据包有跳数限制的值小于 255 的,那么就说明这个数据包最少已经经过了一台路由器,因而该数据包将被丢弃。这种做法可以阻止 NDP 受到来自不与本地链路相连的源节点的攻击或欺骗。
NDP 消息
NDP 在 RFC 2461 中定义,为了完成某些功能,它使用 ICMPv6 协议来交换一些必要的信息。在 RFC2461 中详细说明了以下 5 个新的 ICMPv6 消息:
- 路由器通告(Router Advertisement,RA)消息:由路由器发起,用来通告这些路由器的存在和链路细节的参数。这些消息周期性地发送,也用于答复路由器请求消息
- 路由器请求(Router Solicitation,RS)消息:由主机发起,用来请求路由器发送一个 RA
- 邻居请求(Neighbor Solicitation,NS)消息:由节点主机发起,用来请求另一台主机的链路层地址,也用来实现诸如地址冲突检测和邻居不可达检测的功能
- 邻居通告(Neighbor Advertisement,NA)消息:节点发送一个邻居通告消息来响应邻居请求消息。如果一个节点改变了链路层地址,那么它能够发送一个未请求的邻居通告消息来通告这个新地址
- 重定向(Redirect)消息:重定向消息的使用和 IPv4 协议中的 ICMP 用法是相同的,只不过从基本的 ICMPv6 协议中抽取了一部分到 NDP 中
所有以上 5 种类型的消息中的可选字段,无论包含什么信息,都是由一个或多个类型/长度/数值(TLV)这 3 个参数组合构成的。8 位的类型字段指定在数值字段中携带的信息的类型,8 位的长度字段指定该数值字段的长度大小(单位为字节),以及一个长度可变的数值字段组成。
路由器发现
路由器通过在相连的链路上周期性地发送路由器通告消息,表明它的存在,并通过路由器通告消息通告所配置的所有参数。链路上的主机可以收到路由器通告消息,因而可以学到有关链路的必要信息。
在 Cisco 路由器上,只要使用命令 ipv6 unicast-routing
使 IPv6 有效,Cisco 路由器就可以在以太网和 FDDI 接口上自动地发送路由器通告消息。缺省的间隔值是 200s。路由器通告消息中的各个字段也可以通过相应地命令进行设置。
刚刚连接到某个链路接口的主机需要等待一个路由器通告消息,以便能够发现链路上的路由器和学习到链路的参数。但是等待 200s 的时间显得过长,因此,当链路上的一台主机开始激活时,就会发送一个路由器请求消息去请求立即得到一个路由器通告消息。这个路由器请求消息的源地址可以是未指定的地址(::),也可以是该主机的链路本地 IPv6 地址。它的目的地址则始终是所有路由器的多播地址。
当一台路由器收到一条路由器请求消息时,它就会发送一条路由器通告消息作为响应。如果触发这个路由器通告消息的路由器请求消息的源地址是一台主机的链路本地地址,那么这条路由器通告消息就会使用它的链路本地地址以单播方式传送给该主机。如果路由器请求消息的源地址未指定,那么它所请求的路由器通告消息就会以多播方式被传送给所有的节点地址。
当一台主机收到一条路由器通告消息时,它将会这台路由器添加到它的缺省路由器列表中(除非这台路由器通告消息指定它的路由器生存时间为 0 而不能作为缺省路由器使用)。
地址自动配置
当一台 IPv6 的主机第一次连接到链路上时,它能够自我配置其自己的接口地址。这个过程的第一步是确定地址的 64 位接口 ID 部分。对于广播型接口,使用一种称为 MAC-to-EUI64 转换法的机制。该机制在 MAC 地址中间插入一个保留的 16 位数值 0xFFFE,并把它的全局/本地(Universal/Local,U/L)位翻转设置为 1,这样就把它转化为一个 64 位的接口 ID。
链路本地前缀是一个保留的、公认的数值 0xFE80::/10,使它转换为完整的 64 位前缀,再加上转换导出的接口 ID,这样就构成了一个完整的 IPv6 地址,可以用来和同一链路上其他设备进行通信。
如下是来自我的 Mac 主机的以太网接口 en7 的配置,它的 IPv6 地址可以不用借助任何其他设备的帮助就可以导出自己的链路本地地址:
1 | ▶ ifconfig en7 |
如果一台主机仅仅需要和所在链路上的设备通信,那么它自动配置的链路本地地址就已经足够了。但是如果主机需要和链路以外的设备进行通信,那么它就需要一个更大范围的地址:通常是一个全球 IPv6 地址。这可以通过两种途径获取该地址:有状态或无状态的地址自动配置。
如果一台主机使用有状态地址自动配置,它将会借助 DHCPv6 服务器来获取必要的地址信息。它要么根据预先的配置去查找 DHCPv6 服务器,要么由收到的可能设置了 M 标记的路由器通告消息来告诉它使用的 DHCPv6 服务器。
对于无状态的自动配置,主机从它所收到的路由器通告消息中获取一个或多个路有前缀,然后加上它先前确定的接口 ID,这样就得到了一个全球唯一的 IPv6 地址。
地址冲突检测
虽然利用 MAC 地址转换导出一个接口 ID 的方法,在大多数情况下通常可以保证在任何范围下得到一个唯一的地址。但是不管一台设备是如何获取一个地址的,在使用这个地址之前都必须进行地址冲突检测。这里有一个例外,就是任意播地址,定义的任意播地址可以出现在不止一台的设备上。
已经获得一个新地址的节点会把这个新的地址归类为临时状态的地址,在地址冲突检测操作没有完成并确认链路上没有其他节点使用这个地址之前,该地址不能使用。这个节点会发送一个把目标地址设置为该地址的邻居请求消息来验证,邻居请求消息的源地址是未指定的地址,目的地址是被请求节点的多播地址。这样做的原因是,如果一个节点自动配置了多个接口地址,它所有地址的最后 24 位都应该是相同的,因此一个带有被请求节点多播地址的邻居请求消息应该匹配它所有的接口地址。而且,使用被请求节点的多播地址可以确保在两个节点试图同时对同一个地址执行地址冲突检测操作时,他们可以互相检测到。
如果一个节点收到一个邻居请求消息,并且它的目标地址与该节点所分配的其中一个地址匹配,它就会发送一个目标地址和目的地址都为试探地址的邻居通告消息。发起这个邻居请求消息的节点就会知道这个试探地址是冲突的,并且不能使用。
邻居地址解析
当一个 IPv4 节点希望和本地链路上的另一个 IPv4 节点通信时,它必须首先发现目的节点的链路层地址,然后这个地址被作为数据帧(承载 IP 数据包)的目的地址。IPv4 使用 ARP 协议实现这一过程,而 IPv6 则使用 NDP。
当一个节点检验目的 IPv6 地址的前缀后,就可以判断出目的节点是它所在的本地链路上的邻居,还是一个本地链路以外的节点并因此需要通过缺省路由器才能到达。如果是后一种情况,路由器通告消息中已经包含了缺省路由器的链路层地址。但是如果目的节点位于本地链路上,那么节点就会首先查找邻居缓存看一下是否已经学到该地址。
如果一个地址不在邻居缓存中,该地址也会输入到 IPv6 邻居缓存中但被标记为不完全的(Incomplete),这表示正在进行该地址的地址解析处理。主机会发送一个邻居请求消息到与目标节点相关的被请求节点的多播地址。邻居请求消息中会包含源链路层可选项,这样被请求的节点就可以连接到请求节点的链路层地址,并知道作为答复的邻居通告消息应该发送到哪一个节点。
如果被请求的节点存在并且邻居请求消息是有效的,那么它将回复一个邻居通告消息。邻居通告消息中的目标地址字段设置为对应的邻居请求消息中的目标地址。收到邻居通告消息后,请求节点将目标节点的链路层地址添加到邻居缓存条目中,并且将该条目从不完全状态更改成可达状态。
在 Cisco 路由器中,可以通过 show ipv6 neighbors 来查看邻居缓存。
私有地址
无状态地址自动配置在涉及到以下的一些情况时会产生一种安全问题:即使一台设备从一个子网移到另一个子网,或者从一个主网络转移到另一个主网络,它的接口 ID 也始终保持不变。如果它的接口 ID 保持不变,那么它就能够被追踪。
在 RFC 3041 中,通过定义 IPv6 私有地址来解决该安全问题。私有地址是通过利用伪随机数字算法生成新的接口 ID。接口 ID 大约一天变化一次,也会在节点获取一个新的 IPv6 地址时改变。
但是,一个经常变化的地址对于可达性而言是不实际的。希望与你通信的节点必须通过一个或一些静态地址了解你所在的位置。因此,标准的无状态配置的 IPv6 地址保留了你的 公共 地址,任何一个需要向你发送数据包的节点都使用这个地址作为目的地址。但当你返回数据包时,你使用的确是私有地址。
邻居不可到达性的检测
一个邻居缓存条目可以具有以下 5 种状态:
- Incomplete:该状态说明正在进行邻居地址的解析处理
- Reachable:该状态说明目前地址已经被确认认为是可达的。目前被确认 的意思是指在路由器通告消息中 Reachable Time 字段指定的时间内,已经收到了某些可达性信息
- Stale:该状态说明自从收到最新的有关到达目的地可达性的确认后,已经经历了 Reachable Time 所指定的时间
- Probe:该状态是指每经过 Restransmit Time 时间,节点就会通过向目的节点发送邻居请求消息来搜索可达性的确认
- Delay:当一个数据包发送到一个处于 Stale 状态的目的节点时,这个地址就会进入到该状态。如果在该状态等待了 5s,并在这段时间内还没有收到有关可达性的确认,那么该状态就会转换到 Probe 状态。这个状态是在节点发出搜索的邻居请求消息前,给上层协议一个确认可达性的机会的优化措施。
对于邻居可达性的确认,一般可通过以下两种方法确认:
- 来自上层协议的提示
- 通过请求一个路由器通告消息或邻居通告消息得到有关对目的地址搜索的响应
邻居可达性的检测不仅仅要确认从邻居到本地节点一方的可达性,而且还要确认从本地节点到邻居方向的可达性,确保双向的可达性。因此,一个未经请求的路由器通告消息或邻居通告消息,不能改变邻居缓存条目的状态到 Reachable,在这种情况下,只有收到一个响应请求消息的路由器通告消息或邻居通告消息之后,才能确认双向的可达性。