家庭宽带软路由内网ipv6方案

我目前的网络上游是上海电信的300M宽带,自己想办法改了光猫桥接,然后接自己用 NUC + Ubuntu 20.04 做的软路由。路由拨号之后相应的 pppoe 接口会获得240e开头的IPV6地址。这篇文章主要记录如何正确的让内网设备获得 ipv6 地址。

要给内网客户端分配正确的IPv6地址,首先要理解 ISP 分配 ipv6 地址的方法。最主流的方法是邻居发现协议(Neighbor Discovery, ND) 。ND 本身定义了很多内容,各个运营商在各种情况下一般使用 ND 协议的一个子集来进行地址分配。

​tools.ietf.org/html/rfc4861

关于SLACC分配地址的方式,这里有一篇很好的文章:

肖宏辉:IPv6 --- 动态地址配置67 赞同 · 12 评论文章

ISP 如何分配 ipv6

当客户端请求IPv6地址时,会向ISP广播路由协商(Router Solicitation,RS),ISP接到 RS 后会响应RA。

在我的网络环境中,中国电信会在 pppoe 链路以约700秒的间隔向客户端发送路由通告(Router Advertisement, RA)

ISP 下发 RA

RA中可以包含很多信息,最重要的是 ip prefix。下图中可见 ISP 下发了一个 240e:xxxx:xxxx:xxxx::64 的 ip地址段。而客户端可以选择这个/64段中的任何一个地址作为自身的地址,并向ISP路由报告。

ISP 下发 RA 的内容

由于/64地址段有2^64个地址,客户端可以随意选择。选择地址后要通过邻居协商(Neighbor Solicaitation, NS)向上级路由器报告。NS 并不是地址分配的步骤,而是上级路由为你分配路由的步骤。假设客户端自行在/64段内分配了ipv6地址A,如果客户端不向上级报告自己的 ipv6 地址,那么上游就不知道发往地址A的流量应该如何转发给这个客户端。客户端自然就没办法联网了。

图中是家用路由器下发RA后,路由器下各种客户端争相报告自己的ip的场景。I

小小总结一下:如果想让设备正确连接 ipv6 网路,必须满足以下两点

根据正确的prefix自行分配ipv6地址。向上游协商这个ipv6地址的路由(实际上经过测试并不需要向ISP报告,ISP也会把整个/64路由给我)

知道了而两点,如何在家用软路由上为内网分配 IPv6 地址就呼之欲出了。

家用软路由ipv6分配方案

实际上,目前购买的商用路由器(例如新版本的小米路由)等,支持 IPv6 功能的,都可以正确的处理内网 ipv6 分配问题。如果使用 openwrt 等开源路由方案,openwrt 也提供成熟的方案来进行内网 ipv6 分配。Openwrt 新版本主要是通过 odhcpd 来解决这个问题。通常用户不需要掌握非常专业的网络知识就可以成功配置。我因为使用Ubuntu 系统配置软路由,选择了一条相对困难的路径,才需要自行配置。

刚才说到ISP会给客户端分配一个 /64 的 prefix。相信读者都能看出,这个 prefix 非常大,完全可以满足我们对地址的需求。软路由需要做的事情就是将这个 prefix 原样重复给局域网设备即可,设备会自行选择自己的ip。

软路由上的防火墙应当允许从内部接口到外部接口的转发,这样内网设备才能将数据包发给ISP。

设备自行选择IP后,我们的软路由要正确的记录设备的IP,这样当响应由ISP发给软路由后,软路由能正确的将数据转发给设备。

radvd

radvd 就是一款能够根据ISP 的地址段向内网广播 RA 的软件。我是用的版本是2.18,原因忘了,好像 ubnuntu 20.04 提供的2.17不能用。配置文件在 /etc/radvd.conf

interface br-lan # 内网接口 { AdvSendAdvert on; prefix240e::/64 # 下发的RA是哪个段 { AdvOnLink on; AdvAutonomous on; AdvRouterAddr on; Base6Interface pppoe-wan; # 结合这个接口的实际IP和上面定义的/64 prefix }; };

其中比较重要的参数是 Base6Interface。这个参数会结合这里定义的 pppoe-wan 接口上的实际IP地址的的前64bit决定下发地址段的前缀,(这个64是 prefix240e::/64 中定义的 )

accept_ra & ipv6 fordwarding

想要内网外网能互相通信,首先要内网外网两个接口都有global IPv6 地址。并且打开ipv6 转发功能

net.ipv6.conf.all.forwarding=1 net.ipv6.conf.br-lan.accept_ra=2

这里我踩了一个坑,如果内网的br-lan接口不打开accept_ra=2,那么内网接口不会获得Global IP地址,导致软路由的邻居表(neighbor table)中不会记录内网设备地址,导致数据无法被转发给内网设备。

如果内网设备成功获取IPv6地址了,可以用以下命令检查下软路由的neighbor

ip -6 neighbor show

Firewall 设置

我的软路由使用 firewalld 持久化所有的 iptabes 规则,但是默认的防火墙规则拒绝接口间的IP转发。我没找到好的解决方法,只能直接增加direct rule

sudo firewall-cmd --permanent --direct --add-rule ipv6 filter FORWARD 0 -o pppoe-wan -i br-lan -j ACCEPT sudo firewall-cmd --reload

软路由到底需不需要向ISP通告内部设备IP

这篇文章在我草稿箱里面呆了有一年,此前还包含一个NDPPD的部分,但是搬家后没来得及搞IPv6就没发布。

最近设置的时候,在通往ISP的链路上抓包,发现内网设备获取IP后,并没有 NS 或者 NA 数据传给ISP,内网设备仍然是可以通信的。那就比较有意思了。于是我想要尝试在软路由上以相同/64 prefix 但是后面随机的地址通过ISP发出ICMP echo,看看 ISP 能不能把 ICMP reply 传回来。

本来 nmap 自带的 nping 工具可以做这种事,但是 nping 本身在 ppp 接口上的支持不完善,只能自己写一个工具测试。

Ping ipv6 address with custom source ip​gist.github.com/comzyh/0ffdcbc9597bc0041b5243beb

实际测试结果表明,只要你的 IP 地址在 ISP 下发的 /64 段内,ISP都会把相应的数据路由给你。

不仅如此,即使我在互联网上请求一个新的IP地址,只要这个IP地址在 ISP RA 指定的段里,即使这个IP地址从未向ISP发送过任何数据,ISP也会把相关的数据路由给我。我用一台互联网上的支持ipv6的代理服务器验证了我的想法。