地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

【Go】LookupHostを使って名前解決しようとするとAとAAAAの2回クエリが飛ぶ

r := net.Resolver{}
ip, err = r.LookupHost(context.Background(), "google.com")

みたいな感じで名前解決をしようとするとAとAAAAの2回クエリが飛ぶので追ってみた。最初は以下が呼ばれる。読むのは2ファイルのみsrc/net/dnsclient_unix.go

func (r *Resolver) goLookupHost(ctx context.Context, name string) (addrs []string, err error) {
    return r.goLookupHostOrder(ctx, name, hostLookupFilesDNS)
}

次に以下が呼ばれる。具体的な名前解決のするのは次のgoLookupIPCNAMEOrder。ポイントは呼び出す際の第二引数がハードコードされているという点。

func (r *Resolver) goLookupHostOrder(ctx context.Context, name string, order hostLookupOrder) (addrs []string, err error) {
    if order == hostLookupFilesDNS || order == hostLookupFiles {
        // Use entries from /etc/hosts if they match.
        addrs = lookupStaticHost(name)
        if len(addrs) > 0 || order == hostLookupFiles {
            return
        }
    }
    ips, _, err := r.goLookupIPCNAMEOrder(ctx, "ip", name, order) // ここで文字列"ip"が固定されている
    if err != nil {
        return
    }
    addrs = make([]string, 0, len(ips))
    for _, ip := range ips {
        addrs = append(addrs, ip.String())
    }
    return
}

qtypesっていうのがクエリのタイプになるのだがデフォルトだとAとAAAAが入っていて第二引数で渡されたnetworkの値が

func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, network, name string, order hostLookupOrder) (addrs []IPAddr, cname dnsmessage.Name, err error) {
    qtypes := []dnsmessage.Type{dnsmessage.TypeA, dnsmessage.TypeAAAA}
    switch ipVersion(network) {
    case '4':
        qtypes = []dnsmessage.Type{dnsmessage.TypeA}
    case '6':
        qtypes = []dnsmessage.Type{dnsmessage.TypeAAAA}
    }

src/net/lookup.goのipVersionを見ると末尾の数字を見てIPv4なのかIPv6なのかを見ている。LookupHost経由でこの関数を呼び出す際は常にipが渡されるのでn = 0が戻る。0の場合はqtypes := []dnsmessage.Type{dnsmessage.TypeA, dnsmessage.TypeAAAA}から変わらないので常に2回クエリが飛ぶという。

func ipVersion(network string) byte {
    if network == "" {
        return 0
    }
    n := network[len(network)-1]
    if n != '4' && n != '6' {
        n = 0
    }
    return n
}

CGO_ENABLED=1とかも試したが変わらなかった。