KVM VPS 的 IPv6 邻居发现响应器
我想要 Docker 的 IPv6
这些天我正在使用 Docker,我希望在我的 Docker 容器中使用 IPv6。在 Docker 中启用 IPv6 的最佳指南是 如何在 Ubuntu 18.04 上为 Docker 容器启用 IPv6。该文章中的第一种方法将私有 IPv6 地址分配给容器,并使用 IPv6 NAT,类似于 Docker 处理 IPv4 NAT 的方式。我很快就让它工作了,但我注意到一个不良行为:网络地址转换 (NAT) 会更改传出 UDP 数据报的源端口号,即使存在入站流量的端口转发规则;因此,具有相同源端口和目标端口的 UDP 流被识别为两个单独的流。
<code class = "hljs shell" > $ docker exec nfd nfdc face show 262 faceid=262 remote=udp6: //[2001:db8:f440:2:eb26:f0a9:4dc3:1]:6363 local=udp6: //[fd00:2001:db8:4d55:0:242:ac11:4]:6363 congestion={base-marking-interval=100ms default -threshold=65536B} mtu=1337 counters={in={25i 4603d 2n 1179907B} out={11921i 14d 0n 1506905B}} flags={non-local permanent point-to-point congestion-marking} $ docker exec nfd nfdc face show 270 faceid=270 remote=udp6: //[2001:db8:f440:2:eb26:f0a9:4dc3:1]:1024 local=udp6: //[fd00:2001:db8:4d55:0:242:ac11:4]:6363 expires=0s congestion={base-marking-interval=100ms default -threshold=65536B} mtu=1337 counters={in={11880i 0d 0n 1498032B} out={0i 4594d 0n 1175786B}} flags={non-local on-demand point-to-point congestion-marking}</code> |
该文章中的第二种方法允许每个容器都有一个公共 IPv6 地址。它避免了 NAT 及其带来的问题,但要求主机具有 路由的IPv6 子网。然而, 路由IPv6在KVM服务器上很难实现,因为 Virtualizor等虚拟化平台不支持路由IPv6子网,而只能提供on-link IPv6。
链路 IPv6 与路由 IPv6
那么,链路 IPv6 和路由 IPv6 之间有什么区别呢?其不同之处在于如何配置前一跳的路由器以到达目标 IP 地址。
我先用IPv4术语解释一下:
<code class = "hljs text" > |--------| 192.0.2.1/24 |--------| 198.51.100.1/24 |-----------| | router |--------------------| server |--------------------| container | |--------| 192.0.2.2/24 |--------| 198.51.100.2/24 |-----------| (192.0.2.16-23/24) | | 192.0.2.17/28 |-----------| \-------------------------| container | 192.0.2.18/28 |-----------|</code> |
-
服务器的在线 IP 地址为 192.0.2.2。
- 路由器知道该 IP 地址处于链路状态,因为它位于路由器接口上配置的 192.0.2.0/24 子网中。
- 为了将数据包发送到 192.0.2.2,路由器会发送 192.0.2.2 的 ARP 查询来了解服务器的 MAC 地址,服务器应响应该查询。
-
服务器已路由 IP 子网 198.51.100.0/24。
- 路由器必须配置为知道:198.51.100.0/24 可通过 192.0.2.2 访问。
- 为了将数据包传递到 198.51.100.2,路由器首先查询其路由表并找到上述条目,然后发送 ARP 查询以获知服务器应响应的 MAC 地址 192.0.2.2,最后将数据包传递到学习到的MAC地址。
-
主要区别在于 ARP 查询中包含的 IP 地址:
- 如果目的IP地址是链路上的IP地址,则ARP查询包含目的IP地址本身。
- 如果目标 IP 地址位于路由子网中,则 ARP 查询包含由路由表确定的下一跳 IP 地址。
-
如果我想为容器分配一个链路上的 IPv4 地址(例如 192.0.2.18/28),应该让服务器回答该 IP 地址的 ARP 查询,以便路由器将数据包传递到服务器,然后转发这些数据包到容器。
- 这种技术称为 ARP 代理,其中服务器代表容器响应 ARP 查询。
IPv6 中的情况稍微复杂一些,因为每个网络接口可以有多个 IPv6 地址,但概念相同。 IPv6 使用 属于 ICMPv6 一部分的邻居发现协议,而不是地址解析协议 (ARP)。一些术语有所不同:
IPv4 | IPv6 |
---|---|
ARP | 邻居发现协议 (NDP) |
ARP查询 | ICMPv6 邻居请求 |
ARP回复 | ICMPv6 邻居通告 |
ARP代理 | 新民主党代理 |
如果我想为容器分配一个链路上的 IPv6 地址,服务器应该响应该 IP 地址的邻居请求,以便路由器将数据包传送到服务器。之后,服务器的 Linux 内核可以将数据包路由到容器的网桥,就好像目标 IPv6 地址位于路由子网中一样。
我希望 NDP 代理守护程序能够提供救援?
ndppd或 NDP 代理守护程序是一个程序,用于侦听网络接口上的邻居请求并以邻居通告进行响应。通常建议用于处理服务器只有链接 IPv6 但我们需要路由 IPv6 子网的情况。
我在我的一台服务器上安装了 ndppd,并且它按照以下配置按预期工作:
<code class = "hljs nginx" > proxy uplink { rule 2001:db8:fbc0:2:646f:636b:6572::/112 { auto } }</code> |
我可以使用公共 IPv6 地址启动 Docker 容器。它可以访问 IPv6 Internet,并且可以从外部 ping 通。
<code class = "hljs shell" > $ docker network create --ipv6 --subnet=172.26.0.0/16 \ --subnet=2001:db8:fbc0:2:646f:636b:6572::/112 ipv6exposed 118c3a9e00595262e41b8cb839a55d1bc7bc54979a1ff76b5993273d82eea1f4 $ docker run -it --rm --network ipv6exposed \ --ip6 2001:db8:fbc0:2:646f:636b:6572:d002 alpine # wget -q -O- https: //www.cloudflare.com/cdn-cgi/trace | grep ip ip=2001:db8:fbc0:2:646f:636b:6572:d002</code> |
然而,当我在另一台 KVM 服务器上重复相同的设置时,情况并不顺利:容器根本无法访问 IPv6 Internet。
<code class = "hljs shell" > $ docker run -it --rm --network ipv6exposed \ --ip6 2001:db8:f440:2:646f:636b:6572:d003 alpine / # ping -c 4 ipv6.google.com PING ipv6.google.com (2607:f8b0:400a:809::200e): 56 data bytes --- ipv6.google.com ping statistics --- 4 packets transmitted, 0 packets received, 100% packet loss</code> |
ndppd有什么问题吗 ?
为什么 ndppd在第一台服务器上工作,但在第二台服务器上不起作用?有什么不同?我们需要更深入,所以我转向 tcpdump。
在第一台服务器上,我看到:
<code class = "hljs text" > $ sudo tcpdump -pi uplink icmp6 19:13:17.958191 IP6 2001:db8:fbc0::1 > ff02::1:ff72:d002: ICMP6, neighbor solicitation, who has 2001:db8:fbc0:2:646f:636b:6572:d002, length 32 19:13:17.958472 IP6 2001:db8:fbc0:2::2 > 2001:db8:fbc0::1: ICMP6, neighbor advertisement, tgt is 2001:db8:fbc0:2:646f:636b:6572:d002, length 32</code> |
- 来自路由器的邻居请求来自 全局IPv6 地址。
- 服务器使用来自其 全局IPv6 地址的邻居通告进行响应。请注意,该地址与容器的地址不同。
- IPv6 在容器中工作。
在第二台服务器上,我看到:
<code class = "hljs text" > $ sudo tcpdump -pi uplink icmp6 00:07:53.617438 IP6 fe80::669d:99ff:feb1:55b8 > ff02::1:ff72:d003: ICMP6, neighbor solicitation, who has 2001:db8:f440:2:646f:636b:6572:d003, length 32 00:07:53.617714 IP6 fe80::216:3eff:fedd:7c83 > fe80::669d:99ff:feb1:55b8: ICMP6, neighbor advertisement, tgt is 2001:db8:f440:2:646f:636b:6572:d003, length 32</code> |
- 来自路由器的邻居请求来自 链路本地IPv6 地址。
- 服务器使用来自其 链路本地IPv6 地址的邻居通告进行响应。
- IPv6 在容器中不起作用。
由于 IPv6 一直在第二台服务器上工作,以获取分配给该服务器本身的 IPv6 地址,因此我添加了一个新的 IPv6 地址并捕获了其 NDP 交换:
<code class = "hljs text" > $ sudo tcpdump -pi uplink icmp6 00:29:39.378544 IP6 fe80::669d:99ff:feb1:55b8 > ff02::1:ff00:a006: ICMP6, neighbor solicitation, who has 2001:db8:f440:2::a006, length 32 00:29:39.378581 IP6 2001:db8:f440:2::a006 > fe80::669d:99ff:feb1:55b8: ICMP6, neighbor advertisement, tgt is 2001:db8:f440:2::a006, length 32</code> |
- 来自路由器的邻居请求来自 链路本地IPv6 地址,与上面相同。
- 服务器使用来自目标 全局IPv6 地址的邻居通告进行响应。
- IPv6 通过该地址在服务器上运行。
在 IPv6 中,每个网络接口可以有多个 IPv6 地址。当 Linux 内核响应邻居请求(其中目标地址被分配给同一网络接口)时,它 会使用该特定地址作为源地址。另一方面, ndppd通过PF_INET6 套接字传输邻居通告 ,并且 不指定源地址。在这种情况下,一些复杂的 默认地址选择规则就开始发挥作用。
这些规则之一是优先选择 与目标地址(即路由器)具有相同范围的源地址。在我的第一台服务器上,路由器使用 全局地址,并且服务器选择 全局地址作为其邻居通告的源地址。在我的第二台服务器上,路由器使用 链路本地地址,服务器也选择 链路本地地址。
在未经过滤的网络中,路由器不会关心邻居通告的来源。然而,当涉及 Virtualizor 上的 KVM 服务器时,虚拟机管理程序会将此类数据包视为尝试的 IP 欺骗攻击,并通过 ebtables 规则丢弃它们。因此,邻居通告永远不会到达路由器,并且路由器无法知道如何到达容器的 IPv6 地址。
ndpresponder:KVM VPS 的 NDP 响应程序
我尝试了一些技巧,例如 弃用链接本地地址,但没有一个起作用。因此,我制作了自己的 NDP 响应程序,从目标地址发送邻居通告。
ndpresponder是一个使用GoPacket库的Go 程序 。
- 该程序打开一个 AF_PACKET 套接字,其中包含用于 ICMPv6 邻居请求消息的 BPF 过滤器。
- 当邻居请求到达时,它会根据用户提供的 IP 范围检查目标地址。
- 如果目标地址在 Docker 容器使用的范围内,则程序会构造 ICMPv6 邻居通告消息并通过相同的 AF_PACKET 套接字传输它。
与ndppd的主要区别 在于,邻居通告消息上的源 IPv6 地址始终设置为与邻居请求的目标地址相同的值,以便管理程序不会丢弃该消息。这是可能的,因为我通过 AF_PACKET 套接字发送消息,而不是 ndppd使用的 AF_INET6 套接字。
ndpresponder 的操作方式与“静态”模式下的ndppd类似 。它不会像 ndppd在“自动”模式下那样将邻居通告转发到目标子网,但此功能在 KVM 服务器上并不重要。
如果 ndppd似乎无法在您的 KVM VPS 上运行,请 尝试ndpresponder !前往我的 GitHub 存储库获取安装和使用说明: https: //github.com/yoursunny/ndpresponder
加入讨论: LowEndSpirit LowEndTalk
https://yoursunny.com/t/2021/ndpresponder/