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

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

このコミットは、Go言語のnetパッケージにおけるIPアドレス解決の内部的な挙動を変更するものです。具体的には、resolveInternetAddr関数が単一のIPアドレスではなく、複数のアドレス(特に異なるアドレスファミリーのIPアドレスのペア)をリストとして返すように修正されています。これは、将来的にTCP接続の高速フェイルオーバー(RFC 6555で記述されているデュアルIPスタックノードでの動作)を実装するための準備段階の変更であり、現時点ではAPIの振る舞いに変更はありません。

コミット

commit 7c59c8bdee48cecc3c38d3d10c3c794c1185db22
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Sat Aug 31 10:28:49 2013 +0900

    net: make resolveInternetAddr return a list of addresses
    
    This CL makes resolveInternetAddr return a list of addresses that
    contain a pair of different address family IP addresses if possible,
    but doesn't contain any API behavioral changes yet. A simple IP
    address selection mechanism for Resolve{TCP,UDP,IP}Addr and Dial API
    still prefers IPv4.
    
    This is in preparation for TCP connection setup with fast failover on
    dual IP stack node as described in RFC 6555.
    
    Update #3610
    Update #5267
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/13374043

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

https://github.com/golang/go/commit/7c59c8bdee48cecc3c38d3d10c3c794c1185db22

元コミット内容

net: make resolveInternetAddr return a list of addresses

この変更は、resolveInternetAddr関数が、可能であれば異なるアドレスファミリーのIPアドレスのペアを含むアドレスのリストを返すようにするものです。しかし、まだAPIの振る舞いに変更はありません。Resolve{TCP,UDP,IP}AddrおよびDial APIの単純なIPアドレス選択メカニズムは、引き続きIPv4を優先します。

これは、RFC 6555で記述されているデュアルIPスタックノードでの高速フェイルオーバーを伴うTCP接続設定の準備です。

変更の背景

このコミットの主な背景は、デュアルスタック(IPv4とIPv6の両方をサポートする)環境におけるネットワーク接続の信頼性とパフォーマンスの向上です。特に、RFC 6555「Happy Eyeballs: Success with Dual-Stack Hosts」で提唱されている「Happy Eyeballs」アルゴリズムの実装に向けた準備として行われています。

従来のデュアルスタック環境では、クライアントがIPv6アドレスとIPv4アドレスの両方を持つホストに接続しようとした際、IPv6接続が失敗したり遅延したりすると、IPv4へのフォールバックに時間がかかり、ユーザー体験が悪化するという問題がありました。これは、アプリケーションがまずIPv6での接続を試み、そのタイムアウトを待ってからIPv4を試すというシーケンシャルな挙動によるものです。

Happy Eyeballsアルゴリズムは、この問題を解決するために、IPv4とIPv6の両方で接続試行をほぼ同時に開始し、先に成功した方の接続を使用するというアプローチを取ります。これにより、片方のアドレスファミリーに問題があっても、もう片方で迅速に接続を確立できるため、ユーザーが感じる遅延を最小限に抑えることができます。

このコミットは、Goのnetパッケージが、Happy Eyeballsのようなメカニズムを効率的にサポートできるように、IPアドレス解決の内部ロジックを強化することを目的としています。具体的には、resolveInternetAddrが単一の「最適な」アドレスを返すのではなく、利用可能なアドレスのリスト(特にIPv4とIPv6のペア)を返すようにすることで、上位レイヤーでのアドレス選択と接続試行の柔軟性を高めています。

前提知識の解説

  • IPv4とIPv6: インターネットプロトコル(IP)の2つの主要なバージョン。IPv4は32ビットアドレスを使用し、IPv6は128ビットアドレスを使用します。IPv4アドレスの枯渇問題からIPv6への移行が進んでいますが、両者が混在するデュアルスタック環境が一般的です。
  • デュアルスタック (Dual-Stack): ネットワークデバイスやホストがIPv4とIPv6の両方のプロトコルスタックを同時にサポートし、両方のアドレスを持つことができる状態を指します。
  • DNS (Domain Name System): ドメイン名をIPアドレスに変換するシステム。DNSは、1つのドメイン名に対して複数のIPアドレス(IPv4とIPv6の両方)を返すことがあります。
  • アドレスファミリー (Address Family): ネットワークアドレスの種類を指します。例えば、IPv4アドレスはAF_INET、IPv6アドレスはAF_INET6というアドレスファミリーに属します。
  • RFC 6555 (Happy Eyeballs): 「Happy Eyeballs: Success with Dual-Stack Hosts」というタイトルのRFC(Request for Comments)。デュアルスタック環境での接続遅延を軽減するためのアルゴリズムを定義しています。このアルゴリズムは、IPv4とIPv6の接続試行を並行して行い、先に成功した方を使用することで、ユーザー体験を向上させます。
  • IPアドレス選択 (IP Address Selection): ホストが複数のIPアドレス(例えば、DNS解決によって得られた複数のアドレスや、自身の複数のインターフェースアドレス)を持つ場合に、どのIPアドレスを使用して通信を開始するかを決定するプロセス。RFC 6724などで詳細が定義されています。
  • 高速フェイルオーバー (Fast Failover): ネットワーク接続において、プライマリパスに障害が発生した場合やパフォーマンスが低下した場合に、迅速に代替パスに切り替えるメカニズム。Happy Eyeballsは、異なるIPアドレスファミリー間での高速フェイルオーバーの一種と見なせます。

技術的詳細

このコミットの技術的な核心は、GoのnetパッケージにおけるIPアドレス解決の内部関数であるresolveInternetAddrの戻り値の型と、それに関連するfirstFavoriteAddr関数のロジック変更にあります。

変更前、resolveInternetAddrは単一のnetaddr(IPアドレスとポートを含む抽象的なネットワークアドレス)を返していました。これは、DNSルックアップの結果から、現在のシステム設定や優先順位に基づいて「最適な」単一のアドレスを選択するという考え方に基づいています。

変更後、resolveInternetAddrは、可能であればIPv4とIPv6の両方のアドレスを含むaddrListnetaddrのリスト)を返すように変更されました。これは、DNSが複数のアドレス(特に異なるアドレスファミリーのアドレス)を返した場合に、それらをすべて保持し、上位の接続確立ロジックに渡すことを可能にします。

具体的には、以下の点が変更されています。

  1. resolveInternetAddrの変更:

    • DNSルックアップの結果として、lookupHostDeadlineではなくlookupIPDeadlineを使用するように変更されました。これにより、ホスト名からIPアドレスのリスト([]IP)が直接取得されます。
    • 取得したIPアドレスのリストをfirstFavoriteAddrに渡すようになりました。
    • 戻り値の型が、単一のnetaddrから、複数のアドレスを保持できるnetaddr(実際にはaddrList型)に変更されました。
    • コメントが更新され、この関数が「異なるアドレスファミリーのアドレスのペアを含むリストを返す」可能性があることが明記されました。
  2. firstFavoriteAddrの変更:

    • この関数は、以前は[]string型のアドレスリストを受け取っていましたが、変更後は[]IP型のアドレスリストを受け取るようになりました。
    • 最も重要な変更は、filternilの場合の挙動です。以前は、IPv4を優先しつつも単一のnetaddrを返していました。変更後は、IPv4アドレスとIPv6アドレスの両方が存在する場合、それらをaddrListとして返すようになりました。このaddrListは、IPv4アドレスが先頭に来るように並べ替えられます(swapロジック)。これは、既存のAPIがまだIPv4を優先する振る舞いを維持しているためです。
    • filteripv4onlyipv6onlyなどの特定のフィルターである場合は、引き続き単一の適切なアドレスを返します。
    • この関数は、エラーがない限り少なくとも1つのアドレスを返すことが保証されます。
  3. lookup.goの変更:

    • lookupHostMergelookupIPMergeにリネームされ、[]stringではなく[]IPを返すように変更されました。
    • LookupHostLookupIPの内部実装が、新しいlookupIPMergeを使用するように調整されました。
    • lookupHostDeadlinelookupIPDeadlineにリネームされ、同様に[]IPを返すように変更されました。

これらの変更により、Goのネットワークスタックは、DNSから取得した複数のIPアドレス(特にIPv4とIPv6)を内部的に保持し、将来的にHappy Eyeballsのような高度な接続選択ロジックを実装するための基盤が整えられました。現時点では、Resolve{TCP,UDP,IP}AddrDialなどの高レベルAPIは引き続きIPv4を優先しますが、内部的には複数のアドレスが利用可能になっています。

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

主な変更は以下のファイルに集中しています。

  • src/pkg/net/ipsock.go: IPソケットアドレス解決の主要ロジックが含まれるファイル。firstFavoriteAddrresolveInternetAddr関数の実装が大きく変更されています。
  • src/pkg/net/lookup.go: DNSルックアップ関連の関数が含まれるファイル。lookupHostMergelookupIPMergeにリネームされ、IPアドレスのリストを返すように変更されています。
  • src/pkg/net/ipsock_test.go (新規ファイル): ipsock.goの変更をテストするための新しいテストファイルが追加されています。特にTestFirstFavoriteAddrが追加され、firstFavoriteAddrの新しい挙動(特にaddrListを返すケース)が検証されています。
  • src/pkg/net/dialgoogle_test.go, src/pkg/net/ipraw_test.go, src/pkg/net/tcp_test.go, src/pkg/net/udp_test.go: 既存のテストファイルが、新しいlookupIPの利用や、resolve*Addrテスト構造体のフィールド名変更(litAddrからlitAddrOrName)に合わせて更新されています。また、localhostに対するIPv4とIPv6のデュアルスタックテストケースが追加されています。

src/pkg/net/ipsock.go の主要な変更点

--- a/src/pkg/net/ipsock.go
+++ b/src/pkg/net/ipsock.go
@@ -60,44 +60,61 @@ func (al addrList) toAddr() Addr {
 
 var errNoSuitableAddress = errors.New("no suitable address found")
 
-// firstFavoriteAddr returns an address that implemets netaddr
-// interface.
-func firstFavoriteAddr(filter func(IP) IP, addrs []string, inetaddr func(IP) netaddr) (netaddr, error) {
-	if filter == nil {
-		// We'll take any IP address, but since the dialing code
-		// does not yet try multiple addresses, prefer to use
-		// an IPv4 address if possible.  This is especially relevant
-		// if localhost resolves to [ipv6-localhost, ipv4-localhost].
-		// Too much code assumes localhost == ipv4-localhost.
-		addr, err := firstSupportedAddr(ipv4only, addrs, inetaddr)
-		if err != nil {
-			addr, err = firstSupportedAddr(anyaddr, addrs, inetaddr)
-		}
-		return addr, err
-	} else {
-		return firstSupportedAddr(filter, addrs, inetaddr)
-	}
-}
-
-func firstSupportedAddr(filter func(IP) IP, addrs []string, inetaddr func(IP) netaddr) (netaddr, error) {
-	for _, s := range addrs {
-		if ip := filter(ParseIP(s)); ip != nil {
-			return inetaddr(ip), nil
-		}
-	}
-	return nil, errNoSuitableAddress
-}
-
-// anyaddr returns IP addresses that we can use with the current
-// kernel configuration.  It returns nil when ip is not suitable for
-// the configuration and an IP address.
-func anyaddr(ip IP) IP {
-	if ip4 := ipv4only(ip); ip4 != nil {
-		return ip4
-	}
-	return ipv6only(ip)
-}
-
 // ipv4only returns IPv4 addresses that we can use with the kernel's
 // IPv4 addressing modes.  It returns IPv4-mapped IPv6 addresses as
 // IPv4 addresses and returns other IPv6 address types as nils.
@@ -212,8 +229,11 @@ func JoinHostPort(host, port string) string {
 }
 
 // resolveInternetAddr resolves addr that is either a literal IP
-// address or a DNS registered name and returns an internet protocol
-// family address.
+// address or a DNS name and returns an internet protocol family
+// address. It returns a list that contains a pair of different
+// address family addresses when addr is a DNS name and the name has
+// mutiple address family records. The result contains at least one
+// address when error is nil.
 func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error) {
 	var (
 		err              error
@@ -260,9 +280,9 @@ func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error)
 	if ip, zone = parseIPv6(host, true); ip != nil {
 		return inetaddr(ip), nil
 	}
-	// Try as a DNS registered name.
+	// Try as a DNS name.
 	host, zone = splitHostZone(host)
-	addrs, err := lookupHostDeadline(host, deadline)
+	ips, err := lookupIPDeadline(host, deadline)
 	if err != nil {
 		return nil, err
 	}
@@ -273,7 +293,7 @@ func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error)
 	if net != "" && net[len(net)-1] == '6' || zone != "" {
 		filter = ipv6only
 	}
-	return firstFavoriteAddr(filter, addrs, inetaddr)
+	return firstFavoriteAddr(filter, ips, inetaddr)
 }
 
 func zoneToString(zone int) string {

src/pkg/net/lookup.go の主要な変更点

--- a/src/pkg/net/lookup.go
+++ b/src/pkg/net/lookup.go
@@ -23,19 +23,19 @@ var protocols = map[string]int{
 
 var lookupGroup singleflight
 
-// lookupHostMerge wraps lookupHost, but makes sure that for any given
+// lookupIPMerge wraps lookupIP, but makes sure that for any given
 // host, only one lookup is in-flight at a time. The returned memory
 // is always owned by the caller.
-func lookupHostMerge(host string) (addrs []string, err error) {
+func lookupIPMerge(host string) (addrs []IP, err error) {
 	addrsi, err, shared := lookupGroup.Do(host, func() (interface{}, error) {
-\t\treturn lookupHost(host)\
+\t\treturn lookupIP(host)\
 	})\
 	if err != nil {\
 		return nil, err
 	}\
-\taddrs = addrsi.([]string)\
+\taddrs = addrsi.([]IP)\
 	if shared {\
-\t\tclone := make([]string, len(addrs))\
+\t\tclone := make([]IP, len(addrs))\
 \t\tcopy(clone, addrs)\
 \t\taddrs = clone
 	}\
@@ -45,12 +45,12 @@ func lookupHostMerge(host string) (addrs []string, err error) {
 // LookupHost looks up the given host using the local resolver.\
 // It returns an array of that host's addresses.\
 func LookupHost(host string) (addrs []string, err error) {
-\treturn lookupHostMerge(host)\
+\treturn lookupHost(host)\
 }\
 
-func lookupHostDeadline(host string, deadline time.Time) (addrs []string, err error) {
+func lookupIPDeadline(host string, deadline time.Time) (addrs []IP, err error) {
 	if deadline.IsZero() {
-\t\treturn lookupHostMerge(host)\
+\t\treturn lookupIPMerge(host)\
 	}\
 
 \t// TODO(bradfitz): consider pushing the deadline down into the\
@@ -68,12 +68,12 @@ func lookupHostDeadline(host string, deadline time.Time) (addrs []string, err er\
 \tt := time.NewTimer(timeout)\
 \tdefer t.Stop()\
 \ttype res struct {\
-\t\taddrs []string\
+\t\taddrs []IP\
 \t\terr   error\
 \t}\
 \tresc := make(chan res, 1)\
 \tgo func() {\
-\t\ta, err := lookupHostMerge(host)\
+\t\ta, err := lookupIPMerge(host)\
 \t\tresc <- res{a, err}\
 \t}()\
 \tselect {\
@@ -88,7 +88,7 @@ func lookupHostDeadline(host string, deadline time.Time) (addrs []string, err er\
 // LookupIP looks up host using the local resolver.\
 // It returns an array of that host's IPv4 and IPv6 addresses.\
 func LookupIP(host string) (addrs []IP, err error) {
-\treturn lookupIP(host)\
+\treturn lookupIPMerge(host)\
 }\
 
 // LookupPort looks up the port for the given network and service.

コアとなるコードの解説

src/pkg/net/ipsock.go

  • firstFavoriteAddr関数の変更:
    • この関数は、IPアドレスのリスト([]IP)と、アドレスをnetaddr型に変換するinetaddr関数、そしてIPアドレスをフィルタリングするfilter関数を受け取るようになりました。
    • filternilの場合(つまり、特定のIPv4/IPv6の制約がない場合)、この関数はIPv4とIPv6の両方のアドレスをaddrListとして収集し、返します。
    • addrListは、IPv4アドレスがIPv6アドレスよりも優先されるように並べ替えられます。これは、既存のGoのネットワークAPIがIPv4を優先する振る舞いを維持しているためです。
    • addrListの長さが1の場合は単一のnetaddrを返し、それ以外の場合はaddrList自体を返します。これにより、複数のアドレスを上位に渡すことが可能になります。
    • anyaddr関数は削除されました。これは、firstFavoriteAddrの新しいロジックで不要になったためです。
  • resolveInternetAddr関数の変更:
    • この関数は、ホスト名からIPアドレスを解決する際に、lookupHostDeadlineの代わりにlookupIPDeadlineを使用するようになりました。これにより、DNSから解決されたIPアドレスのリスト([]IP)が直接取得されます。
    • 取得したipsリストは、新しいfirstFavoriteAddr関数に渡され、複数のアドレスが返される可能性に対応しています。
    • 関数のコメントが更新され、DNS名が複数のアドレスファミリーのレコードを持つ場合に、異なるアドレスファミリーのペアを含むリストを返す可能性があることが明記されました。

src/pkg/net/lookup.go

  • lookupHostMergeからlookupIPMergeへの変更:
    • lookupHostMerge関数がlookupIPMergeにリネームされました。
    • この関数は、ホスト名からIPアドレスのリスト([]IP)を返すように変更されました。以前は文字列のスライス([]string)を返していました。
    • singleflightパッケージを使用して、同じホスト名に対する複数の同時ルックアップを単一のルックアップにマージするロジックは維持されています。
  • LookupHostLookupIPの変更:
    • LookupHostは、内部的にlookupHost(文字列のスライスを返す)を呼び出すように変更されました。
    • LookupIPは、内部的に新しいlookupIPMerge(IPアドレスのリストを返す)を呼び出すように変更されました。これにより、LookupIPがIPアドレスのリストを返すという意図がより明確になりました。
  • lookupHostDeadlineからlookupIPDeadlineへの変更:
    • lookupHostDeadline関数がlookupIPDeadlineにリネームされ、同様にIPアドレスのリスト([]IP)を返すように変更されました。

これらの変更は、Goのネットワークスタックが、DNS解決によって得られる複数のIPアドレス(特にIPv4とIPv6)をより柔軟に扱い、将来的にHappy Eyeballsのような高度な接続選択アルゴリズムを実装するための基盤を構築していることを示しています。

関連リンク

参考にした情報源リンク