WireGuard
是由 Jason A. Donenfeld 等人建立的下一代開源 *** 協議,旨在解決許多困擾 IPSec/IKEv2
、Open***
或 L2TP
等其餘 *** 協議的問題。2020 年 1 月 29 日,WireGuard 正式合併進入 Linux 5.6
內核主線。html
利用 WireGuard 咱們能夠實現不少很是奇妙的功能,好比跨公有云組建 Kubernetes 集羣,本地直接訪問公有云 Kubernetes
集羣中的 Pod IP 和 Service IP,在家中沒有公網 IP 的狀況下直連家中的設備,等等。linux
若是你是第一次據說 WireGuard,建議你花點時間看看我以前寫的 WireGuard 工做原理。git
本文將探討 WireGuard 使用過程當中遇到的一個重大難題:如何使兩個位於 NAT 後面(且沒有指定公網出口)的客戶端之間直接創建鏈接。github
WireGuard 不區分服務端和客戶端,你們都是客戶端,與本身鏈接的全部客戶端都被稱之爲 Peer
。緩存
WireGuard 的核心部分是加密密鑰路由(Cryptokey Routing),它的工做原理是將公鑰和 IP 地址列表(AllowedIPs
)關聯起來。每個網絡接口都有一個私鑰和一個 Peer 列表,每個 Peer 都有一個公鑰和 IP 地址列表。發送數據時,能夠把 IP 地址列表當作路由表;接收數據時,能夠把 IP 地址列表當作訪問控制列表。安全
公鑰和 IP 地址列表的關聯組成了 Peer 的必要配置,從隧道驗證的角度看,根本不須要 Peer 具有靜態 IP 地址。理論上,若是 Peer 的 IP 地址不一樣時發生變化,WireGuard 是能夠實現 IP 漫遊的。bash
如今回到最初的問題:假設兩個 Peer 都在 NAT 後面,且這個 NAT 不受咱們控制,沒法配置 UDP 端口轉發,即沒法指定公網出口,要想創建鏈接,不只要動態發現 Peer 的 IP 地址,還要發現 Peer 的端口。服務器
找了一圈下來,現有的工具根本沒法實現這個需求,本文將致力於不對 WireGuard 源碼作任何改動的狀況下實現上述需求。網絡
你可能會問我爲何不使用中心輻射型(hub-and-spoke)網絡拓撲?中心輻射型網絡有一個 *** 網關,這個網關一般都有一個靜態 IP 地址,其餘全部的客戶端都須要鏈接這個 *** 網關,再由網關將流量轉發到其餘的客戶端。假設 Alice
和 Bob
都位於 NAT 後面,那麼 Alice
和 Bob
都要和網關創建隧道,而後 Alice
和 Bob
之間就能夠經過 *** 網關轉發流量來實現相互通訊。app
其實這個方法是現在你們都在用的方法,已經沒什麼可說的了,缺點至關明顯:
本文想探討的是 Alice
和 Bob
之間直接創建隧道,中心輻射型(hub-and-spoke)網絡拓撲是沒法作到的。
要想在 Alice
和 Bob
之間直接創建一個 WireGuard 隧道,就須要它們可以穿過擋在它們面前的 NAT。因爲 WireGuard 是經過 UDP
來相互通訊的,因此理論上 UDP 打洞(UDP hole punching) 是最佳選擇。
UDP 打洞(UDP hole punching)利用了這樣一個事實:大多數 NAT 在將入站數據包與現有的鏈接進行匹配時都很寬鬆。這樣就能夠重複使用端口狀態來打洞,由於 NAT 路由器不會限制只接收來自原始目的地址(信使服務器)的流量,其餘客戶端的流量也能夠接收。
舉個例子,假設 Alice
向新主機 Carol
發送一個 UDP 數據包,而 Bob
此時經過某種方法獲取到了 Alice
的 NAT 在地址轉換過程當中使用的出站源 IP:Port
,Bob
就能夠向這個 IP:Port
(2.2.2.2:7777) 發送 UDP 數據包來和 Alice
創建聯繫。
其實上面討論的就是徹底圓錐型 NAT(Full cone NAT),即一對一(one-to-one)NAT。它具備如下特色:
大部分的 NAT 都是這種 NAT,對於其餘少數不常見的 NAT,這種打洞方法有必定的侷限性,沒法順利使用。
回到上面的例子,UDP 打洞過程當中有幾個問題相當重要:
IP:Port
?RFC5389 關於 STUN(Session Traversal Utilities for NAT,NAT會話穿越應用程序)的詳細描述中定義了一個協議回答了上面的一部分問題,這是一篇內容很長的 RFC,因此我將盡我所能對其進行總結。先提醒一下,STUN
並不能直接解決上面的問題,它只是個扳手,你還得拿他去打造一個稱手的工具:
STUN 自己並非 NAT 穿透問題的解決方案,它只是定義了一個機制,你能夠用這個機制來組建實際的解決方案。
— RFC5389
STUN(Session Traversal Utilities for NAT,NAT會話穿越應用程序)是一種網絡協議,它容許位於NAT(或多重NAT)後的客戶端找出本身的公網地址,查出本身位於哪一種類型的 NAT 以後以及 NAT 爲某一個本地端口所綁定的公網端口。這些信息被用來在兩個同時處於 NAT 路由器以後的主機之間創建 UDP 通訊。該協議由 RFC 5389 定義。
STUN 是一個客戶端-服務端協議,在上圖的例子中,Alice
是客戶端,Carol
是服務端。Alice
向 Carol
發送一個 STUN Binding
請求,當 Binding 請求經過 Alice
的 NAT 時,源 IP:Port
會被重寫。當 Carol
收到 Binding 請求後,會將三層和四層的源 IP:Port
複製到 Binding 響應的有效載荷中,並將其發送給 Alice
。Binding 響應經過 Alice 的 NAT 轉發到內網的 Alice
,此時的目標 IP:Port 被重寫成了內網地址,但有效載荷保持不變。Alice
收到 Binding 響應後,就會意識到這個 Socket 的公網 IP:Port 是 2.2.2.2:7777
。
然而,STUN
並非一個完整的解決方案,它只是提供了這麼一種機制,讓應用程序獲取到它的公網 IP:Port
,但 STUN 並無提供具體的方法來向相關方向發出信號。若是要重頭編寫一個具備 NAT 穿透功能的應用,確定要利用 STUN 來實現。固然,明智的作法是不修改 WireGuard 的源碼,最好是借鑑 STUN 的概念來實現。總之,無論如何,都須要一個擁有靜態公網地址的主機來充當信使服務器。
早在 2016 年 8 月份,WireGuard 的建立者就在 WireGuard 郵件列表上分享了一個 NAT 穿透示例。Jason 的示例包含了客戶端應用和服務端應用,其中客戶端應用於 WireGuard 一塊兒運行,服務端運行在擁有靜態地址的主機上用來發現各個 Peer 的 IP:Port
,客戶端使用原始套接字(raw socket)與服務端進行通訊。
/* We use raw sockets so that the WireGuard interface can actually own the real socket. */ sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); if (sock < 0) { perror("socket"); return errno; }
正如評論中指出的,WireGuard 擁有「真正的套接字」。經過使用原始套接字(raw socket),客戶端可以向服務端假裝本地 WireGuard 的源端口,這樣就確保了在服務端返回響應通過 NAT 時目標 IP:Port
會被映射到 WireGuard 套接字上。
客戶端在其原始套接字上使用一個經典的 BPF 過濾器來過濾服務端發往 WireGuard 端口的回覆。
static void apply_bpf(int sock, uint16_t port, uint32_t ip) { struct sock_filter filter[] = { BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 12 /* src ip */), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ip, 0, 5), BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 /* src port */), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, PORT, 0, 3), BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 22 /* dst port */), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, port, 0, 1), BPF_STMT(BPF_RET + BPF_K, -1), BPF_STMT(BPF_RET + BPF_K, 0) }; struct sock_fprog filter_prog = { .len = sizeof(filter) / sizeof(filter[0]), .filter = filter }; if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog, sizeof(filter_prog)) < 0) { perror("setsockopt(bpf)"); exit(errno); } }
客戶端與服務端的通訊數據都被定義在 packet
和 reply
這兩個結構體中:
struct { struct udphdr udp; uint8_t my_pubkey[32]; uint8_t their_pubkey[32]; } __attribute__((packed)) packet = { .udp = { .len = htons(sizeof(packet)), .dest = htons(PORT) } }; struct { struct iphdr iphdr; struct udphdr udp; uint32_t ip; uint16_t port; } __attribute__((packed)) reply;
客戶端會遍歷配置好的 WireGuard Peer(wg show <interface> peers
),併爲每個 Peer 發送一個數據包給服務端,其中 my_pubkey
和 their_pubkey
字段會被適當填充。當服務端收到來自客戶端的數據包時,它會向以公鑰爲密鑰的 Peer 內存表中插入或更新一個 pubkey=my_pubkey
的 entry
,而後再從該表中查找 pubkey=their_pubkey
的 entry
,一但發現 entry
存在,就會將其中的 IP:Port
發送給客戶端。當客戶端收到回覆時,會將 IP 和端口從數據包中解包,並配置 Peer 的 endpoint 地址(wg set <interface> peer <key> <options...> endpoint <ip>:<port>
)。
entry
結構體源碼:
struct entry { uint8_t pubkey[32]; uint32_t ip; uint16_t port; };
entry
結構體中的 ip
和 port
字段是從客戶端收到的數據包中提取的 IP 和 UDP 頭部,每次客戶端請求 Peer 的 IP 和端口信息時,都會在 Peer 列表中刷新本身的 IP 和端口信息。
上面的例子展現了 WireGuard 如何實現 UDP 打洞,但仍是太複雜了,由於並非全部的 Peer 端都能打開原始套接字(raw socket),也並非全部的 Peer 端都能利用 BPF 過濾器。並且這裏還用到了自定義的 wire protocol,代碼層面的數據(鏈表、隊列、二叉樹)都是結構化的,但網絡層看到的都是二進制流,所謂 wire protocol
就是把結構化的數據序列化爲二進制流發送出去,而且對方也能以一樣的格式反序列化出來。這種方式是很難調試的,因此咱們須要另闢蹊徑,利用現有的成熟工具來達到目的。
其實徹底不必這麼麻煩,咱們能夠直接利用 WireGuard 自己的特性來實現 UDP 打洞,直接看圖:
你可能會認爲這是個中心輻射型(hub-and-spoke)網絡拓撲,但實際上仍是有些區別的,這裏的 Registry Peer 不會充當網關的角色,由於它沒有相應的路由,不會轉發流量。Registry 的 WireGuard 接口地址爲 10.0.0.254/32
,Alice 和 Bob 的 AllowedIPs
中只包含了 10.0.0.254/32
,表示只接收來自 Registry
的流量,因此 Alice 和 Bob 之間沒法經過 Registry 來進行通訊。
這裏有一點相當重要,Registry
分別和 Alice 與 Bob 創建了兩個隧道,這就會在 Alice 和 Bob 的 NAT 上打開一個洞,咱們須要找到一種方法來從 Registry Peer 中查詢這些洞的 IP:Port
,天然而然就想到了 DNS
協議。DNS 的優點很明顯,它比較簡單、成熟,還跨平臺。有一種 DNS 記錄類型叫 SRV記錄(Service Record,服務定位記錄),它用來記錄服務器提供的服務,即識別服務的 IP 和端口,RFC6763 用具體的結構和查詢模式對這種記錄類型進行了擴展,用於發現給定域下的服務,咱們能夠直接利用這些擴展語義。
選好了服務發現協議後,還須要一種方法來將其與 WireGuard 對接。CoreDNS 是 Golang 編寫的一個插件式 DNS 服務器,是目前 Kubernetes 內置的默認 DNS 服務器,而且已從 CNCF 畢業。咱們能夠直接寫一個 CoreDNS 插件,用來接受 DNS-SD
(DNS-based Service Discovery)查詢並返回相關 WireGuard Peer 的信息,其中公鑰做爲記錄名稱,fuckcloudnative.io 做爲域。若是你熟悉 bind 風格的域文件,能夠想象一個相似這樣的域數據:
_wireguard._udp IN PTR alice._wireguard._udp.fuckcloudnative.io. _wireguard._udp IN PTR bob._wireguard._udp.fuckcloudnative.io. alice._wireguard._udp IN SRV 0 1 7777 alice.fuckcloudnative.io. alice IN A 2.2.2.2 bob._wireguard._udp IN SRV 0 1 8888 bob.fuckcloudnative.io. bob IN A 3.3.3.3
到目前爲止,咱們一直使用別名 Alice 和 Bob 來替代其對應的 WireGuard 公鑰。WireGuard 公鑰是 Base64
編碼的,長度爲 44
字節:
$ wg genkey | wg pubkey UlVJVmPSwuG4U9BwyVILFDNlM+Gk9nQ7444HimPPgQg=
Base 64 編碼的設計是爲了以一種容許使用大寫字母和小寫字母的形式來表示任意的八位字節序列。
— RFC4648
不幸的是,DNS 的 SRV 記錄的服務名稱是不區分大小寫的:
DNS 樹中的每一個節點都有一個由零個或多個標籤組成的名稱 [STD13, RFC1591, RFC2606],這些標籤不區分大小寫。
— RFC4343
Base32
雖然產生了一個稍長的字符串(56
字節),但它的表現形式容許咱們在 DNS 內部表示 WireGuard 公鑰:
Base32 編碼的目的是爲了表示任意八位字節序列,其形式必須不區分大小寫。
咱們可使用 base64
和 base32
命令來回轉換編碼格式,例如:
$ wg genkey | wg pubkey > pub.txt $ cat pub.txt O9rAAiO5qTejOEtFbsQhCl745ovoM9coTGiprFTaHUE= $ cat pub.txt | base64 -D | base32 HPNMAARDXGUTPIZYJNCW5RBBBJPPRZUL5AZ5OKCMNCU2YVG2DVAQ==== $ cat pub.txt | base64 -D | base32 | base32 -d | base64 O9rAAiO5qTejOEtFbsQhCl745ovoM9coTGiprFTaHUE=
咱們能夠直接使用 base32
這種不區分大小寫的公鑰編碼,來使其與 DNS 兼容。
CoreDNS 提供了編寫插件的文檔,插件必需要實現 plugin.Handler
接口:
type Handler interface { ServeDNS(context.Context, dns.ResponseWriter, *dns.Msg) (int, error) Name() string }
我本身已經寫好了插件,經過 DNS-SD
(DNS-based Service Discovery)語義來提供 WireGuard 的 Peer 信息,該插件名就叫 wgsd。本身編寫的插件不屬於官方內置插件,從 CoreDNS 官方下載頁下載的可執行程序並不包括這兩個插件,因此須要本身編譯 CoreDNS。
編譯 CoreDNS 並不複雜,在沒有外部插件的狀況下能夠這麼編譯:
$ git clone https://github.com/coredns/coredns.git $ cd coredns $ make
若是要加上 wgsd 插件,則在 make
前,要修改 plugin.cfg
文件,加入如下一行:
wgsd:github.com/jwhited/wgsd
而後開始編譯:
$ go generate $ go build
查看編譯好的二進制文件是否包含該插件:
$ ./coredns -plugins | grep wgsd dns.wgsd
編譯完成後,就能夠在配置文件中啓用 wgsd
插件了:
.:53 { wgsd <zone> <wg device> }
能夠來測試一下,配置文件以下:
$ cat Corefile .:53 { debug wgsd fuckcloudnative.io. wg0 }
運行 CoreDNS:
$ ./coredns -conf Corefile .:53 CoreDNS-1.8.1 linux/amd64, go1.15,
當前節點的 WireGuard 信息:
$ sudo wg show interface: wg0 listening port: 52022 peer: mvplwow3agnGM8G78+BiJ3tmlPf9gDtbJ2NdxqV44D8= endpoint: 3.3.3.3:8888 allowed ips: 10.0.0.2/32
下面就是見證奇蹟的時候,列出全部 Peer:
$ dig @127.0.0.1 _wireguard._udp.fuckcloudnative.io. PTR +noall +answer +additional ; <<>> DiG 9.10.6 <<>> @127.0.0.1 _wireguard._udp.fuckcloudnative.io. PTR +noall +answer +additional ; (1 server found) ;; global options: +cmd _wireguard._udp.fuckcloudnative.io. 0 IN PTR TL5GLQUMG5VATRRTYG57HYDCE55WNFHX7WADWWZHMNO4NJLY4A7Q====._wireguard._udp.fuckcloudnative.io.
查詢每一個 Peer 的 IP 和端口:
$ dig @127.0.0.1 TL5GLQUMG5VATRRTYG57HYDCE55WNFHX7WADWWZHMNO4NJLY4A7Q====._wireguard._udp.fuckcloudnative.io. SRV +noall +answer +additional ; <<>> DiG 9.10.6 <<>> @127.0.0.1 TL5GLQUMG5VATRRTYG57HYDCE55WNFHX7WADWWZHMNO4NJLY4A7Q====._wireguard._udp.fuckcloudnative.io. SRV +noall +answer +additional ; (1 server found) ;; global options: +cmd tl5glqumg5vatrrtyg57hydce55wnfhx7wadwwzhmno4njly4a7q====._wireguard._udp.fuckcloudnative.io. 0 IN SRV 0 0 8888 TL5GLQUMG5VATRRTYG57HYDCE55WNFHX7WADWWZHMNO4NJLY4A7Q====.fuckcloudnative.io. TL5GLQUMG5VATRRTYG57HYDCE55WNFHX7WADWWZHMNO4NJLY4A7Q====.fuckcloudnative.io. 0 IN A 3.3.3.3
???? ???? ???? 完美!???? ???? ????
驗證公鑰是否匹配:
$ wg show wg0 peers mvplwow3agnGM8G78+BiJ3tmlPf9gDtbJ2NdxqV44D8= $ dig @127.0.0.1 _wireguard._udp.fuckcloudnative.io. PTR +short | cut -d. -f1 | base32 -d | base64 mvplwow3agnGM8G78+BiJ3tmlPf9gDtbJ2NdxqV44D8=
???? ???? ????
最終實現的通訊流程以下:
一開始,Alice 和 Bob 分別與 Registry 創建了隧道;接下來,Alice 上的 wgsd-client
向 Registry 節點上運行的 CoreDNS插件(wgsd
)發起查詢請求,該插件從 WireGuard 信息中檢索 Bob
的 endpoint 信息,並將其返回給 wgsd-client
;而後 wgsd-client
開始設置 Bob 的 endpoint;最後 Alice 和 Bob 之間直接創建了一條隧道。
任何說起 "創建隧道 "的地方都只是意味着發生了握手,數據包能夠在 Peer 之間傳輸。雖然 WireGuard 確實有一個握手機制,但它比你想象的更像是一個無鏈接的協議。
任何安全協議都須要保持一些狀態,因此最初的握手是很是簡單的,只是創建用於數據傳輸的對稱密鑰。這種握手每隔幾分鐘就會發生一次,以提供輪換密鑰來實現完美的前向保密。它是根據時間來完成的,而不是根據以前數據包的內容來完成的,由於它的設計是爲了優雅地處理數據包丟失的問題。
如今萬事俱備,只欠東風,只須要實現 wgsd-client
就完事了。
wgsd-client
負責使 Peer 的 endpoint 配置保持最新狀態,它會檢索配置中的 Peer 列表,查詢 CoreDNS 中與之匹配的公鑰,而後在須要時爲相應的 Peer 更新 endpoint 的值。最初的實現方式是以定時任務或者相似的調度機制運行,以序列化的方式檢查全部 Peer,設置 endpoint,而後退出。目前它還不是一個守護進程,後續會繼續改進優化。
wgsd-client
的源碼位於 wgsd 倉庫中的 cmd/wgsd-client 目錄。
下面開始進行最終的測試。
Alice 和 Bob 都在 NAT 後面,Registry 沒有 NAT,且有固定的公網地址。這三個 Peer 的信息以下:
Peer | Public Key | Tunnel Address |
---|---|---|
Alice | xScVkH3fUGUv4RrJFfmcqm8rs3SEHr41km6+yffAHw4= | 10.0.0.1 |
Bob | syKB97XhGnvC+kynh2KqQJPXoOoOpx/HmpMRTc+r4js= | 10.0.0.2 |
Registry | JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY= | 10.0.0.254 |
它們各自的初始配置:
$ cat /etc/wireguard/wg0.conf [Interface] Address = 10.0.0.1/32 PrivateKey = 0CtieMOYKa2RduPbJss/Um9BiQPSjgvHW+B7Mor5OnE= ListenPort = 51820 # Registry [Peer] PublicKey = JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY= Endpoint = 4.4.4.4:51820 PersistentKeepalive = 5 AllowedIPs = 10.0.0.254/32 # Bob [Peer] PublicKey = syKB97XhGnvC+kynh2KqQJPXoOoOpx/HmpMRTc+r4js= PersistentKeepalive = 5 AllowedIPs = 10.0.0.2/32 $ wg show interface: wg0 public key: xScVkH3fUGUv4RrJFfmcqm8rs3SEHr41km6+yffAHw4= private key: (hidden) listening port: 51820 peer: JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY= endpoint: 4.4.4.4:51820 allowed ips: 10.0.0.254/32 latest handshake: 48 seconds ago transfer: 1.67 KiB received, 11.99 KiB sent persistent keepalive: every 5 seconds peer: syKB97XhGnvC+kynh2KqQJPXoOoOpx/HmpMRTc+r4js= allowed ips: 10.0.0.2/32 persistent keepalive: every 5 seconds
$ cat /etc/wireguard/wg0.conf [Interface] Address = 10.0.0.2/32 PrivateKey = cIN5NqeWcbreXoaIhR/4wgrrQJGym/E7WrTttMtK8Gc= ListenPort = 51820 # Registry [Peer] PublicKey = JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY= Endpoint = 4.4.4.4:51820 PersistentKeepalive = 5 AllowedIPs = 10.0.0.254/32 # Alice [Peer] PublicKey = xScVkH3fUGUv4RrJFfmcqm8rs3SEHr41km6+yffAHw4= PersistentKeepalive = 5 AllowedIPs = 10.0.0.1/32 $ wg show interface: wg0 public key: syKB97XhGnvC+kynh2KqQJPXoOoOpx/HmpMRTc+r4js= private key: (hidden) listening port: 51820 peer: JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY= endpoint: 4.4.4.4:51820 allowed ips: 10.0.0.254/32 latest handshake: 26 seconds ago transfer: 1.54 KiB received, 11.75 KiB sent persistent keepalive: every 5 seconds peer: xScVkH3fUGUv4RrJFfmcqm8rs3SEHr41km6+yffAHw4= allowed ips: 10.0.0.1/32 persistent keepalive: every 5 seconds
$ cat /etc/wireguard/wg0.conf [Interface] Address = 10.0.0.254/32 PrivateKey = wLw2ja5AapryT+3SsBiyYVNVDYABJiWfPxLzyuiy5nE= ListenPort = 51820 # Alice [Peer] PublicKey = xScVkH3fUGUv4RrJFfmcqm8rs3SEHr41km6+yffAHw4= AllowedIPs = 10.0.0.1/32 # Bob [Peer] PublicKey = syKB97XhGnvC+kynh2KqQJPXoOoOpx/HmpMRTc+r4js= AllowedIPs = 10.0.0.2/32 $ wg show interface: wg0 public key: JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY= private key: (hidden) listening port: 51820 peer: xScVkH3fUGUv4RrJFfmcqm8rs3SEHr41km6+yffAHw4= endpoint: 2.2.2.2:41424 allowed ips: 10.0.0.1/32 latest handshake: 6 seconds ago transfer: 510.29 KiB received, 52.11 KiB sent peer: syKB97XhGnvC+kynh2KqQJPXoOoOpx/HmpMRTc+r4js= endpoint: 3.3.3.3:51820 allowed ips: 10.0.0.2/32 latest handshake: 1 minute, 46 seconds ago transfer: 498.04 KiB received, 50.59 KiB sent
Registry 與 Alice 和 Bob 都創建了鏈接,能夠直接查詢它們的 endpoint 信息:
$ dig @4.4.4.4 -p 53 _wireguard._udp.fuckcloudnative.io. PTR +noall +answer +additional ; <<>> DiG 9.10.6 <<>> @4.4.4.4 -p 53 _wireguard._udp.fuckcloudnative.io. PTR +noall +answer +additional ; (1 server found) ;; global options: +cmd _wireguard._udp.fuckcloudnative.io. 0 IN PTR YUTRLED535IGKL7BDLERL6M4VJXSXM3UQQPL4NMSN27MT56AD4HA====._wireguard._udp.fuckcloudnative.io. _wireguard._udp.fuckcloudnative.io. 0 IN PTR WMRID55V4ENHXQX2JSTYOYVKICJ5PIHKB2TR7R42SMIU3T5L4I5Q====._wireguard._udp.fuckcloudnative.io. $ dig @4.4.4.4 -p 53 YUTRLED535IGKL7BDLERL6M4VJXSXM3UQQPL4NMSN27MT56AD4HA====._wireguard._udp.fuckcloudnative.io. SRV +noall +answer +additional ; <<>> DiG 9.10.6 <<>> @4.4.4.4 -p 53 YUTRLED535IGKL7BDLERL6M4VJXSXM3UQQPL4NMSN27MT56AD4HA====._wireguard._udp.fuckcloudnative.io. SRV +noall +answer +additional ; (1 server found) ;; global options: +cmd yutrled535igkl7bdlerl6m4vjxsxm3uqqpl4nmsn27mt56ad4ha====._wireguard._udp.fuckcloudnative.io. 0 IN SRV 0 0 41424 YUTRLED535IGKL7BDLERL6M4VJXSXM3UQQPL4NMSN27MT56AD4HA====.fuckcloudnative.io. YUTRLED535IGKL7BDLERL6M4VJXSXM3UQQPL4NMSN27MT56AD4HA====.fuckcloudnative.io. 0 IN A 2.2.2.2
完美,下面分別在 Alice 和 Bob 上啓動 wgsd-client
試試:
# Alice $ ./wgsd-client -device=wg0 -dns=4.4.4.4:53 -zone=fuckcloudnative.io. 2020/05/20 13:24:02 [JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY=] no SRV records found jwhited@Alice:~$ ping 10.0.0.2 PING 10.0.0.2 (10.0.0.2): 56 data bytes 64 bytes from 10.0.0.2: icmp_seq=0 ttl=64 time=173.260 ms ^C jwhited@Alice:~$ wg show interface: wg0 public key: xScVkH3fUGUv4RrJFfmcqm8rs3SEHr41km6+yffAHw4= private key: (hidden) listening port: 51820 peer: syKB97XhGnvC+kynh2KqQJPXoOoOpx/HmpMRTc+r4js= endpoint: 3.3.3.3:51820 allowed ips: 10.0.0.2/32 latest handshake: 2 seconds ago transfer: 252 B received, 264 B sent persistent keepalive: every 5 seconds peer: JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY= endpoint: 4.4.4.4:51820 allowed ips: 10.0.0.254/32 latest handshake: 1 minute, 19 seconds ago transfer: 184 B received, 1.57 KiB sent persistent keepalive: every 5 seconds
# Bob $ ./wgsd-client -device=wg0 -dns=4.4.4.4:53 -zone=fuckcloudnative.io. 2020/05/20 13:24:04 [JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY=] no SRV records found jwhited@Bob:~$ wg show interface: wg0 public key: syKB97XhGnvC+kynh2KqQJPXoOoOpx/HmpMRTc+r4js= private key: (hidden) listening port: 51820 peer: xScVkH3fUGUv4RrJFfmcqm8rs3SEHr41km6+yffAHw4= endpoint: 2.2.2.2:41424 allowed ips: 10.0.0.1/32 latest handshake: 22 seconds ago transfer: 392 B received, 9.73 KiB sent persistent keepalive: every 5 seconds peer: JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY= endpoint: 4.4.4.4:51820 allowed ips: 10.0.0.254/32 latest handshake: 1 minute, 14 seconds ago transfer: 2.08 KiB received, 17.59 KiB sent persistent keepalive: every 5 seconds
wgsd-client
成功發現了 Peer 的 endpoint 地址並更新了 WireGuard 的配置,最終 Alice 和 Bob 之間直接創建了一條隧道!
本文探討了如何在受 NAT 限制的兩個 Peer 之間直接創建一條 WireGuard 隧道。本文提供的解決方案都是使用現有的協議和服務發現技術,以及本身寫了個可插拔的插件,你能夠直接使用 dig
或 nslookup
來進行調試,不須要干擾或修改 WireGuard 自己。
固然,這個 CoreDNS 插件確定還能夠優化,wgsd-client
也須要繼續優化。好比,CoreDNS 服務器是否應該限制只在 Registry 的隧道中可用?是否應該對域進行簽名?每次查詢 DNS 時是否都須要查詢一次 WireGuard 的 Peer 信息,仍是說能夠用緩存來解決?這些都是值得思考的問題。
wgsd 插件的代碼是開源的,歡迎你們踊躍貢獻。