目錄

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 的優先權等)。

大概的步驟是:

  1. DNS 解析 (Input):拿到一堆目標 IP (Candidate Destination Addresses)
    • D1: 2001:db8::1
    • D2: 74.6.231.21
  2. 執行 Source Address Selection (SAS)
    • 對應 D1 -> 選出最佳來源 S1
    • 對應 D2 -> 選出最佳來源 S2
  3. 執行 Destination Address Selection (DAS):現在我們有了 (S1, D1) 和 (S2, D2) 兩組候選配對。RFC 6724 就會開始依照規則(Scope, Precedence, Label…)來比較這兩組,決定誰排前面

RFC 6724 自己有定義十個 Rules 來排序 Destination Address,這邊簡單講一下:

  1. Rule 1: Avoid unusable destination (避開不可達的目標,例如路由表查不到的)
  2. Rule 2: Prefer matching scope (優先選 Scope 相同的,例如 Global 對 Global,Link-local 對 Link-local)
  3. Rule 3: Avoid deprecated addresses (避開被標記為廢棄的地址)
  4. Rule 4: Prefer home addresses (Mobile IPv6 專用,優先選 Home Address 而非 Care-of Address)
  5. Rule 5: Prefer matching label (關鍵規則:優先選 Label 相同的。確保 IPv6 Native 優先連 IPv6 Native,而不是透過 6to4 等過渡機制)
  6. Rule 6: Prefer higher precedence (關鍵規則:IPv6 vs IPv4 的分水嶺。查表看誰的 Precedence 高)
  7. Rule 7: Prefer native transport (優先選非封裝的傳輸方式)
  8. Rule 8: Prefer smaller scope (若 Scope 不同,優先選 Scope 小的。例如 Link-local 優於 Global)
  9. Rule 9: Use longest matching prefix (關鍵規則:與來源 IP 有最長相同前綴的優先,即「就近原則」)
  10. 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() 的任務流程是:

  1. 輸入一個 目標 IP (aio->aio_ai)
  2. 問 Kernel:「如果我要去那裡,你會用哪張網卡、哪個 IP 出去?」
  3. 把 Kernel 回答的 來源 IP 填入 aio->aio_srcsa

然後裡面的 dummy connect 會做查路由,Kernel 會去查 Routing Table,確認:「如果要送去 dest_ip,我該走哪條路?有沒有路?要用哪個 Source IP?

Reference