RFC 6724 怎麼決定 DNS 解析的 IP 順序的
正文
DNS 常常用在解析 domain,獲得 domain 的 IP,常常會認為 Domain 可能最多只有一組 IPv4 and IPv6,但我們實際用指令來看 Yahoo:
kola@kola-VivoBook-ASUSLaptop-X412FAC-X412FA:~$ dig yahoo.com
; <<>> DiG 9.18.39-0ubuntu0.24.04.2-Ubuntu <<>> yahoo.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8652
;; flags: qr rd ra; QUERY: 1, ANSWER: 6, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;yahoo.com. IN A
;; ANSWER SECTION:
yahoo.com. 1800 IN A 74.6.231.21
yahoo.com. 1800 IN A 74.6.231.20
yahoo.com. 1800 IN A 98.137.11.164
yahoo.com. 1800 IN A 98.137.11.163
yahoo.com. 1800 IN A 74.6.143.26
yahoo.com. 1800 IN A 74.6.143.25
;; Query time: 247 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Tue Jan 27 19:01:37 CST 2026
;; MSG SIZE rcvd: 134你用 dig google.com 查看 Google 只會看到一個 IP,是因為 Google 利用精準的 GeoDNS 與 Anycast 技術,在 DNS 回應階段就已經完成了「最佳路徑」的選擇。Client 端能做的選擇很少,主要是決定要走 IPv4 還是 IPv6。
Yahoo 採取的是比較傳統但也非常穩健的 DNS Round-Robin (輪詢) 策略,DNS Server 一次給你一組 VIP (Virtual IP) 清單(如你看到的 6 個),它把「負載平衡」的一部分責任交給了 Client 端 (你的電腦)。
所以 Yahoo 來說,它會回傳一個 List,這時候 IP 的排序就很重要了,因為像是一般的應用程式,假設用 C 就是用 getaddrinfo(),它會直接選擇第一個 IP,所以就有 RFC 6724 提供了演算法來排序 IP,讓最好品質的 IP 排第一個,這樣會比較好。
RFC 6724
實際上我們會有決定 Source and Destination 的演算法,RFC 3484 (2003年,已廢棄) 最早定義了這兩套演算法。
RFC 6724 (2012年,現行標準):取代了 RFC 3484。它修正了舊版的一些問題(例如 Site-local address 被廢除、加入了 ULA 支援、調整了 IPv4/IPv6 的優先權等)。
大概的步驟是:
- DNS 解析 (Input):拿到一堆目標 IP (Candidate Destination Addresses)
- D1: 2001:db8::1
- D2: 74.6.231.21
- 執行 Source Address Selection (SAS)
- 對應 D1 -> 選出最佳來源 S1
- 對應 D2 -> 選出最佳來源 S2
- 執行 Destination Address Selection (DAS):現在我們有了 (S1, D1) 和 (S2, D2) 兩組候選配對。RFC 6724 就會開始依照規則(Scope, Precedence, Label…)來比較這兩組,決定誰排前面
RFC 6724 自己有定義十個 Rules 來排序 Destination Address,這邊簡單講一下:
- Rule 1: Avoid unusable destination (避開不可達的目標,例如路由表查不到的)
- Rule 2: Prefer matching scope (優先選 Scope 相同的,例如 Global 對 Global,Link-local 對 Link-local)
- Rule 3: Avoid deprecated addresses (避開被標記為廢棄的地址)
- Rule 4: Prefer home addresses (Mobile IPv6 專用,優先選 Home Address 而非 Care-of Address)
- Rule 5: Prefer matching label (關鍵規則:優先選 Label 相同的。確保 IPv6 Native 優先連 IPv6 Native,而不是透過 6to4 等過渡機制)
- Rule 6: Prefer higher precedence (關鍵規則:IPv6 vs IPv4 的分水嶺。查表看誰的 Precedence 高)
- Rule 7: Prefer native transport (優先選非封裝的傳輸方式)
- Rule 8: Prefer smaller scope (若 Scope 不同,優先選 Scope 小的。例如 Link-local 優於 Global)
- Rule 9: Use longest matching prefix (關鍵規則:與來源 IP 有最長相同前綴的優先,即「就近原則」)
- Rule 10: Otherwise, leave the order unchanged (如果前面都分不出高下,保留 DNS Server 回傳的原始順序)
FreeBSD getaddrinfo 額外的 connect
因為 RFC 6724,我們以為 DNS 解析像是 getaddrinfo() 只會跟 DNS server 做連線,實際上 getaddrinfo() 還會額外的 connect 到 DNS 解析回傳的 IP,來判斷 Rule 1 到底能不能連線 (dummy connect),可以看 freebsd-src/lib/libc/net/getaddrinfo.c getaddrinfo() 的 set_source() function,如下:
static void
set_source(struct ai_order *aio, struct policyhead *ph)
{
struct addrinfo ai = *aio->aio_ai;
struct sockaddr_storage ss;
socklen_t srclen;
int s;
/* set unspec ("no source is available"), just in case */
aio->aio_srcsa.sa_family = AF_UNSPEC;
aio->aio_srcscope = -1;
switch(ai.ai_family) {
case AF_INET:
#ifdef INET6
case AF_INET6:
#endif
break;
default: /* ignore unsupported AFs explicitly */
return;
}
/* XXX: make a dummy addrinfo to call connect() */
ai.ai_socktype = SOCK_DGRAM;
ai.ai_protocol = IPPROTO_UDP; /* is UDP too specific? */
ai.ai_next = NULL;
memset(&ss, 0, sizeof(ss));
memcpy(&ss, ai.ai_addr, ai.ai_addrlen);
ai.ai_addr = (struct sockaddr *)&ss;
get_port(&ai, "1", 0);所以 set_source() 的任務流程是:
- 輸入一個 目標 IP (aio->aio_ai)
- 問 Kernel:「如果我要去那裡,你會用哪張網卡、哪個 IP 出去?」
- 把 Kernel 回答的 來源 IP 填入 aio->aio_srcsa
然後裡面的 dummy connect 會做查路由,Kernel 會去查 Routing Table,確認:「如果要送去 dest_ip,我該走哪條路?有沒有路?要用哪個 Source IP?