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

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

このコミットは、Go言語の net パッケージにおけるIPアドレス選択の挙動を改善し、特にIPv6のみのカーネル環境下での動作を修正するものです。具体的には、net/ipsock.gonet/ipsock_plan9.gonet/ipsock_posix.go の3つのファイルが変更されています。これらの変更は、システムがIPv4またはIPv6のどちらのネットワークスタックをサポートしているかをより正確に検出し、それに基づいて適切なIPアドレスを選択できるようにすることを目的としています。

コミット

commit f0291a8e10660205efb5fe8704a6a87b551973df
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Sat Aug 3 12:17:01 2013 +0900

    net: make IP address selection work correctly on IPv6-only kernel
    
    Update #3610
    Update #5267
    Update #5707
    
    R=golang-dev, bradfitz, dave, fvbommel
    CC=golang-dev
    https://golang.org/cl/11958043

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

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

元コミット内容

net: make IP address selection work correctly on IPv6-only kernel

このコミットメッセージは、IPv6のみのカーネル環境において、IPアドレスの選択が正しく機能するように修正したことを示しています。これは、特定の環境下でネットワーク接続が適切に行われない問題を解決するためのものです。

変更の背景

このコミットは、以下のGoイシュートラッカーの課題を解決するために行われました。

  • Issue #3610: net: IPv6-only kernel support このイシューは、IPv6のみのカーネル環境でGoのネットワーク機能が正しく動作しないという問題提起です。特に、IPv4アドレスが利用できない環境で、GoアプリケーションがIPv4アドレスを扱おうとしてエラーになるケースが報告されていました。
  • Issue #5267: net: IPv6-only kernel support for net.Dial net.Dial 関数がIPv6のみの環境でIPv4アドレスに接続しようとした際に失敗する問題に焦点を当てています。これは、システムがIPv4をサポートしていないにもかかわらず、Goの内部ロジックがIPv4アドレスを優先的に選択しようとすることが原因でした。
  • Issue #5707: net: IPv6-only kernel support for net.Listen net.Listen 関数がIPv6のみの環境でIPv4アドレスにバインドしようとした際に失敗する問題です。これも同様に、システムがIPv4をサポートしていない状況での不適切なアドレス選択が原因でした。

これらの問題の根本原因は、Goの net パッケージが、実行されているカーネルがIPv4とIPv6のどちらをサポートしているかを正確に判断できていなかった点にありました。特に、IPv6のみの環境では、IPv4アドレス(またはIPv4-mapped IPv6アドレス)を扱おうとすると、EAFNOSUPPORT (Address family not supported by protocol) や EPROTONOSUPPORT (Protocol not supported) といったエラーが発生し、ネットワーク操作が失敗していました。このコミットは、これらの問題を解決し、Goアプリケーションが多様なネットワーク環境(特にIPv6のみの環境)でより堅牢に動作するようにするためのものです。

前提知識の解説

このコミットを理解するためには、以下のネットワークに関する基本的な知識が必要です。

  1. IPv4とIPv6:
    • IPv4 (Internet Protocol version 4): 現在広く使われているIPアドレスのバージョンで、32ビットのアドレス空間を持ちます(例: 192.168.1.1)。アドレス枯渇問題が深刻化しています。
    • IPv6 (Internet Protocol version 6): IPv4の後継として開発されたIPアドレスのバージョンで、128ビットのアドレス空間を持ちます(例: 2001:0db8:85a3:0000:0000:8a2e:0370:7334)。IPv4のアドレス枯渇問題を解決し、より多くのデバイスをインターネットに接続できるように設計されています。
  2. IPv6-mapped IPv4アドレス (RFC 4291): IPv6アドレス空間内にIPv4アドレスを表現するための特殊な形式です。::ffff:C.D.E.F の形式で表され、IPv6スタックを持つシステムがIPv4通信を行う際に利用されることがあります。例えば、::ffff:192.168.1.1 のようになります。これは、IPv6ソケットを使用してIPv4ホストと通信するためのメカニズムを提供します。
  3. カーネルのネットワークスタック: オペレーティングシステム(OS)のカーネルには、ネットワーク通信を処理するためのソフトウェアコンポーネント(ネットワークスタック)が組み込まれています。このスタックは、IPアドレスのルーティング、TCP/UDPソケットの管理など、ネットワーク通信の基盤を提供します。システムによっては、IPv4スタックのみ、IPv6スタックのみ、または両方をサポートする場合があります。
  4. EAFNOSUPPORTEPROTONOSUPPORT エラー:
    • EAFNOSUPPORT (Address family not supported by protocol): 指定されたアドレスファミリー(例: AF_INET for IPv4, AF_INET6 for IPv6)が、プロトコル(例: TCP, UDP)によってサポートされていない場合に発生します。例えば、IPv6のみのカーネルでIPv4ソケットを作成しようとした場合など。
    • EPROTONOSUPPORT (Protocol not supported): 指定されたプロトコルがサポートされていない場合に発生します。
  5. ソケットプログラミング: ネットワーク通信を行うためのAPIで、ソケットを作成し、アドレスにバインドし、接続を確立またはリッスンする一連の操作を指します。Goの net パッケージは、これらのソケットプログラミングの抽象化を提供します。

技術的詳細

このコミットの主要な目的は、Goの net パッケージが、実行環境のカーネルがIPv4とIPv6のどちらのネットワークスタックをサポートしているかを正確に「プローブ(検出)」し、その情報に基づいてIPアドレスの選択ロジックを調整することです。

変更の核心は以下の点にあります。

  1. supportsIPv4 変数の導入: これまでのGoの net パッケージでは、supportsIPv6supportsIPv4map のみが存在し、IPv4スタック自体のサポート状況を明示的に示す変数がありませんでした。このコミットでは、supportsIPv4 という新しいグローバル変数を導入し、カーネルがIPv4ネットワーキング機能をサポートしているかどうかを追跡します。

  2. probeIPv4Stack() 関数の追加: supportsIPv4 の値を初期化するために、probeIPv4Stack() という新しい関数が追加されました。この関数は、プラットフォーム固有の実装を持ちます。

    • ipsock_posix.go (Unix系システム): syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP) を呼び出して、IPv4 TCPソケットを作成できるかどうかを試します。
      • もし EAFNOSUPPORT または EPROTONOSUPPORT エラーが返された場合、IPv4スタックはサポートされていないと判断し、false を返します。
      • エラーがなければソケットは作成できたことになるので、true を返し、作成したソケットは closesocket(s) で閉じます。
    • ipsock_plan9.go (Plan 9システム): 現状では常に true を返します。これは、Plan 9がIPv6のみのカーネルをサポートするようになった際に実装が必要であるというTODOコメントが残されています。
  3. init() 関数でのプローブ実行: net パッケージの init() 関数内で、sysInit() の後に supportsIPv4 = probeIPv4Stack() が呼び出されるようになりました。これにより、パッケージがロードされる際に、システムのIPv4サポート状況が自動的に検出されます。

  4. IPアドレス選択ロジックの改善:

    • anyaddr(ip IP) IP 関数の変更: この関数は、与えられたIPアドレスが現在のカーネル設定で使用可能かどうかを判断します。変更前は単純にIPv4アドレスを優先し、IPv6がサポートされていればIPv6アドレスも返すというロジックでした。変更後は、ipv4only(ip) を試行し、それが nil であれば ipv6only(ip) を試すという、より明示的な順序になりました。
    • ipv4only(ip IP) IP 関数の変更: この関数は、IPv4アドレス(またはIPv4-mapped IPv6アドレス)をIPv4アドレスとして返します。重要な変更点は、supportsIPv4true の場合にのみIPv4アドレスを返すようになったことです。これにより、IPv4スタックが利用できない環境では、IPv4アドレスが選択されることがなくなります。
    • ipv6only(ip IP) IP 関数の変更: この関数は、IPv6アドレスを返します。変更前は supportsIPv6 のみを見ていましたが、変更後は ip.To4() == nil (IPv4-mapped IPv6アドレスではないこと) も条件に追加されました。これにより、IPv4-mapped IPv6アドレスがIPv6アドレスとして誤って扱われることを防ぎます。

これらの変更により、Goのネットワークスタックは、実行環境のカーネルの能力をより正確に把握し、それに基づいて適切なIPアドレスを選択できるようになりました。特にIPv6のみの環境では、IPv4関連の操作を試みなくなり、前述のイシューで報告されたエラーを回避できるようになります。

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

src/pkg/net/ipsock.go

--- a/src/pkg/net/ipsock.go
+++ b/src/pkg/net/ipsock.go
@@ -8,10 +8,24 @@ package net
 
 import "time"
 
-var supportsIPv6, supportsIPv4map bool
+var (
+	// supportsIPv4 reports whether the platform supports IPv4
+	// networking functionality.
+	supportsIPv4 bool
+
+	// supportsIPv6 reports whether the platfrom supports IPv6
+	// networking functionality.
+	supportsIPv6 bool
+
+	// supportsIPv4map reports whether the platform supports
+	// mapping an IPv4 address inside an IPv6 address at transport
+	// layer protocols.  See RFC 4291, RFC 4038 and RFC 3493.
+	supportsIPv4map bool
+)
 
 func init() {
 	sysInit()
+	supportsIPv4 = probeIPv4Stack()
 	supportsIPv6, supportsIPv4map = probeIPv6Stack()
 }
 
@@ -41,23 +55,32 @@ func firstSupportedAddr(filter func(IP) IP, addrs []string) IP {
 	return nil
 }
 
-func anyaddr(x IP) IP {
-	if x4 := x.To4(); x4 != nil {
-		return x4
+// 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
 	}
-	if supportsIPv6 {
-		return x
+	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.
+func ipv4only(ip IP) IP {
+	if supportsIPv4 {
+		return ip.To4()
 	}
 	return nil
 }
 
-func ipv4only(x IP) IP { return x.To4() }
-
-func ipv6only(x IP) IP {
-	// Only return addresses that we can use
-	// with the kernel's IPv6 addressing modes.
-	if len(x) == IPv6len && x.To4() == nil && supportsIPv6 {
-		return x
+// ipv6only returns IPv6 addresses that we can use with the kernel's
+// IPv6 addressing modes.  It returns IPv4-mapped IPv6 addresses as
+// nils and returns other IPv6 address types as IPv6 addresses.
+func ipv6only(ip IP) IP {
+	if supportsIPv6 && len(ip) == IPv6len && ip.To4() == nil {
+		return ip
 	}
 	return nil
 }

src/pkg/net/ipsock_plan9.go

--- a/src/pkg/netError flushing log events: Error: getaddrinfo ENOTFOUND play.googleapis.com
    at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:120:26) {
  errno: -3008,
  code: 'ENOTFOUND',
  syscall: 'getaddrinfo',
  hostname: 'play.googleapis.com'
}
/ipsock_plan9.go
+++ b/src/pkg/net/ipsock_plan9.go
@@ -12,10 +12,18 @@ import (
 	"syscall"
 )
 
+func probeIPv4Stack() bool {
+	// TODO(mikio): implement this when Plan 9 supports IPv6-only
+	// kernel.
+	return true
+}
+
 // probeIPv6Stack returns two boolean values.  If the first boolean
 // value is true, kernel supports basic IPv6 functionality.  If the
 // second boolean value is true, kernel supports IPv6 IPv4-mapping.
 func probeIPv6Stack() (supportsIPv6, supportsIPv4map bool) {
+	// TODO(mikio): implement this once Plan 9 gets an IPv6
+	// protocol stack implementation.
 	return false, false
 }
 

src/pkg/net/ipsock_posix.go

--- a/src/pkg/net/ipsock_posix.go
+++ b/src/pkg/net/ipsock_posix.go
@@ -13,6 +13,17 @@ import (
 	"time"
 )
 
+func probeIPv4Stack() bool {
+	s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
+	switch err {
+	case syscall.EAFNOSUPPORT, syscall.EPROTONOSUPPORT:
+		return false
+	case nil:
+		closesocket(s)
+	}
+	return true
+}
+
 // Should we try to use the IPv4 socket interface if we're
 // only dealing with IPv4 sockets?  As long as the host system
 // understands IPv6, it's okay to pass IPv4 addresses to the IPv6

コアとなるコードの解説

src/pkg/net/ipsock.go

  • グローバル変数の追加: supportsIPv4 が追加され、IPv4スタックのサポート状況を保持するようになりました。
  • init() 関数の変更: probeIPv4Stack() が呼び出され、supportsIPv4 が初期化されるようになりました。これにより、Goプログラムが起動する際に、システムのIPv4サポート状況が自動的に検出されます。
  • anyaddr 関数のロジック変更:
    • 変更前は、まずIPv4アドレスを試し、次にIPv6がサポートされていればIPv6アドレスを返すという単純なものでした。
    • 変更後は、ipv4only(ip) を呼び出してIPv4アドレスが利用可能かを確認し、利用可能であればそれを返します。そうでなければ ipv6only(ip) を呼び出してIPv6アドレスが利用可能かを確認します。この順序により、システムがIPv4をサポートしていない場合にIPv4アドレスを不必要に試行することを防ぎます。
  • ipv4only 関数のロジック変更:
    • 変更前は、単に ip.To4() を呼び出してIPv4アドレスを返していました。
    • 変更後は、supportsIPv4true の場合にのみ ip.To4() を返すようになりました。これにより、IPv4スタックがカーネルでサポートされていない場合、この関数は常に nil を返し、IPv4アドレスが選択されることを防ぎます。
  • ipv6only 関数のロジック変更:
    • 変更前は、IPv6アドレスであり、かつIPv4-mappedアドレスではない場合にIPv6アドレスを返していました。
    • 変更後は、supportsIPv6true であることに加えて、len(ip) == IPv6len (IPv6アドレスの長さであること) と ip.To4() == nil (IPv4-mapped IPv6アドレスではないこと) の両方を条件としています。これにより、IPv4-mapped IPv6アドレスが純粋なIPv6アドレスとして誤って扱われることを防ぎ、より厳密なIPv6アドレスの選択が可能になります。

src/pkg/net/ipsock_plan9.go

  • probeIPv4Stack() の追加: Plan 9環境向けの probeIPv4Stack() が追加されました。現状では常に true を返しますが、将来的にPlan 9がIPv6のみのカーネルをサポートするようになった際に、より詳細な実装が必要であることがTODOコメントで示されています。
  • probeIPv6Stack() のTODOコメント更新: Plan 9のIPv6スタック実装に関するTODOコメントが更新されました。

src/pkg/net/ipsock_posix.go

  • probeIPv4Stack() の追加: Unix系システム向けの probeIPv4Stack() が追加されました。この関数は、実際にIPv4 TCPソケットの作成を試みることで、カーネルがIPv4スタックをサポートしているかどうかを動的に検出します。
    • syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP) を呼び出します。
    • 返されたエラーが syscall.EAFNOSUPPORT または syscall.EPROTONOSUPPORT であれば、IPv4はサポートされていないと判断し false を返します。
    • エラーがなければソケットは正常に作成されたとみなし true を返します。作成されたソケットは closesocket(s) で閉じられます。

これらの変更により、Goの net パッケージは、実行環境のネットワークスタックの能力をより正確に把握し、それに基づいて適切なIPアドレスを選択できるようになりました。特に、IPv6のみのカーネル環境でのネットワーク操作の信頼性が向上しました。

関連リンク

参考にした情報源リンク

  • RFC 4291: IP Version 6 Addressing Architecture
  • RFC 4038: Application Aspects of IPv6 Transition
  • RFC 3493: Basic Socket Interface Extensions for IPv6
  • Go言語の公式ドキュメントおよびソースコード
  • Unix/Linux man pages (e.g., socket(2))