[インデックス 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の両方のアドレスを含むaddrList
(netaddr
のリスト)を返すように変更されました。これは、DNSが複数のアドレス(特に異なるアドレスファミリーのアドレス)を返した場合に、それらをすべて保持し、上位の接続確立ロジックに渡すことを可能にします。
具体的には、以下の点が変更されています。
-
resolveInternetAddr
の変更:- DNSルックアップの結果として、
lookupHostDeadline
ではなくlookupIPDeadline
を使用するように変更されました。これにより、ホスト名からIPアドレスのリスト([]IP
)が直接取得されます。 - 取得したIPアドレスのリストを
firstFavoriteAddr
に渡すようになりました。 - 戻り値の型が、単一の
netaddr
から、複数のアドレスを保持できるnetaddr
(実際にはaddrList
型)に変更されました。 - コメントが更新され、この関数が「異なるアドレスファミリーのアドレスのペアを含むリストを返す」可能性があることが明記されました。
- DNSルックアップの結果として、
-
firstFavoriteAddr
の変更:- この関数は、以前は
[]string
型のアドレスリストを受け取っていましたが、変更後は[]IP
型のアドレスリストを受け取るようになりました。 - 最も重要な変更は、
filter
がnil
の場合の挙動です。以前は、IPv4を優先しつつも単一のnetaddr
を返していました。変更後は、IPv4アドレスとIPv6アドレスの両方が存在する場合、それらをaddrList
として返すようになりました。このaddrList
は、IPv4アドレスが先頭に来るように並べ替えられます(swap
ロジック)。これは、既存のAPIがまだIPv4を優先する振る舞いを維持しているためです。 filter
がipv4only
やipv6only
などの特定のフィルターである場合は、引き続き単一の適切なアドレスを返します。- この関数は、エラーがない限り少なくとも1つのアドレスを返すことが保証されます。
- この関数は、以前は
-
lookup.go
の変更:lookupHostMerge
がlookupIPMerge
にリネームされ、[]string
ではなく[]IP
を返すように変更されました。LookupHost
とLookupIP
の内部実装が、新しいlookupIPMerge
を使用するように調整されました。lookupHostDeadline
がlookupIPDeadline
にリネームされ、同様に[]IP
を返すように変更されました。
これらの変更により、Goのネットワークスタックは、DNSから取得した複数のIPアドレス(特にIPv4とIPv6)を内部的に保持し、将来的にHappy Eyeballsのような高度な接続選択ロジックを実装するための基盤が整えられました。現時点では、Resolve{TCP,UDP,IP}Addr
やDial
などの高レベルAPIは引き続きIPv4を優先しますが、内部的には複数のアドレスが利用可能になっています。
コアとなるコードの変更箇所
主な変更は以下のファイルに集中しています。
src/pkg/net/ipsock.go
: IPソケットアドレス解決の主要ロジックが含まれるファイル。firstFavoriteAddr
とresolveInternetAddr
関数の実装が大きく変更されています。src/pkg/net/lookup.go
: DNSルックアップ関連の関数が含まれるファイル。lookupHostMerge
がlookupIPMerge
にリネームされ、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
関数を受け取るようになりました。 filter
がnil
の場合(つまり、特定のIPv4/IPv6の制約がない場合)、この関数はIPv4とIPv6の両方のアドレスをaddrList
として収集し、返します。addrList
は、IPv4アドレスがIPv6アドレスよりも優先されるように並べ替えられます。これは、既存のGoのネットワークAPIがIPv4を優先する振る舞いを維持しているためです。addrList
の長さが1の場合は単一のnetaddr
を返し、それ以外の場合はaddrList
自体を返します。これにより、複数のアドレスを上位に渡すことが可能になります。anyaddr
関数は削除されました。これは、firstFavoriteAddr
の新しいロジックで不要になったためです。
- この関数は、IPアドレスのリスト(
resolveInternetAddr
関数の変更:- この関数は、ホスト名からIPアドレスを解決する際に、
lookupHostDeadline
の代わりにlookupIPDeadline
を使用するようになりました。これにより、DNSから解決されたIPアドレスのリスト([]IP
)が直接取得されます。 - 取得した
ips
リストは、新しいfirstFavoriteAddr
関数に渡され、複数のアドレスが返される可能性に対応しています。 - 関数のコメントが更新され、DNS名が複数のアドレスファミリーのレコードを持つ場合に、異なるアドレスファミリーのペアを含むリストを返す可能性があることが明記されました。
- この関数は、ホスト名からIPアドレスを解決する際に、
src/pkg/net/lookup.go
lookupHostMerge
からlookupIPMerge
への変更:lookupHostMerge
関数がlookupIPMerge
にリネームされました。- この関数は、ホスト名からIPアドレスのリスト(
[]IP
)を返すように変更されました。以前は文字列のスライス([]string
)を返していました。 singleflight
パッケージを使用して、同じホスト名に対する複数の同時ルックアップを単一のルックアップにマージするロジックは維持されています。
LookupHost
とLookupIP
の変更:LookupHost
は、内部的にlookupHost
(文字列のスライスを返す)を呼び出すように変更されました。LookupIP
は、内部的に新しいlookupIPMerge
(IPアドレスのリストを返す)を呼び出すように変更されました。これにより、LookupIP
がIPアドレスのリストを返すという意図がより明確になりました。
lookupHostDeadline
からlookupIPDeadline
への変更:lookupHostDeadline
関数がlookupIPDeadline
にリネームされ、同様にIPアドレスのリスト([]IP
)を返すように変更されました。
これらの変更は、Goのネットワークスタックが、DNS解決によって得られる複数のIPアドレス(特にIPv4とIPv6)をより柔軟に扱い、将来的にHappy Eyeballsのような高度な接続選択アルゴリズムを実装するための基盤を構築していることを示しています。
関連リンク
- Go言語の
net
パッケージのドキュメント: https://pkg.go.dev/net - RFC 6555: Happy Eyeballs: Success with Dual-Stack Hosts: https://datatracker.ietf.org/doc/html/rfc6555
- Goの変更リスト (CL) 13374043: https://golang.org/cl/13374043
参考にした情報源リンク
- RFC 6555 - Happy Eyeballs: Success with Dual-Stack Hosts: https://datatracker.ietf.org/doc/html/rfc6555
- RFC 8305 - Happy Eyeballs Version 2: Better Connectivity Using Concurrency (RFC 6555を廃止): https://datatracker.ietf.org/doc/html/rfc8305
- Go言語のソースコード (netパッケージ): https://github.com/golang/go/tree/master/src/net
- GoのIssue #3610: net: Happy Eyeballs: https://github.com/golang/go/issues/3610
- GoのIssue #5267: net: Happy Eyeballs for Dial: https://github.com/golang/go/issues/5267