IPv6

IPv6

路由器的 ipv6 真是一团乱麻。ipv4 直接就是 DHCP / arp 协议。但是给局域网设备分配 ipv6 地址涉及一堆乱七八糟的:IPV6 prefix, RA, DHCPv6... 头疼的要死。整理篇文档系统学习一下。

IPV6 基础知识

ipv6不需要传统的子网掩码。在IPv4中,每个IP地址必须要搭配一个子网掩码才能使用,IPv6也使用了子网的概念,但子网ID是直接嵌入到地址中的。

rfc2374 规定在IPv6地址中,前48位表示网络前缀,紧接着的16位是子网ID,最后64位是接口标识,即接口ID或设备ID。如果需要的话,预留给设备ID的位可以借用来表示额外的子网掩码,但通常是不需要的,因为16位子网和64位设备ID可以提供多达65536个子网。

IPv6网络中没有子网掩码的概念,也没有网络号与主机号的概念。取而代之的是“前缀长度”和“接口ID”。前缀长度就可以当作子网掩码来理解。接口ID可以当作主机号来理解。比如,地址 2001:1234:2234:abcd::1/64 就表示前缀长度为64位,剩下的是接口ID。

IPV6 用 ND (Neighbor Discovery) 协议代替了 ARP, ND 协议的主要功能:

  • Address Autoconfiguration: perform stateless configuration of addresses for an interface;
  • Address Resolution: Mapping from IP address to link-layer address;
  • Neighbor Unreachability Detection (NUD): determine that a neighbor is no longer reachable on the link;
  • Duplicate Address Detection (DAD): nodes can check whether an address is already in use;

URL hostname 部分的 IPV6 地址需要用 "[]" 括起来,

OpenWrt IPv6 分配方式

OpenWrt 默认使用 odhcpd 提供 DHCPV6和RA服务。其 luci 对应的配置文件是 /etc/config/dhcp。(odhcpd也承担DHCP功能,但其默认配置交给dnsmasq负责DHCPV4)

ISP 一般直接下发整个 /64 段的 IPv6 地址到路由器(pppoe)。

路由器发出 RA ( Router Advertisement )给LAN里设备分配IPv6。除了下发前缀以外,局域网里设备会将 IPv6 的默认网关,指向 RA 的源 IP 地址,也就是 Router 的 link-local 地址。

所以如果在主路由上开启 RA,内网主机的默认网关就会指向主路由;
如果在旁路由上开启 RA,又不知道运营商动态下发的前缀;

DHCPv6 不支持配置子网掩码、路由、默认网关,必须依赖 RA。(所以DHCPV6只能下发 IP 和 DNS 配置。)

IPV6 地址动态分配方式

DHCPv6

对应于DHCP,IPv6协议中也有DHCPv6(RFC8415/RFC3315)。但是就像IPv6完全不兼容IPv4一样,DHCPv6除了工作模式与DHCP类似以外,与DHCP也不兼容,它的协议内容也进行了重新定义。

因为IPv6中没有广播的概念,所以不像DHCP基于广播地址255.255.255.255来发现DHCP Server,DHCPv6中,有两个保留的组播地址,用来发现网络中的DHCP Server:

  • All_DHCP_Relay_Agents_and_Servers (ff02::1:2): DHCPv6 client使用这个地址将DHCP请求发送给所有的DHCPv6 relay(中继)agent和DHCPv6 server,这样可以发现网络中的DHCPv6 Server
  • All_DHCP_Servers (ff05::1:3): DHCPv6 relay agent通过这个地址将DHCP请求转发给所有的DHCPv6 server

DHCPv6仍然是基于UDP协议,但是使用的是UDP的547(Server监听端口)和546(Client监听端口)。与IPv4协议不同的是,IPv6协议下,每个网卡都默认带一个link-local地址,这个地址是fe80::/10的前缀加上(一般情况下)网卡的MAC地址生成[RFC4291]。IPv6的link-local用来在一个二层链路中唯一标识一块网卡,并且可以在有限场景下在一个二层链路中用来通信。

在DHCP(IPv4)协议下,DHCP Client在发起请求的时候,因为还没有IP地址,所以源IP只能是Unspecified Address(0.0.0.0)。而DHCPv6 Client在发起请求的时候,网卡已经有IPv6地址了,所以源IPv6地址就是网卡的link-local地址,目的地址是DHCPv6保留的组播地址ff02::1:2。

DHCPv6 Server在收到请求之后,将IPv6地址在单播回给Client网卡的link-local地址。其中IPv6地址包含在Advertise和Reply中,这个过程与DHCP类似。

除了上面实现细节上的差别,DHCPv6与DHCP的区别还在于,DHCPv6不支持传输子网掩码长度,路由,和默认网关。这个区别是由IPv4迁移至IPv6容易引起错误的地方。

IPv6 动态地址配置模式

动态地址配置对于IPv6来说尤其重要,因为在IPv4场景下,地址长度,地址数量有限,静态配置还有可行性,但是如果要真正的完全使用IPv6,通过静态配置地址基本不可能。尽管IPv6下面已经有DHCPv6协议,尽管DHCPv6已经与DHCP不兼容,但是IPv6和IPv4动态地址配置的差别远比DHCPv6和DHCP的差别大。

两个协议,三种模式

IPv6的动态地址配置主要依赖两个协议,一个是DHCPv6(RFC8415),另一个是IPv6 Stateless Address Autoconfiguration(RFC4862)。IPv6的动态地址配置方式客观的说是合理的,并且一定程度结合了IPv4动态地址配置的经验。

在现实场景中,为了让一个网络工作,除了将设备连接起来的线路,交换机,我们主要还需要两个设备,一个是DHCP Server,主要用来管理、分配地址和配置信息,另一个就是Router路由器,主要用来做三层转发,连接当前网络和其他网络。实际中,经常将DHCP Server配置在路由器上,或者路由器作为一个DHCP relay agent。另一方面,如果没有路由器,网络只是一个二层网络,作用有限。因此,这两个设备本身可以只是一个设备,并且它们之中,路由器占主导地位。基于这个背景,IPv6的动态地址配置有三种模式:

  • SLAAC,Stateless Auto Address Configuration
  • Stateless DHCPv6
  • Stateful DHCPv6

这里的Stateful,指的就是DHCP Server管理的IP地址,因为这些地址存在一个分配关系,需要一个程序去管理这个状态。Stateless Address是指这个地址就是分配给某一个确定的主机使用,没有其他状态。

SLAAC

SLAAC 是 ND 协议的组成部分,基于RFC4861和RFC4862。在SLAAC的世界里,没有DHCPv6。SLAAC协议由路由器来通告配置IPv6地址所需要的信息。具体工作流程是这样:支持IPv6的网卡启动的时候会发送一条RS(Router Solicitation)消息,源IP是网卡的link-local地址,目的IP是ff02::2。ff02::2也是保留的组播地址,用来表示所有的路由器。这条消息用来查找当前网络中的路由器。

路由器收到这条消息之后,会回传一条RA(Router Advertisement)。一般情况下,RA的源IP地址是Router的link-local地址,目的地址是ff02::1。ff02::1也是一个保留的组播地址,用来表示所有的主机。也就是说任意网卡发起的RS消息,都会引起路由器将RA消息在整个网络中发送给所有的主机。除此之外,就算没有任何RS消息,路由器也应当定期向所有主机发送RA。

RA消息是SLAAC协议的核心,RA可以发送给网络中所有主机的基础就是,不像DHCP消息,RA是无状态的,任何主机接收到了RA消息之后,都能根据其中的信息完成IP地址配置。RA消息的options区段内容中通常包括:

  • MTU: 主机可以根据这个MTU值配置自己的MTU
  • 路由器的MAC地址
  • 0或者N个prefix(网段)

如果 RA 消息里没有带 prefix, 主机不会配置global IPV6 地址(但仍然可能配置IPV6默认网关为ra消息源地址)。

如果RA中一个prefix的auto标志位是1,那相当于告诉网卡,可以自己从这个prefix中生成一个IPv6的地址。网卡只需要使用一个局域网中唯一的标识符,再加上这个prefix,就能生成一个IPv6地址。网卡在局域网中的天然唯一标识符就是MAC地址,因此一般情况下,SLAAC协议中,网卡是通过RA中的prefix和自身的MAC地址,再根据EUI-64格式生成一个IPv6地址(某些系统为了隐私使用随机数生成EUI-64)。SLAAC协议的基础就是IPv6地址长度足够大,能支持这样的配置。

RA消息还能带来一个潜在的配置,主机会将IPv6的默认网关,指向RA的源IP地址,也就是Router的link-local地址。生成的默认路由是有时效的,时间是Router Lifetime。所以需要路由器定时发布RA(类似于心跳),更新主机的默认路由。这个机制讲道理是合理的,默认路由只有在路由器还活着的时候才有意义。但是这个机制增加了管理的复杂度,尤其在SDN场景,虚拟路由器下定时发布RA。

SLAAC协议有两个问题,一个是IPv6的地址不可控了,一般情况下与主机的MAC地址相关;另一个是RA格式比较简单,能传递的配置有限,一些复杂的主机配置无法通过RA传递。

但是好处是简单,只用路由器,不需要DHCPv6的介入,就可以完成简单的IP地址配置;另外无状态地址能简化管理,只需要一些简单的程序就能完成Prefix分发,不需要集中的管理地址,处理多节点数据同步,这有点像分布式架构。

Stateless DHCPv6

Stateless DHCPv6就是结合了SLAAC和DHCPv6。其中IPv6地址配置通过SLAAC下发,其他配置通过DHCPv6下发。这样能弥补SLAAC模式下,RA所能传递配置有限的问题。

Stateful DHCPv6

这里就是纯纯的DHCPv6了,但是前面说过,DHCPv6不支持子网掩码长度,路由,和默认路由。这样就导致通过DHCPv6获得的IPv6地址,因为没有掩码长度,地址的掩码都是128位。而路由和默认网关,还是需要依赖RA。因为在IPv6中,动态配置路由和默认网关的唯一方式就是使用Router Advertisement消息。所以,就算使用了DHCPv6,也不能完全摆脱RA消息。只是说DHCPv6下,IPv6地址变得可控了。所以,在IPv6动态地址配置中,Router Advertisement是必不可少的,因为它控制了主机路由和默认路由。同时Router Advertisement还控制了IPv6的动态地址配置模式。在RA的数据中,有两个标志位M(Managed)和O(Other)。

  • 当M=0,O=0时,IPv6地址工作在SLAAC模式下
  • 当M=1时,IPv6地址工作在Stateful DHCPv6模式下
  • 当M=0,O=1时,IPv6地址工作在Stateless DHCPv6模式下(RFC3736, 这种模式下主机是先通过 RA 或静态设置方式获取到IPV6地址/默认网关,然后再用DHCPV6配置DNS等)

IPv6中,如果需要地址可控,那只能使用DHCPv6。如果只是需要分配一个IPv6地址,对地址无要求,那么使用Stateless地址分配方式更为合理。

总结 & 其他说明

新版 RA RFC 支持 ND RDNSS 扩展为设备分配 DNS。(这样就完全不需要DHCPV6了)

根据 RFC,主机默认使用 SLAAC (RA) 获取 IPV6 地址。如果网络里没有找到任何路由器(收不到RA消息)或者接收到的RA消息里设置了M或O flag,则会使用 DHCP。

RA 消息的 flag:

  • M flag (DHCPv6 / stateful): When the M, or managed address configuration, flag field is set to “1”, it indicates that addresses should be available to be assigned via a DHCPv6 server. In other words, this flag tells the client to attempt to get an IPv6 address from a DHCPv6 server.
  • O flag: When the O, or other configuration, flag field is set to “1”, it specifies that configuration information other than the IP address should be available as well (typically, via the same DHCPv6 server that would be providing the IP address). This information would usually include DNS server addresses. In other words (pun intended), this flag tells the client to attempt to get additional configuration information such as the address of the recursive DNS servers from a DHCPv6 server.
  • A flag (SLAAC / stateless): When the A (or autonomous address-configuration) flag is set to “1”, it specifies that the included prefix can be used for SLAAC. Thus, the Prefix Information Option message must include both a prefix and the A flag set to one for SLAAC to function properly.

OpenWrt 的 LAN 接口 DHCP - IPV6 - DHCPV6 mode 设置有3种选项,其对应配置文件里的 ra_management 配置。

  • stateless (0): M-flag = 0, A-Flag = 1
  • stateless + stateful (default, 1): M-flag = 1, A-Flag = 1
  • stateful only (2): M-flag = 1, A-Flag = 0

目前所有主流设备和OS都支持 SLAAC。DHCPV6和 ND RDNSS 的支持情况参考:主要 OS 的 IPV6 支持

  • Android / Chrome OS: 不支持 DHCPV6,支持 ND RDNSS。
  • iOS / 主流发行版 Linux / FreeBSD / Mac OS: 支持 DHCPV6 和 ND RDNSS。
  • Windows 10 及更低版本 Windows: 支持 DHCPV6,不支持 ND RDNSS。

SLAAC 需要最少 /64 大小的 prefix。(设备根据 48 位的 MAC 地址计算出 64 位的 EUI-64 作为自身 IPV6 地址后缀)目前中国电信等 ISP 分配的是 /60 的 prefix(注意除了分配一个 /60 prefix,电信还会通过 PPPoE 给路由器自身分配一个 /64 的 IPV6 地址,两者不要混淆。OpenWrt 把分配的 /60 prefix 的第1个地址(地址最后一段为1)划给路由器自身的 LAN 接口)。

IPV6 不使用 ARP 协议,而用 ND。在 Linux 里查看 IPV6 的相同二层局域网主机仍然可以使用 ip neighbor 命令:

ip -6 neighbor

IPV6 DNS 问题

OpenWrt 的 ND RDNSS 和/或 DHCPV6 默认会下发路由器 IPV6 的 link-only address 为 ipv6 DNS 地址,而很多傻逼设备会默认优先使用 IPV6 DNS,某些情况下这会导致问题(特别是配合X86旁路由使用时)。解决方法是关闭 OpenWrt 的 IPV6 DNS 下发。

修改 OpenWrt 的 odhcpd 配置文件 /etc/config/dhcp 的 config dhcp 'lan' 区块,加入 option ra_dns '0' 这一行配置:

ra_dns 配置目前似乎还无法在 luci Web 界面里配置。请注意只有较新的 OpenWrt 版本才支持这个配置项(测试 OpenWrt 19.07 可以)

config dhcp 'lan'
        option interface 'lan'
        option leasetime '12h'
        option limit '50'
        option start '150'
        option ra 'server'
        option dhcpv6 'server'
        option ra_management '1'
        option ra_dns '0'

其他 Workaround: (不推荐,某些设备可能会有问题)

dnsmasq 配置里设置只监听 IPv4 地址;或者直接在路由器 ip6tables 里 REJECT 掉:

ip6tables -A input_lan_rule -p udp --dport 53 -j REJECT

另一个:

OpenWrt Web UI - lan interface 配置 - DHCP Server - IPV6 Settings, 将 Announced DNS servers
设为 "::1" (即 IPV6 的本地地址) 或 "::" (an unspecified address)。请注意这里不填或填写 ipv4 地址均无效(如果不填,默认广播路由器 lan 的 IPV6 link-only 地址作为 IPV6 DNS)

IPV6 NAT / Firewall / DDNS

NAT / Firewall

OpenWrt 默认对 IPV6 未开启 NAT。如果需要,可以安装 kmod-ip6tables 和 kmod-ipt-nat6 这两个包,然后用 ip6tables -t nat 对 IPV6 做 NAT。但通常没有这样做的必要。

OpenWrt 默认 ip6tables 防火墙规则阻止所有 WAN 对 LAN 的 IPV6 的 TCP / UDP 入站连接请求。如果需要有选择性的打开局域网内某些设备的 IPV6 地址上端口以允许外部访问,可以用 ip6tables 选择性放行某些局域网设备 IPV6 地址。(需要安装 ip6tables-extra 和 kmod-ip6tables-extra 这两个包?)

ip6tables -A forwarding_wan_rule -d 240e::0001  -j ACCEPT

和 iptables 一样,ip6tables的 -d / -s 也只能匹配IPV6地址或prefix,好像无法匹配 suffix(比如匹配设备 mac 地址派生出来的 EUI64)?。

DDNS

如果ISP分配的 IPv6 prefix 是动态的,需要配合 DDNS,但这里做的 DDNS 需要根据路由器获得的 ipv6 prefix 和局域网设备的网卡 mac 地址计算出局域网设备的 IPV6 (SLAAC stateless 模式) 地址,直接把域名解析到到局域网设备的 IPV6 地址上,这个过程可以在设备上做,也可以直接在路由器上做,

请注意:因为ISP有时会定时强制路由器重拨,导致路由器获得的ipv6 prefix变化。局域网内设备会(几乎)立即通过ND协议获得并自动配置新的ipv6地址。但旧的ipv6地址不会马上消失,而需要等超时expired后才删除,OpenWrt的默认配置下最长可能要等1800秒(30多钟)。这段时间局域网内设备会有两个ipv6地址(其中只有1个是正确的)。如果在设备上做 DDNS,需要正确找出新的ipv6地址去更新ddns,否则这段时间无法访问主机。

在设备(Linux)上通过查看 ip addr show 发现每个ip后面一行给出了expired时间。新ip的expired时间总是比旧ip大。这样就可以找出最新的ipv6地址了。以下shell脚本会显示出设备的ipv4地址和最新的ipv6(eui64)地址:

#!/bin/sh
ip addr show|grep -A1 'inet [^f:]'|sed -nr 's#^ +inet ([0-9.]+)/[0-9]+ brd [0-9./]+ scope global .*#\1#p'
ip addr show|grep -v deprecated|grep -A1 'inet6 [^f:]'|sed -nr ':a;N;s#^ +inet6 ([a-f0-9:]+)/.+? scope global .*? valid_lft ([0-9]+sec) .*#\2 \1#p;ta'|grep 'ff:fe'|sort -nr|head -n1|cut -d' ' -f2

此显示ipv6的脚本执行步骤是:

  1. ip addr show
  2. 去除 deprecated 地址
  3. 挑出inet6地址,并同时显示下一行
  4. 把expired时间和ipv6地址,通过正则找出来,并显示在同一行。超时时间在前,ip在后。
  5. 过滤出eui64地址
  6. 根据时间的长短,反向排序
  7. 输出第一行
  8. 输出第二列(ip)

另外,默认设置下,OpenWrt 的 DHCPV6 (SLAAC) 除了 ISP 给的global IPV6 地址还会额外分配一个私有的 IPV6 /64 地址给设备(OpenWrt Web UI - Network - Interfaces - Global network options - IPv6 ULA-Prefix),所以也可以直接DDNS到路由器自身的WAN IPV6地址上然后在路由器上做 IPV6 NAT把端口重定向到设备从路由器获得的私有 IPV6 地址上。这样做能简化一些配置并且增加一些安全性。

Debug

# 列出所有局域网 ipv6 设备
ip -6 neigh

Linux 可以用 rdisc6 (包名 ndisc6)这个工具调试 ND / SLACC 相关的 IPV6 问题:

rdisc6 eth0

Last update: 2020-09-28 08:12:25 UTC