前言
一些 VPS /云服务器厂家给用户提供 /64 的 IPv6 前缀,通常会在产品界面提到 ‘routed /64 IPv6’ 或者 ‘/64 IPv6’ 的字样,这就意味着理论上整块 /64 内的地址你都可以拿来用。
示例的用途有:给每个容器独立的 IPv6 GUA 使其不依赖任何 NAT 技术;或者给 vrf 虚拟 L3 interface 分配专门的 IPv6 GUA,使得可以细粒度地指定 per-application 的 IPv6 使用。
简介
一般来说厂商会假设分给你的 /64 就是在 eth0(或者类似的名称,比如 enp0s3 等)所处的 LAN segment,所以假如你直接在 eth0 上添加这个地址,例如 2001:db8:1234:1::b/64,那么可以直接ping通,但是如果把这个地址添加在其它接口上则不行。之所以这样,是因为你的 VPS 的网关会先往你 eth0 所处的网段发送一个 NDP neighbor solicitation 请求(以下简称 NDP NS 请求)以确认这个地址真的被 assign 在了该网段的某个接口上,顺便确认该地址对应的 L2 地址(MAC地址/以太网地址)。
所以,假如你把 2001:db8:1234:1::a/64 assign 在 eth0,2001:db8:1234:1::b/64 assign 在另一个接口,大概率会发现 2001:db8:1234:1::a/64 能 ping 通,2001:db8:1234:1::b/64 则不行。原因就是网关无法获取到 2001:db8:1234:1::b/64 的 L2 地址。
方法之一就是在 eth0(你的 VPS 到网关的那个接口)进行 NDP 代答,或称 NDP Proxy:即便 2001:db8:1234:1::b/64 没有 assign 在 eth0 上,eth0 也像模像样地回答针对 2001:db8:1234:1::b/64 的 NDP NS 请求,先应付 NDP 这一关,让上联网关把封包先发过来,拿到封包了之后,在自己的地界上想怎么路由就怎么路由。
过程
首先启动 IPv6 转发和 NDP Proxy:
sysctl -w net.ipv6.conf.all.forwarding=1
sysctl -w net.ipv6.conf.eth0.proxy_ndp=1
假设 2001:db8:1234:1::/64 是厂商给你的 routed IPv6 段,涉及到的分配如下:
- eth0:
2001:db8:1234:1::a/64 - 轻量 VRF 虚拟接口 vrf-test1:
2001:db8:1234:1::b/128 - 容器内的 eth0:
2001:db8:1234:1::c/128
添加要代答 NDP 的地址:
ip -6 neigh proxy 2001:db8:1234:1::b dev eth0
ip -6 neigh proxy 2001:db8:1234:1::c dev eth0
这时如果你再用 tcpdump 监测 icmp6 封包,会发现 NDP NS 请求已经可以被回答了。
创建一个 vrf-lite 接口,指定路由表编号为 101:
ip l add vrf-test1 type vrf table 101
ip l set vrf-test1 up
sysctl -w net.ipv6.conf.vrf-test1.forwarding=1
给这个 vrf-lite 接口分配地址和路由:
ip -6 a add 2001:db8:1234:1::b/128 dev vrf-lite
ip -6 r add 2001:db8:1234:1::b dev vrf-lite
现在这个地址已经可以 ping 了:

那么这时除了能 ping 之外,你还可以在这个 vrf 虚拟接口上运行应用程序,比如:
ip vrf exec vrf-test1 python -m http.server -b '::' 47057
相当于给一个 http server 分配了一个专用的 IPv6 地址:

下面演示如何给容器分配 IPv6 GUA。我们会使用 podman,它支持在指定的 netns 上启动容器,较新的版本还支持在指定的 vrf 上启动容器。
首先创建 netns:
ip netns add v6test
ip -n v6test l set lo up
创建一条“虚拟网线”连接 host netns 和容器 netns:
ip l add v6test-veth type veth peer eth0 netns v6test
ip l set v6test-veth up
ip -n v6test l set eth0 up
配置地址和路由:
ip a add fe80::1/64 dev v6test-veth
ip -n v6test a add 2001:db8:1234::c/128 dev eth0
ip -n v6test r add default via fe80::1 dev eth0
ip -6 route add 2001:db8:1234::c/128 dev v6test-veth
这时这个地址应该能 ping 通了:

可以在该 netns 上启动一个容器,这个容器自动得以享受到公网 IPv6:
podman run --network 'ns:/run/netns/v6test' nginx:latest
然后使用 curl 来测试该 http 服务器:

扩展
对于小规模的场景,你可以使用 Linux networking stack 自带的 NDP proxy 功能(ip -6 neigh add proxy 和 sysctl -w net.ipv6.conf.<if_name>.proxy_ndp),开箱即用,但是得一个一个地址添加。
你可以主动了解更多用户态的 NDP proxy / NDP 代答实现方式,比如 ndppd 软件,支持子网级别的配置颗粒度,一次性指定一整个子网,就不用再一个个地添加。
