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

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

このコミットは、Go言語の net パッケージに netaddr インターフェースを追加するものです。この新しいインターフェースは、単一のネットワークエンドポイントアドレス、またはIPアドレスの短いリストを運ぶことを目的としており、今後のコミットで導入されるダイヤルヘルパー関数で使用される予定です。これは、RFC 6555で記述されているデュアルIPスタックノードでの高速フェイルオーバーを伴うTCP接続設定の準備として行われました。

コミット

commit 3c6558ad904debb65b554baaacb1cb23ea7839d4
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Fri Aug 30 09:09:45 2013 +0900

    net: add netaddr interface
    
    This CL adds the netaddr interface that will carry a single network
    endpoint address or a short list of IP addresses to dial helper
    functions in the upcoming CLs.
    
    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/13368044

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

https://github.com/golang/go/commit/3c6558ad904debb65b554baaacb1cb23ea7839d4

元コミット内容

net: add netaddr interface

この変更は、今後のCL(Change List)でダイヤルヘルパー関数に単一のネットワークエンドポイントアドレスまたはIPアドレスの短いリストを渡すための netaddr インターフェースを追加します。

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

変更の背景

このコミットの主な背景は、デュアルスタック(IPv4とIPv6の両方をサポートする)環境におけるネットワーク接続のパフォーマンスと信頼性を向上させることです。特に、RFC 6555「Happy Eyeballs: Success with Dual-Stack Hosts」で提唱されている「高速フェイルオーバー」メカニズムをGoのネットワークスタックに組み込むための準備として、この netaddr インターフェースが導入されました。

従来のデュアルスタック環境では、クライアントアプリケーションがIPv6アドレスとIPv4アドレスの両方を持つサーバーに接続しようとした際、もしIPv6接続が失敗したり遅延したりすると、IPv4へのフォールバックまでに数秒のタイムアウトが発生し、ユーザー体験が著しく損なわれるという問題がありました。RFC 6555は、この遅延を緩和するために、IPv4とIPv6の両方で接続試行を並行して(またはわずかな時間差で)開始し、最初に成功した接続を使用するというアプローチを提案しています。これにより、ユーザーは遅延を感じることなく、利用可能な最速の接続を利用できるようになります。

このコミットは、この「Happy Eyeballs」アルゴリズムをGoの net パッケージに実装するための基盤となる変更であり、ネットワークエンドポイントアドレスをより柔軟に扱うための抽象化レイヤーとして netaddr インターフェースを導入しています。

前提知識の解説

  • デュアルスタック (Dual-Stack): ネットワークデバイスやホストがIPv4とIPv6の両方のプロトコルスタックを同時にサポートし、両方のアドレスタイプで通信できる状態を指します。
  • IPv4とIPv6:
    • IPv4 (Internet Protocol version 4): 現在広く使用されているインターネットプロトコルのバージョンで、32ビットのアドレスを使用します。アドレス枯渇の問題が指摘されています。
    • IPv6 (Internet Protocol version 6): IPv4の後継として設計されたインターネットプロトコルのバージョンで、128ビットのアドレスを使用し、より広大なアドレス空間を提供します。
  • ネットワークエンドポイントアドレス: ネットワーク上の特定のサービスやアプリケーションを識別するための情報で、通常はIPアドレスとポート番号の組み合わせで構成されます。例えば、192.168.1.1:8080[::1]:443 など。
  • RFC 6555 (Happy Eyeballs): 「Happy Eyeballs: Success with Dual-Stack Hosts」というタイトルのIETF標準ドキュメントです。デュアルスタック環境における接続遅延の問題を解決するために、IPv4とIPv6の接続試行を並行して行うことで、ユーザー体験を向上させるメカニズムを定義しています。
    • アルゴリズムの概要:
      1. クライアントがホスト名を解決し、IPv4とIPv6の両方のアドレスを取得します。
      2. 通常、優先されるアドレスファミリー(例: IPv6)のアドレスへの接続試行を開始します。
      3. もし最初の接続試行が短いタイムアウト(ブラウザでは通常300ミリ秒程度)内に完了しない場合、クライアントはもう一方のアドレスファミリー(例: IPv4)のアドレスを使用して、並行して2番目の接続試行を開始します。
      4. 最初に確立された接続が使用され、他の保留中の接続試行はキャンセルまたは破棄されます。
    • このアプローチにより、ユーザーは最速の接続を体験でき、目に見える遅延なしに動作するIPスタックに「フェイルオーバー」することができます。
  • 高速フェイルオーバー: ネットワーク接続において、障害が発生した場合やパフォーマンスが低下した場合に、別の利用可能な経路やプロトコルに迅速に切り替えるメカニズムを指します。Happy Eyeballsは、デュアルスタック環境における高速フェイルオーバーの一種です。

技術的詳細

このコミットの核心は、netaddr という新しいインターフェースの導入と、既存の Addr 型(IPAddr, TCPAddr, UDPAddr, UnixAddr)がこのインターフェースを実装するように変更された点です。

netaddr インターフェースの導入

src/pkg/net/ipsock.go に以下のインターフェースが追加されました。

// A netaddr represents a network endpoint address or a list of
// network endpoint addresses.
type netaddr interface {
	// toAddr returns the address represented in Addr interface.
	// It returns a nil interface when the address is nil.
	toAddr() Addr
}

このインターフェースは、ネットワークエンドポイントアドレス、またはアドレスのリストを抽象化することを目的としています。特に重要なのは toAddr() メソッドで、これは netaddr インターフェースを実装する型が、Goの標準的なネットワークアドレスを表す net.Addr インターフェースのインスタンスを返すことを保証します。これにより、異なる種類のアドレス(IP、TCP、UDP、Unixなど)を統一的に扱うことが可能になります。

既存の Addr 型への toAddr() メソッドの追加

IPAddr, TCPAddr, UDPAddr, UnixAddr といった既存の具体的なアドレス型に、netaddr インターフェースを満たすための toAddr() メソッドが追加されました。例えば、IPAddr の場合:

func (a *IPAddr) toAddr() Addr {
	if a == nil {
		return nil
	}
	return a
}

このメソッドは、レシーバである IPAddr 自体を net.Addr 型として返します。これは、これらのアドレス型がすでに net.Addr インターフェースを満たしているため、単純な型アサーションで実現できます。nil チェックは、レシーバが nil の場合に nil インターフェースを返すための安全策です。

firstFavoriteAddr 関数の変更

src/pkg/net/ipsock.go 内の firstFavoriteAddr 関数は、以前は IP 型を返していましたが、netaddr インターフェースを返すように変更されました。また、inetaddr という新しい引数が追加され、これは IP から netaddr を生成する関数です。

変更前:

func firstFavoriteAddr(filter func(IP) IP, addrs []string) (addr IP)
func firstSupportedAddr(filter func(IP) IP, addrs []string) IP

変更後:

func firstFavoriteAddr(filter func(IP) IP, addrs []string, inetaddr func(IP) netaddr) (netaddr, error)
func firstSupportedAddr(filter func(IP) IP, addrs []string, inetaddr func(IP) netaddr) (netaddr, error)

この変更により、firstFavoriteAddr は単なるIPアドレスだけでなく、より具体的なネットワークエンドポイントアドレス(TCPAddr, UDPAddrなど)を netaddr インターフェースとして返すことができるようになりました。これにより、Happy Eyeballsのような複数のアドレスタイプを扱うロジックが、より汎用的に実装できるようになります。

resolveInternetAddr 関数の変更

resolveInternetAddr 関数も、戻り値の型が Addr から netaddr に変更されました。また、内部で inetaddr というクロージャを定義し、これを使って IP から適切な netaddr 型を生成するように変更されています。

変更前:

func resolveInternetAddr(net, addr string, deadline time.Time) (Addr, error)

変更後:

func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error)

この変更により、resolveInternetAddr は解決されたアドレスを netaddr インターフェースとして返すようになり、呼び出し元は toAddr() メソッドを通じて具体的な Addr 型にアクセスできます。

dial.go および dial_gen.go の変更

resolveAddr 関数が Addr ではなく netaddr を返すように変更され、それに伴い dial 関数への引数も ra.toAddr() を介して Addr 型に変換されて渡されるようになりました。

// dial.go
-func resolveAddr(op, net, addr string, deadline time.Time) (Addr, error) {
+func resolveAddr(op, net, addr string, deadline time.Time) (netaddr, error) {

// dial_gen.go, fd_unix.go, fd_windows.go
-		return dial(net, addr, localAddr, ra, noDeadline)
+		return dial(net, addr, localAddr, ra.toAddr(), noDeadline)

これらの変更は、netaddr インターフェースがネットワークアドレスの抽象化レイヤーとして機能し、今後の高速フェイルオーバーの実装において、より柔軟なアドレス選択と接続試行を可能にするための基盤を構築していることを示しています。

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

このコミットにおけるコアとなるコードの変更は、主に以下のファイルに集中しています。

  1. src/pkg/net/ipsock.go:
    • netaddr インターフェースの定義。
    • firstFavoriteAddr および firstSupportedAddr 関数のシグネチャ変更と、netaddr を返すように内部ロジックの修正。
    • resolveInternetAddr 関数のシグネチャ変更と、netaddr を返すように内部ロジックの修正。
    • errNoSuitableAddress エラーの追加。
  2. src/pkg/net/iprawsock.go, src/pkg/net/tcpsock.go, src/pkg/net/udpsock.go, src/pkg/net/unixsock.go:
    • それぞれの Addr 型(IPAddr, TCPAddr, UDPAddr, UnixAddr)に toAddr() Addr メソッドを追加し、netaddr インターフェースを実装するように変更。
  3. src/pkg/net/sock_posix.go:
    • sockaddr インターフェースが netaddr インターフェースを埋め込むように変更。
    • sockaddr インターフェースから toAddr() sockaddr メソッドを削除(netaddr インターフェースの toAddr() Addr に置き換えられたため)。
  4. src/pkg/net/dial.go, src/pkg/net/dial_gen.go, src/pkg/net/fd_unix.go, src/pkg/net/fd_windows.go:
    • resolveAddr 関数の戻り値の型を Addr から netaddr に変更。
    • dial 関数を呼び出す際に、netaddr 型の変数に対して toAddr() メソッドを呼び出して Addr 型に変換してから渡すように変更。

コアとなるコードの解説

netaddr インターフェース

// src/pkg/net/ipsock.go
type netaddr interface {
	// toAddr returns the address represented in Addr interface.
	// It returns a nil interface when the address is nil.
	toAddr() Addr
}

このインターフェースは、ネットワークアドレスの抽象化を提供します。toAddr() メソッドは、netaddr を実装する任意の型が、Goの標準的なネットワークアドレスインターフェースである net.Addr に変換可能であることを保証します。これにより、異なる種類のアドレス(IP、TCP、UDP、Unixなど)を統一的に扱うことができ、特にHappy Eyeballsのような複数のアドレスタイプを考慮するロジックの実装が容易になります。

Addr 型への toAddr() メソッドの実装例 (IPAddr)

// src/pkg/net/iprawsock.go
func (a *IPAddr) toAddr() Addr {
	if a == nil {
		return nil
	}
	return a
}

IPAddrTCPAddrUDPAddrUnixAddr の各型にこの toAddr() メソッドが追加されました。これらの型は元々 net.Addr インターフェースを満たしているため、メソッドの実装はシンプルにレシーバ自身を Addr 型として返すだけです。nil チェックは、nil レシーバに対してメソッドが呼び出された場合にパニックを防ぎ、nil インターフェースを返すためのものです。

firstFavoriteAddr 関数の変更

// src/pkg/net/ipsock.go
func firstFavoriteAddr(filter func(IP) IP, addrs []string, inetaddr func(IP) netaddr) (netaddr, error) {
	// ... (既存のロジック) ...
	if filter == nil {
		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 // ここでinetaddrを使ってnetaddrを生成
		}
	}
	return nil, errNoSuitableAddress
}

firstFavoriteAddrfirstSupportedAddr は、与えられたIPアドレスのリストから、指定されたフィルター条件に合致する最初のアドレスを見つける関数です。変更点として、inetaddr func(IP) netaddr という新しい引数が追加されました。これは、見つかった IP アドレスを、具体的な netaddr 型(例: TCPAddrUDPAddr)に変換するためのファクトリ関数として機能します。これにより、これらの関数は単なるIPアドレスではなく、より完全なネットワークエンドポイントアドレスを netaddr インターフェースとして返すことができるようになりました。

resolveInternetAddr 関数の変更

// src/pkg/net/ipsock.go
func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error) {
	// ... (既存のロジック) ...
	inetaddr := func(ip IP) netaddr {
		switch net {
		case "tcp", "tcp4", "tcp6":
			return &TCPAddr{IP: ip, Port: portnum, Zone: zone}
		case "udp", "udp4", "udp6":
			return &UDPAddr{IP: ip, Port: portnum, Zone: zone}
		case "ip", "ip4", "ip6":
			return &IPAddr{IP: ip, Zone: zone}
		default:
			panic("unexpected network: " + net)
		}
	}
	// ... (IPアドレスのパースとDNSルックアップのロジック) ...
	return firstFavoriteAddr(filter, addrs, inetaddr) // inetaddrを渡してnetaddrを取得
}

resolveInternetAddr は、ホスト名とポート番号からインターネットアドレスを解決する関数です。この関数内で inetaddr というクロージャが定義され、これは引数として受け取った IP アドレスと、解決されたポート番号やゾーン情報に基づいて、適切な TCPAddrUDPAddr、または IPAddr のインスタンスを netaddr 型として返します。最終的に、firstFavoriteAddr を呼び出す際にこの inetaddr クロージャを渡すことで、解決されたIPアドレスから具体的な netaddr 型のインスタンスを取得し、それを呼び出し元に返します。

これらの変更は、Goのネットワークスタックが、Happy Eyeballsのような高度な接続ロジックをサポートするために、アドレスの表現と処理をより柔軟かつ抽象的に行うための重要なステップです。

関連リンク

参考にした情報源リンク