KVM VPS 的 IPv6 鄰居發(fā)現(xiàn)響應(yīng)器
我想要 Docker 的 IPv6
這些天我正在使用 Docker,我希望在我的 Docker 容器中使用 IPv6。在 Docker 中啟用 IPv6 的最佳指南是 如何在 Ubuntu 18.04 上為 Docker 容器啟用 IPv6。該文章中的第一種方法將私有 IPv6 地址分配給容器,并使用 IPv6 NAT,類似于 Docker 處理 IPv4 NAT 的方式。我很快就讓它工作了,但我注意到一個(gè)不良行為:網(wǎng)絡(luò)地址轉(zhuǎn)換 (NAT) 會(huì)更改傳出 UDP 數(shù)據(jù)報(bào)的源端口號(hào),即使存在入站流量的端口轉(zhuǎn)發(fā)規(guī)則;因此,具有相同源端口和目標(biāo)端口的 UDP 流被識(shí)別為兩個(gè)單獨(dú)的流。
<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]:6363congestion={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=0scongestion={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> |
該文章中的第二種方法允許每個(gè)容器都有一個(gè)公共 IPv6 地址。它避免了 NAT 及其帶來(lái)的問(wèn)題,但要求主機(jī)具有 路由的IPv6 子網(wǎng)。然而, 路由IPv6在KVM服務(wù)器上很難實(shí)現(xiàn),因?yàn)?nbsp;Virtualizor等虛擬化平臺(tái)不支持路由IPv6子網(wǎng),而只能提供on-link IPv6。
鏈路 IPv6 與路由 IPv6
那么,鏈路 IPv6 和路由 IPv6 之間有什么區(qū)別呢?其不同之處在于如何配置前一跳的路由器以到達(dá)目標(biāo) IP 地址。
我先用IPv4術(shù)語(yǔ)解釋一下:
<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> |
-
服務(wù)器的在線 IP 地址為 192.0.2.2。
- 路由器知道該 IP 地址處于鏈路狀態(tài),因?yàn)樗挥诼酚善鹘涌谏吓渲玫?192.0.2.0/24 子網(wǎng)中。
- 為了將數(shù)據(jù)包發(fā)送到 192.0.2.2,路由器會(huì)發(fā)送 192.0.2.2 的 ARP 查詢來(lái)了解服務(wù)器的 MAC 地址,服務(wù)器應(yīng)響應(yīng)該查詢。
-
服務(wù)器已路由 IP 子網(wǎng) 198.51.100.0/24。
- 路由器必須配置為知道:198.51.100.0/24 可通過(guò) 192.0.2.2 訪問(wèn)。
- 為了將數(shù)據(jù)包傳遞到 198.51.100.2,路由器首先查詢其路由表并找到上述條目,然后發(fā)送 ARP 查詢以獲知服務(wù)器應(yīng)響應(yīng)的 MAC 地址 192.0.2.2,最后將數(shù)據(jù)包傳遞到學(xué)習(xí)到的MAC地址。
-
主要區(qū)別在于 ARP 查詢中包含的 IP 地址:
- 如果目的IP地址是鏈路上的IP地址,則ARP查詢包含目的IP地址本身。
- 如果目標(biāo) IP 地址位于路由子網(wǎng)中,則 ARP 查詢包含由路由表確定的下一跳 IP 地址。
-
如果我想為容器分配一個(gè)鏈路上的 IPv4 地址(例如 192.0.2.18/28),應(yīng)該讓服務(wù)器回答該 IP 地址的 ARP 查詢,以便路由器將數(shù)據(jù)包傳遞到服務(wù)器,然后轉(zhuǎn)發(fā)這些數(shù)據(jù)包到容器。
- 這種技術(shù)稱為 ARP 代理,其中服務(wù)器代表容器響應(yīng) ARP 查詢。
IPv6 中的情況稍微復(fù)雜一些,因?yàn)槊總€(gè)網(wǎng)絡(luò)接口可以有多個(gè) IPv6 地址,但概念相同。 IPv6 使用 屬于 ICMPv6 一部分的鄰居發(fā)現(xiàn)協(xié)議,而不是地址解析協(xié)議 (ARP)。一些術(shù)語(yǔ)有所不同:
| IPv4 | IPv6 |
|---|---|
| ARP | 鄰居發(fā)現(xiàn)協(xié)議 (NDP) |
| ARP查詢 | ICMPv6 鄰居請(qǐng)求 |
| ARP回復(fù) | ICMPv6 鄰居通告 |
| ARP代理 | 新民主黨代理 |
如果我想為容器分配一個(gè)鏈路上的 IPv6 地址,服務(wù)器應(yīng)該響應(yīng)該 IP 地址的鄰居請(qǐng)求,以便路由器將數(shù)據(jù)包傳送到服務(wù)器。之后,服務(wù)器的 Linux 內(nèi)核可以將數(shù)據(jù)包路由到容器的網(wǎng)橋,就好像目標(biāo) IPv6 地址位于路由子網(wǎng)中一樣。
我希望 NDP 代理守護(hù)程序能夠提供救援?
ndppd或 NDP 代理守護(hù)程序是一個(gè)程序,用于偵聽(tīng)網(wǎng)絡(luò)接口上的鄰居請(qǐng)求并以鄰居通告進(jìn)行響應(yīng)。通常建議用于處理服務(wù)器只有鏈接 IPv6 但我們需要路由 IPv6 子網(wǎng)的情況。
我在我的一臺(tái)服務(wù)器上安裝了 ndppd,并且它按照以下配置按預(yù)期工作:
<code class="hljs nginx">proxy uplink { rule 2001:db8:fbc0:2:646f:636b:6572::/112 { auto }}</code> |
我可以使用公共 IPv6 地址啟動(dòng) Docker 容器。它可以訪問(wèn) 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 ipv6exposed118c3a9e00595262e41b8cb839a55d1bc7bc54979a1ff76b5993273d82eea1f4$ 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 ipip=2001:db8:fbc0:2:646f:636b:6572:d002</code> |
然而,當(dāng)我在另一臺(tái) KVM 服務(wù)器上重復(fù)相同的設(shè)置時(shí),情況并不順利:容器根本無(wú)法訪問(wèn) 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.comPING 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有什么問(wèn)題嗎 ?
為什么 ndppd在第一臺(tái)服務(wù)器上工作,但在第二臺(tái)服務(wù)器上不起作用?有什么不同?我們需要更深入,所以我轉(zhuǎn)向 tcpdump。
在第一臺(tái)服務(wù)器上,我看到:
<code class="hljs text">$ sudo tcpdump -pi uplink icmp619: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 3219: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> |
- 來(lái)自路由器的鄰居請(qǐng)求來(lái)自 全局IPv6 地址。
- 服務(wù)器使用來(lái)自其 全局IPv6 地址的鄰居通告進(jìn)行響應(yīng)。請(qǐng)注意,該地址與容器的地址不同。
- IPv6 在容器中工作。
在第二臺(tái)服務(wù)器上,我看到:
<code class="hljs text">$ sudo tcpdump -pi uplink icmp600: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 3200: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> |
- 來(lái)自路由器的鄰居請(qǐng)求來(lái)自 鏈路本地IPv6 地址。
- 服務(wù)器使用來(lái)自其 鏈路本地IPv6 地址的鄰居通告進(jìn)行響應(yīng)。
- IPv6 在容器中不起作用。
由于 IPv6 一直在第二臺(tái)服務(wù)器上工作,以獲取分配給該服務(wù)器本身的 IPv6 地址,因此我添加了一個(gè)新的 IPv6 地址并捕獲了其 NDP 交換:
<code class="hljs text">$ sudo tcpdump -pi uplink icmp600:29:39.378544 IP6 fe80::669d:99ff:feb1:55b8 > ff02::1:ff00:a006: ICMP6, neighbor solicitation, who has 2001:db8:f440:2::a006, length 3200: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> |
- 來(lái)自路由器的鄰居請(qǐng)求來(lái)自 鏈路本地IPv6 地址,與上面相同。
- 服務(wù)器使用來(lái)自目標(biāo) 全局IPv6 地址的鄰居通告進(jìn)行響應(yīng)。
- IPv6 通過(guò)該地址在服務(wù)器上運(yùn)行。
在 IPv6 中,每個(gè)網(wǎng)絡(luò)接口可以有多個(gè) IPv6 地址。當(dāng) Linux 內(nèi)核響應(yīng)鄰居請(qǐng)求(其中目標(biāo)地址被分配給同一網(wǎng)絡(luò)接口)時(shí),它 會(huì)使用該特定地址作為源地址。另一方面, ndppd通過(guò)PF_INET6 套接字傳輸鄰居通告 ,并且 不指定源地址。在這種情況下,一些復(fù)雜的 默認(rèn)地址選擇規(guī)則就開(kāi)始發(fā)揮作用。
這些規(guī)則之一是優(yōu)先選擇 與目標(biāo)地址(即路由器)具有相同范圍的源地址。在我的第一臺(tái)服務(wù)器上,路由器使用 全局地址,并且服務(wù)器選擇 全局地址作為其鄰居通告的源地址。在我的第二臺(tái)服務(wù)器上,路由器使用 鏈路本地地址,服務(wù)器也選擇 鏈路本地地址。
在未經(jīng)過(guò)濾的網(wǎng)絡(luò)中,路由器不會(huì)關(guān)心鄰居通告的來(lái)源。然而,當(dāng)涉及 Virtualizor 上的 KVM 服務(wù)器時(shí),虛擬機(jī)管理程序會(huì)將此類數(shù)據(jù)包視為嘗試的 IP 欺騙攻擊,并通過(guò) ebtables 規(guī)則丟棄它們。因此,鄰居通告永遠(yuǎn)不會(huì)到達(dá)路由器,并且路由器無(wú)法知道如何到達(dá)容器的 IPv6 地址。
ndpresponder:KVM VPS 的 NDP 響應(yīng)程序
我嘗試了一些技巧,例如 棄用鏈接本地地址,但沒(méi)有一個(gè)起作用。因此,我制作了自己的 NDP 響應(yīng)程序,從目標(biāo)地址發(fā)送鄰居通告。
ndpresponder是一個(gè)使用GoPacket庫(kù)的Go 程序 。
- 該程序打開(kāi)一個(gè) AF_PACKET 套接字,其中包含用于 ICMPv6 鄰居請(qǐng)求消息的 BPF 過(guò)濾器。
- 當(dāng)鄰居請(qǐng)求到達(dá)時(shí),它會(huì)根據(jù)用戶提供的 IP 范圍檢查目標(biāo)地址。
- 如果目標(biāo)地址在 Docker 容器使用的范圍內(nèi),則程序會(huì)構(gòu)造 ICMPv6 鄰居通告消息并通過(guò)相同的 AF_PACKET 套接字傳輸它。
與ndppd的主要區(qū)別 在于,鄰居通告消息上的源 IPv6 地址始終設(shè)置為與鄰居請(qǐng)求的目標(biāo)地址相同的值,以便管理程序不會(huì)丟棄該消息。這是可能的,因?yàn)槲彝ㄟ^(guò) AF_PACKET 套接字發(fā)送消息,而不是 ndppd使用的 AF_INET6 套接字。
ndpresponder 的操作方式與“靜態(tài)”模式下的ndppd類似 。它不會(huì)像 ndppd在“自動(dòng)”模式下那樣將鄰居通告轉(zhuǎn)發(fā)到目標(biāo)子網(wǎng),但此功能在 KVM 服務(wù)器上并不重要。
如果 ndppd似乎無(wú)法在您的 KVM VPS 上運(yùn)行,請(qǐng) 嘗試ndpresponder !前往我的 GitHub 存儲(chǔ)庫(kù)獲取安裝和使用說(shuō)明: https: //github.com/yoursunny/ndpresponder
加入討論: LowEndSpirit LowEndTalk
標(biāo)簽: Docker Go IPv6 Linux 托管
https://yoursunny.com/t/2021/ndpresponder/
浙公網(wǎng)安備 33010602011771號(hào)