Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 13398] ファイルの概要

このコミットは、Go言語のnetパッケージにおけるDNSルックアップのバグ修正に関するものです。具体的には、Cgoを使用しない環境で、AAAAレコード(IPv6アドレス)のみを持つホスト名の解決が正しく行われない問題を修正しています。

コミット

commit dfbd42e4f881717a58309d59e40671a29572dd12
Author: Michael Stapelberg <michael@stapelberg.de>
Date:   Mon Jun 25 17:32:39 2012 -0400

    net: fix lookup of AAAA-only hosts without cgo
    
    Fixes #3762.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/6303106

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/dfbd42e4f881717a58309d59e40671a29572dd12

元コミット内容

net: fix lookup of AAAA-only hosts without cgo

変更の背景

このコミットは、Go言語の標準ライブラリであるnetパッケージにおけるDNSルックアップの不具合を修正するために行われました。具体的には、GoのネイティブDNSリゾルバ(Cgoを使用しない場合)が、Aレコード(IPv4アドレス)を持たず、AAAAレコード(IPv6アドレス)のみを持つホスト名を正しく解決できないという問題(Issue 3762)に対応しています。

従来のgoLookupIP関数では、まずAレコードのルックアップを試み、その後にAAAAレコードのルックアップを行っていました。もしAレコードのルックアップが失敗した場合、たとえAAAAレコードのルックアップが成功しても、全体としてエラーが返されてしまい、AAAAレコードのみを持つホスト名が解決できないという問題がありました。これは、IPv6のみの環境や、IPv6を優先する設定のシステムにおいて、ネットワーク接続に支障をきたす可能性がありました。

前提知識の解説

DNS (Domain Name System)

DNSは、インターネット上のドメイン名(例: example.com)をIPアドレス(例: 192.0.2.12001:0db8::1)に変換するための分散型データベースシステムです。ユーザーがWebサイトにアクセスする際、ブラウザはまずDNSを使ってドメイン名に対応するIPアドレスを調べ、そのIPアドレスを使ってサーバーに接続します。

AレコードとAAAAレコード

DNSには様々な種類のリソースレコードがありますが、IPアドレスに関連する主要なものとしてAレコードとAAAAレコードがあります。

  • Aレコード (Address Record): ドメイン名に対応するIPv4アドレスを定義します。例えば、example.com192.0.2.1というIPv4アドレスを持つ場合、Aレコードとして登録されます。
  • AAAAレコード (Quad-A Record): ドメイン名に対応するIPv6アドレスを定義します。例えば、example.com2001:0db8::1というIPv6アドレスを持つ場合、AAAAレコードとして登録されます。

現代のインターネットではIPv4とIPv6が共存しており、多くのホストは両方のアドレスを持つか、あるいはどちらか一方のみを持つ場合があります。

Cgo

Cgoは、GoプログラムからC言語のコードを呼び出すためのGoの機能です。Goのnetパッケージは、DNSルックアップにおいて、Cgoを使用してシステムのリゾルバライブラリ(例: glibcのgetaddrinfo)を呼び出すことができます。しかし、Cgoを使用しないビルド(CGO_ENABLED=0)の場合、Goは独自のネイティブDNSリゾルバを使用します。このネイティブリゾルバは、Cgoに依存しないためクロスコンパイルが容易であるなどの利点がありますが、システムのリゾルバとは異なる動作をする場合があります。今回の問題は、このネイティブリゾルバの挙動に起因していました。

技術的詳細

修正前のgoLookupIP関数は、ホスト名nameに対してIPアドレスをルックアップする際に、以下の順序で処理を行っていました。

  1. lookup(name, dnsTypeA)を呼び出してAレコード(IPv4)を検索。
  2. Aレコードが見つかった場合、それらをaddrsに追加。
  3. lookup(name, dnsTypeAAAA)を呼び出してAAAAレコード(IPv6)を検索。
  4. AAAAレコードが見つかった場合、それらをaddrsに追加。

問題は、Aレコードのルックアップ(err4に対応)でエラーが発生した場合の処理にありました。修正前は、err4nilでない場合、即座にそのエラーを返していました。

// 修正前のコードの抜粋
cname, records, err = lookup(name, dnsTypeA)
if err != nil {
    return // Aレコードのルックアップでエラーがあれば、ここで終了
}
// ...
_, records, err = lookup(name, dnsTypeAAAA)
if err != nil && len(addrs) > 0 {
    // Aレコードが取得できていれば、AAAAのエラーは無視
    err = nil
}
if err != nil {
    return // AAAAのエラーがあれば、ここで終了
}

このロジックでは、もし対象のホストがAレコードを持たず、AAAAレコードのみを持つ場合、lookup(name, dnsTypeA)がエラーを返します。すると、そのエラーが即座にgoLookupIPから返されてしまい、その後のAAAAレコードのルックアップが実行されませんでした。結果として、AAAAレコードのみを持つホスト名が解決できないという問題が発生していました。

コアとなるコードの変更箇所

変更はsrc/pkg/net/dnsclient_unix.goファイルのgoLookupIP関数に集中しています。

--- a/src/pkg/net/dnsclient_unix.go
+++ b/src/pkg/net/dnsclient_unix.go
@@ -237,24 +237,30 @@ func goLookupIP(name string) (addrs []IP, err error) {
 	}
 	var records []dnsRR
 	var cname string
-	cname, records, err = lookup(name, dnsTypeA)
-	if err != nil {
-		return
-	}
+	var err4, err6 error
+	cname, records, err4 = lookup(name, dnsTypeA)
 	addrs = convertRR_A(records)
 	if cname != "" {
 		name = cname
 	}
-	_, records, err = lookup(name, dnsTypeAAAA)
-	if err != nil && len(addrs) > 0 {
-		// Ignore error because A lookup succeeded.
-		err = nil
+	_, records, err6 = lookup(name, dnsTypeAAAA)
+	if err4 != nil && err6 == nil {
+		// Ignore A error because AAAA lookup succeeded.
+		err4 = nil
 	}
-	if err != nil {
-		return
+	if err6 != nil && len(addrs) > 0 {
+		// Ignore AAAA error because A lookup succeeded.
+		err6 = nil
 	}
-	addrs = append(addrs, convertRR_AAAA(records)...)
-	return
+	if err4 != nil {
+		return nil, err4
+	}
+	if err6 != nil {
+		return nil, err6
+	}
+
+	addrs = append(addrs, convertRR_AAAA(records)...)
+	return addrs, nil
 }

コアとなるコードの解説

修正後のgoLookupIP関数では、AレコードとAAAAレコードのルックアップ結果のエラーハンドリングが改善されています。

  1. エラー変数の分離:

    • Aレコードのルックアップ結果のエラーをerr4に、AAAAレコードのルックアップ結果のエラーをerr6にそれぞれ格納するように変更されました。これにより、両方のルックアップを独立して実行し、後でエラーを評価できるようになります。
  2. エラー評価ロジックの変更:

    • if err4 != nil && err6 == nil: Aレコードのルックアップが失敗し、かつAAAAレコードのルックアップが成功した場合、Aレコードのエラー(err4)は無視されます。これは、AAAAのみを持つホストの場合に、AAAAレコードが取得できたのであれば、Aレコードの失敗は問題ではないという判断です。
    • if err6 != nil && len(addrs) > 0: AAAAレコードのルックアップが失敗し、かつAレコードのルックアップが成功してaddrsにIPアドレスが追加されている場合、AAAAレコードのエラー(err6)は無視されます。これは、Aレコードが取得できたのであれば、AAAAレコードの失敗は問題ではないという判断です。
  3. 最終的なエラーチェック:

    • 上記の条件でエラーが無視されなかった場合、つまり、AレコードとAAAAレコードの両方のルックアップが失敗し、かつどちらか一方でも成功しなかった場合にのみ、エラーが返されるようになりました。
    • if err4 != nil { return nil, err4 }
    • if err6 != nil { return nil, err6 }

この変更により、Aレコードのルックアップが失敗しても、AAAAレコードのルックアップが成功すれば、そのAAAAレコードが結果として返されるようになり、AAAAレコードのみを持つホスト名の解決が可能になりました。

関連リンク

参考にした情報源リンク