了解IPv6以及ND协议
IPv6 从诞生到现在已经很多年了,国家也从政策层面开始推进 IPv6 部署,现在的大部分互联网服务都已经增加了 ipv6 支持。
但目前的很多开发者对 IPv6 完全没有概念,只对 IPv4 熟悉。本文以初学者的角度出发,结合 RFC 已发布标准试图以更利于理解的方式介绍 IPv6 和相关协议。
很多人不会去看 RFC ,一个是原文为英文,二是一些 RFC 内容较长,专业度较高,不够通俗。 但 rfc 是标准文档,涵盖了所有的细节,是 blog 文章无法取代的。如果需要详细的了解网络相关知识,rfc 文档是必不可少的。
推荐一个 rfc 中文网站,支持中英文对照查看 https://rfc2cn.com , 再也不用担心看不懂 rfc 文档啦,so easy。
IPv6 简述
IPv6 在rfc8200定义。
相对于 IPv4,IPv6 将 IP 地址大小从 32 bit 增加到 128 bit,可以认为 IPv6 地址永远用不完了; 精简了 IP 包头部;通过选项的方式来进行 IP 扩展;增加了流标记;更原生的方式支持了身份认证,数据加密。
IPv6 不再或减少使用了 NAT 技术,在 IPv6 的部署中,一般不再使用 NAT 了。
对于硬件转发来说,IPv6 转发效率相对会更高了。
IPv6 地址
rfc4291 规定了 IPv6 地址。
IPv6 有三类寻址:
- Unicast,单播: 单个接口的标识符。发送到单播地址的数据包被发送到由该地址标识的接口。
- Anycast,选播:一组接口(通常属于不同节点)的标识符。发送到选播地址的数据包被发送到该地址标识的接口之一(根据路由协议的距离度量,“最近的”接口)。
- Multicast,多播:一组接口(通常属于不同节点)的标识符。发送到多播地址的数据包被发送到该地址标识的所有接口。
相对于 IPv4 ,IPv6 使用多播代替了广播。
还有一个非常重要的点,IPv6 强制每个接口都要有 链路本地地址(link local) 地址,以fe08:
开头,就像 IPv4 的 169.254.0.0/16
地址。
现在知道为啥网卡上都有一个 IPv6 地址了吧。
IPv6 地址在文本上使用 16 进制编码,每 16bit 用“:”分割,一共有 8 节。
ABCD:EF01:2345:6789:ABCD:EF01:2345:6789
IPv6 地址比较长,部分情况还支持了缩略写法:
- 可以移除每一节中的前导 0,但每一节至少要有一个 0(数字).例如:
2001:DB8:0:0:8:800:200C:417A
- 对于多个连续节都是 0 的,可以省略,使用
::
替换,但这种替换只能出现一次:例如2001:DB8:0:0:8:800:200C:417A
->2001:DB8::8:800:200C:417A
,0:0:0:0:0:0:0:1
->::1
::1
是不是也很眼熟,就是 IPv6 的 loopback,相当于 IPv4 的 127.0.0.1
。
在 IPv4 到 IPv6 过渡期间,IPv4 地址还能使用 IPv6 表示。例如:0:0:0:0:0:0:13.1.68.3
,不过这种情况一般不使用。
IPv6 也有 CIDR 表示的格式,ipv6-address/prefix-length
,虽然可以用 CIDR 表示,但 IPv6 没有“子网掩码”这个概念。
地址类型:
Address type | Binary prefix | IPv6 notation |
---|---|---|
Unspecified 保留 | 00…0 (128 bits) | ::/128 |
Loopback 回环地址 | 00…1 (128 bits) | ::1/128 |
Multicast 多播地址 | 1111 1111 | FF00::/8 |
Link-Local Unicast 链路本地地址 | 1111 1110 10 | FE80::/10 |
Global Unicast 全球单播地址 | 其他剩余的 |
单播地址:
| n bits | m bits | 128-n-m bits |
+------------------------+-----------+----------------------------+
| global routing prefix | subnet ID | interface ID |
+------------------------+-----------+----------------------------+
除以二进制 000 开头的地址外(ipv4 in ipv6), 所有全局单播地址都有一个 64 位 interface ID 字段(此时 n+m=64)。
IPv6 单播地址中的接口标识符(interface ID)用于标识链路上的接口。 它们在子网前缀中必须是唯一的。建议不要将同一接口标识符分配给链路上的不同节点。 在更广泛的范围内,它们也可能是独一无二的。在某些情况下,接口的标识符将直接从该接口的链接层地址派生。 同一接口标识符可用于单个节点上的多个接口,只要它们连接到不同的子网。
也就是说,能够进行路由寻址的单播地址前缀固定为 64 bit,内网接口寻址固定为 64 bit。
举个例子:ISP 给你的 IPv6 地址应当是一个最小可路由到的 IPv6 CIDR, 并且格式应当为 abcd:ef01:2345:6789::/64
,像这样:
$ rdisc6 ppp-telecom -1
...
Prefix : 240e:398:e12:2b35::/64
...
从 ISP 路由通告中拿到的 prefix 为 240e:398:e12:2b35::/64
,ISP 控制 n+m=64 bit 的前缀 240e:398:e12:2b35
, 局域网控制分配 64 bit 的接口 ID。
局域网中的接口根据自己的物理地址(或其他)生成(重点关注 EUI-64 生成方式) 64 位的 interface id,组合起来就是接口的 IPv6 地址。
是不是很简单,根本不用 dhcp,只要保证一个内网中的物理地址唯一就可以完美“分配”IPv6 地址,这就是无状态配置的基础(SLAAC).
多播地址:
| 8 | 4 | 4 | 112 bits |
+------ -+----+----+---------------------------------------------+
|11111111|flgs|scop| group ID |
+--------+----+----+---------------------------------------------+
+-+-+-+-+
flgs is a set of 4 flags: |0|R|P|T|
+-+-+-+-+
flag 中第一位保留为 0
T=0 表示由 Internet 分配号码管理局(IANA)分配的永久分配(“已知”)多播地址;T=1 表示非永久分配(“瞬态”或“动态”分配)多播地址。
P 标志的定义和用法见RFC3306, R 标志的定义和用法见RFC3956。
scop 是一个 4 位多播作用域值,用于限制多播组的作用域。数值如下:
0 保留 1 接口本地作用域 2 链接本地作用域 3 保留 4 管理员本地作用域 5 站点本地作用域 6(未分配)7(未分配)8 组织本地作用域 9(未分配)A(未分配)B(未分配)C(未分配)D(未分配)E 全局作用域 F 保留
group ID, 标识给定范围内的永久或暂时多播组。RFC3306中提供了多播组 ID 字段结构的其他定义。
以 RDP 中的 Router Solicitation
包为例,目的地址就是本地多播地址 ff02::2
:
IP6 (flowlabel 0x13fc4, hlim 255, next-header ICMPv6 (58) payload length: 8) fe80::216:3eff:fe67:55c5 > ff02::2: [icmp6 sum ok] ICMP6, router solicitation, length 8
展开为FF02:0:0:0:0:0:0:2
,FF
为 11111111 的 hex 编码,0
表示 flags 为|0|0|0|0|
,2
表示 scope 为 Link-Local
,0:0:0:0:0:0:2
表示所有 IPv6 路由器组(这是预定义的 group ID)。
IPv6 头
固定头:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| Traffic Class | Flow Label |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Length | Next Header | Hop Limit |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Source Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Destination Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
name | descriptions |
---|---|
Version | 4 位互联网协议版本号=6 |
Traffic Class | 8 位流量等级字段 |
Flow Label | 20 位流量标签 |
Payload Length | 16 位无符号整数,IPv6 有效负载的长度,即该 IPv6 报头之后的数据包的剩余部分,以八位字节为单位。 |
Next Header | 8 位选择器。标识紧跟在 IPv6 标头之后的标头类型。 |
Hop Limit | 8 位无符号整数。转发数据包的每个节点递减 1。 |
Source Address | 数据包发起方的 128 位地址。 |
Destination Address | 数据包的预期收件人的 128 位地址 |
看起来比 IPv4 的 header 要简单许多,更多的扩展功能都在扩展头里面。对于路由转发来说,一般只需要解析固定头就可以判定需要转发到何处了。需要解包的字节更少了,效率也会更高。
IPv6 报头中 Next Header 遵循IANA-PN 中的类型。
IPv6 报头中的 20 位流标签字段由源用于标记要在网络中作为单个流处理的数据包序列。IPv6 流标签的当前定义可在RFC6437中找到。
IPv6 标头中的 8 位流量类别字段由网络用于流量管理。接收到的数据包或片段中的流量类位的值可能与数据包源发送的值不同。 RFC2474和 RFC3168中规定了区分服务和显式拥塞通知的当前使用流量类别字段。
扩展头:
在 IPv6 中,可选的 internet 层信息编码在单独的报头中,这些报头可以放在数据包中的 IPv6 报头和上层报头之间。 IPv6 数据包可携带零个、一个或多个扩展报头,每个扩展报头由前一报头的下一报头字段标识:
+---------------+------------------------
| IPv6 header | TCP header + data
| |
| Next Header = |
| TCP |
+---------------+------------------------
+---------------+----------------+------------------------
| IPv6 header | Routing header | TCP header + data
| | |
| Next Header = | Next Header = |
| Routing | TCP |
+---------------+----------------+------------------------
+---------------+----------------+-----------------+-----------------
| IPv6 header | Routing header | Fragment header | fragment of TCP
| | | | header + data
| Next Header = | Next Header = | Next Header = |
| Routing | Fragment | TCP |
+---------------+----------------+-----------------+-----------------
每个扩展头是 8 个八位字节的整数倍,以便为后续头保留 8 个八位字节的对齐。 每个扩展标头内的多个八位元字段在其自然边界上对齐,即,宽度为 n 个八位元的字段从标头开始以 n 个八位元的整数倍放置,n=1、2、4 或 8。
扩展头从 IPv6 header 中的 next header type 指定开始,可以链式设置。使用多个扩展标头时,需要按照建议的顺序排序。
IPv6 标头或任何扩展标头的 next header 值为 59 表示该标头后面没有任何内容。
以 ICMP6 包为例:
ethertype IPv6 (0x86dd), length 62: (flowlabel 0x13fc4, hlim 255, next-header ICMPv6 (58) payload length: 8) fe80::216:3eff:fe67:55c5 > ff02::2: [icmp6 sum ok] ICMP6, router solicitation, length 8
中 next-header 为 58 (ICMP6),表示该 IP 包 data 部分就是 icmp6 header 和 data,在 icmp6 header 中 next header type 应当为 59。
所有扩展标头,都需要使用以下格式:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Next Header | Hdr Ext Len | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +
| |
. .
. Header-Specific Data .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
IPv6 的完整实现包括以下扩展头的实现:
- Hop-by-Hop Options
- Routing
- Fragment
- Destination Options
- Authentication
- Encapsulating Security Payload
Hop-by-Hop-Options Header 用于携带可选信息,这些信息可由每个节点沿着数据包的传递路径进行检查和处理。
Routing Header 用于 IPv6 源列出一个或多个中间节点,这些节点将在到达数据包目的地的途中“访问”。此功能与 IPv4 的松散源代码和记录路由选项非常相似。
Fragment Header 向其目的地发送比路径 MTU 中适合的数据包大的数据包。通俗一点就是 IP 分片。
Destination Options Header 用于承载仅需要由数据包的目的地节点检查的可选信息。
与 IPv4 不同,IPv6 节点不需要强制执行最大数据包生存期。 这就是 IPv4“生存时间”字段在 IPv6 中重命名为“跃点限制”的原因。 实际上,很少有 IPv4 实现符合限制数据包生存期的要求,因此这在实践中不是一个变化。 任何依赖互联网层(IPv4 或 IPv6)来限制数据包生存期的上层协议都应该升级,以提供自己的机制来检测和丢弃过时的数据包。
NDP
了解了基本的 IPv6,来看看 IPv6 是如何部署的。
Neighbor Discovery for IP version 6 (IPv6) 由rfc4861 定义。
节点(主机和路由器)使用邻居发现来确定已知驻留在连接链路上的邻居的链路层地址,并快速清除无效的缓存值。 主机还使用邻居发现来查找愿意代表其转发数据包的邻居路由器。 最后,节点使用该协议主动跟踪哪些邻居是可访问的,哪些是不可访问的,并检测更改的链路层地址。 当路由器或到路由器的路径出现故障时,主机会主动搜索运行中的备用路由器。
可以看到,ND 协议类似 IPv4 中的 arp 协议。
该协议解决了与连接到同一链路的节点之间的交互相关的一组问题。它定义了解决以下每个问题的机制:
- 路由器发现:主机如何定位驻留在连接链路上的路由器。
- 前缀发现:主机如何发现一组地址前缀,这些地址前缀定义了连接的链接上的目的地。(节点使用前缀区分驻留在链路上的目的地和只能通过路由器到达的目的地。)
- 参数发现:节点如何学习链路参数(如链路 MTU)或 Internet 参数(如跳数限制值)以放入传出数据包中。
- 地址自动配置:介绍允许节点以无状态方式为接口配置地址所需的机制。[ADDRCONF]中指定了无状态地址自动配置。
- 地址解析:仅给定目的地的 IP 地址,节点如何确定链路上目的地(如邻居)的链路层地址。
- 下一跳确定:将 IP 目标地址映射到邻居的 IP 地址的算法,该邻居应将目标的通信发送到该地址。下一跳可以是路由器或目的地本身。
- 邻居不可达性检测:节点如何确定邻居不再可达。对于用作路由器的邻居,可以尝试备用默认路由器。对于路由器和主机,都可以再次执行地址解析。
- 重复地址检测:一个节点如何确定它希望使用的地址是否已经被另一个节点使用。
- 重定向:路由器如何通知主机更好的第一跳节点以到达特定目的地。
邻居发现定义了五种不同的 ICMP 数据包类型:一对路由器请求和路由器广告消息、一对邻居请求和邻居广告消息以及重定向消息。这些信息的目的如下:
- Router Solicitation:当接口启用时,主机可能会发送路由器请求,请求路由器立即生成路由器广告,而不是在下一个预定时间。
- Router Advertisement:路由器定期或响应路由器请求消息,通过各种链路和互联网参数来宣传其存在。路由器广告包含用于确定另一地址是否共享同一链路(链路上确定)和/或地址配置、建议的跃点限制值等的前缀。
- Neighbor Solicitation:由节点发送,以确定邻居的链路层地址,或验证是否仍然可以通过缓存的链路层地址访问邻居。邻居请求也用于重复地址检测。
- Neighbor Advertisement:对邻居请求消息的响应。节点还可以发送未经请求的邻居公告,以宣布链路层地址更改。
- Redirect:路由器用来通知主机一个目的地的更好的第一跳。
直接通过现实的案例来看,下面的数据包是 IPv6 从 ISP 获取 IPv6 配置的过程:
03:25:01.807801 IP6 (flowlabel 0x85abb, hlim 255, next-header ICMPv6 (58) payload length: 8) fe80::92e2:baf6:678b:91b4 > ff02::2: [icmp6 sum ok] ICMP6, router solicitation, length 8
03:25:01.813171 IP6 (class 0xe0, hlim 255, next-header ICMPv6 (58) payload length: 104) fe80::ce1a:faff:fee8:2a00 > fe80::92e2:baf6:678b:91b4: [icmp6 sum ok] ICMP6, router advertisement, length 104
hop limit 64, Flags [none], pref medium, router lifetime 1800s, reachable time 0ms, retrans timer 0ms
source link-address option (1), length 8 (1): cc:1a:fa:e8:2a:00
0x0000: cc1a fae8 2a00
mtu option (5), length 8 (1): 1492
0x0000: 0000 0000 05d4
rdnss option (25), length 40 (5): lifetime 4294967295s, addr: 240e:56:4000:8000::69 addr: 240e:56:4000::218
0x0000: 0000 ffff ffff 240e 0056 4000 8000 0000
0x0010: 0000 0000 0069 240e 0056 4000 0000 0000
0x0020: 0000 0000 0218
prefix info option (3), length 32 (4): 240e:398:e12:112e::/64, Flags [onlink, auto], valid time 2592000s, pref. time 604800s
0x0000: 40c0 0027 8d00 0009 3a80 0000 0000 240e
0x0010: 0398 0e12 112e 0000 0000 0000 0000
首先客户端向所有路由多播地址发送 router solicitation ,并携带自己的物理地址,如果有路由器收到,那也顺便就知道了客户端的物理地址了,可以直接回消息。
路由器收到路由请求后,会回复 router advertisement,包含了许多必要信息:
flags [none]
,表示 M 和 O flag 都没有设置,使用 SLAAC 不能使用 dhcpv6router lifetime 1800s
代表自己可以作为默认路由器,并且指明生存时间。reachable time 0ms
指示了假设的路由器可达时间,由邻居不可达性检测算法使用,0 表示未设置- 在 options 里面,通告了:
- source link-address option 通告路由器物理地址
- mtu option 1492 用于更新 mtu
- rdnss option 用于设置 ipv6 dns 服务器
- prefix info option 用于通告 prefix,prefix 中的
Flags [onlink, auto]
表示该前缀 scope link 可用,并且自治,可以将 prefix 用于 SLAAC。
客户端收到路由通告后,获取的这些信息,足够本地配置 IPv6 了。
收到 prefix 后,按照上面的 prefix + interface id 的方式组合,就是端口的 IPv6 地址,还包含了 mtu 和 dns,直接配置上就完成了,是不是非常的简单。相比于 IPv4 , 根本就不需要 dhcp 的参与,也不需要介于二层和三层的 arp 协议。这就是IPv6 Stateless Address Autoconfiguration (SLACC)
还有后续, 还应注意,在将地址分配给接口之前,必须执行重复地址检测,以防止多个节点同时使用同一地址。
在确定使用这个 IPv6 地址之前还要进行重复地址检测,重复地址检测使用 Neighbor Solicitation 和 Neighbor Advertisement 来完成。
IP6 (hlim 255, next-header ICMPv6 (58) payload length: 32) fe80::3015:60ff:fe28:956b > fe80::216:3eff:fe67:55c5: [icmp6 sum ok] ICMP6, neighbor solicitation, length 32, who has fe80::216:3eff:fe67:55c5
source link-address option (1), length 8 (1): 32:15:60:28:95:6b
通俗一点就是,问 “有没有人用这个地址啊,有人用就说一声,没人用我就用了啊”。重复地址检测是可以被关闭的。
客户端配置完,但是此时的路由器还不知道客户端使用的是哪个 IPv6 地址,需要由客户端发布邻居通告,告诉局域网内的节点自己的存在。
IP6 (flowlabel 0xfd344, hlim 255, next-header ICMPv6 (58) payload length: 24) fe80::3015:60ff:fe28:956b > ff02::1: [icmp6 sum ok] ICMP6, router advertisement, length 24
hop limit 0, Flags [other stateful], pref medium, router lifetime 1800s, reachable time 0ms, retrans timer 0ms
source link-address option (1), length 8 (1): 32:15:60:28:95:6b
此时邻居节点/路由器就知道这个地址以及物理地址了,可以对这个地址进行通信了。
dhcpv6
dhcpv6 由 rfc8415定义
虽然 SLAAC 简单方便,但是在企业管理场景中,网关没有各个节点的“状态”,如果需要在 IPv6 地址分配上做更多工作或其他需要 dhcp 的场景。 就需要启用 dhcpv6 服务器了。
dhcpv6 也可以和 SLAAC 协同工作,例如使用 SLAAC 下发 IP 地址,使用 dhcp 下发 dns ,ntp 等其他信息。
在 router advertisement 中,有两个 flag M 和 O,分别配置接口的 AdvManagedFlag 和 AdvOtherConfigFlag。
- M “托管地址配置”标志。设置后,表示地址可通过动态主机配置协议[DHCPv6]使用。
- O “其他配置”标志。设置时,表示通过 DHCPv6 可以获得其他配置信息。此类信息的示例为 DNS 相关信息或网络内其他服务器上的信息。
也就是节点收到路由通告后就知道是不是要开始 dhcp 请求了.
dhcpv6 还定义了 prefix delegation, 你可以将使用 dhcpv6 获取到的前缀进行子网化后再分给下级节点。
例如 可以将收到的 /48
前缀子网化为/64
前缀,并将一个/64
前缀分配给下级网络中的每个链路。(但是这个 prefix 不得超过 64)。
dhcp6 的内容相对过于复杂,不在这里说明。