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

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

このコミットは、Go言語の標準ライブラリnetパッケージにおける、C言語のgetaddrinfoシステムコール呼び出しの挙動を改善するものです。具体的には、getaddrinfoSOCK_STREAM(TCP)タイプのアドレスのみを要求するヒントを与えることで、効率性の向上と、Solaris環境での互換性問題を解決しています。

コミット

commit ba6cf63cba611d8d4602781bd8abf5bade2af3ca
Author: Ian Lance Taylor <iant@golang.org>
Date:   Fri Aug 9 09:23:27 2013 -0700

    net: give C.getaddrinfo a hint that we only want SOCK_STREAM answers
    
    This should be more efficient everywhere, and appears to be
    required on Solaris.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/12583046

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

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

元コミット内容

net: give C.getaddrinfo a hint that we only want SOCK_STREAM answers

この変更は、C.getaddrinfoに対して、SOCK_STREAM(TCP)タイプのアドレスのみを返すようにヒントを与えるものです。これにより、全体的な効率が向上し、特にSolaris環境で必要とされているようです。

変更の背景

Go言語のnetパッケージは、ネットワークアドレスの解決にC言語の標準ライブラリ関数であるgetaddrinfoを利用しています。getaddrinfoは、ホスト名やサービス名から、ネットワークアドレス構造体(addrinfo構造体)のリストを返す関数です。

従来のgetaddrinfoの呼び出しでは、特定のソケットタイプ(例: TCPかUDPか)を指定せずにアドレス情報を要求していました。この場合、getaddrinfoは通常、指定されたホスト名に対して利用可能なすべてのアドレス情報(例えば、TCP用とUDP用の両方)を返します。Goのnetパッケージでは、TCP接続を確立する際にgetaddrinfoを使用しますが、UDP用のアドレス情報も取得してしまうと、不要な情報処理が発生し、効率が低下する可能性がありました。

さらに重要な問題として、Solarisのような一部のUNIX系OSでは、getaddrinfoがデフォルトでTCPとUDPの両方のアドレスを返す際に、特定のソケットタイプを明示的に指定しないと、期待通りの動作をしない、あるいは問題を引き起こすケースがあったと考えられます。コミットメッセージにある「appears to be required on Solaris」という記述は、Solaris環境での安定性や正確な動作のために、この変更が必須であったことを示唆しています。

この変更の目的は、以下の2点に集約されます。

  1. 効率性の向上: 不要なアドレスタイプ(UDP)の情報を取得しないことで、getaddrinfoの呼び出しから返される結果セットが小さくなり、その後の処理(フィルタリングなど)のオーバーヘッドが削減されます。
  2. 互換性と安定性の確保: Solarisなどの特定のプラットフォームで、getaddrinfoの挙動が期待通りになるように修正し、潜在的な問題を回避します。

前提知識の解説

1. getaddrinfoシステムコール

getaddrinfoは、POSIX標準で定義されているネットワークプログラミングのAPIで、ホスト名やサービス名(ポート番号)から、ネットワークアドレス情報を取得するために使用されます。これは、IPv4とIPv6の両方に対応し、名前解決の複雑さを抽象化します。

getaddrinfoの主な引数と構造体は以下の通りです。

  • node: ホスト名(例: "www.example.com")またはIPアドレス文字列。
  • service: サービス名(例: "http", "ftp")またはポート番号文字列。
  • hints: addrinfo構造体へのポインタで、検索のヒント(条件)を指定します。
  • res: addrinfo構造体のリンクリストへのポインタで、結果が格納されます。

hints構造体には、以下のようなメンバーがあります。

  • ai_family: アドレスファミリー(例: AF_INET (IPv4), AF_INET6 (IPv6), AF_UNSPEC (指定なし))。
  • ai_socktype: ソケットタイプ(例: SOCK_STREAM (TCP), SOCK_DGRAM (UDP), SOCK_RAW)。
  • ai_protocol: プロトコル(例: IPPROTO_TCP, IPPROTO_UDP)。
  • ai_flags: 挙動を制御するフラグ(例: AI_PASSIVE, AI_CANONNAME)。

2. SOCK_STREAMSOCK_DGRAM

ソケットタイプは、ネットワーク通信の特性を定義します。

  • SOCK_STREAM: ストリームソケットを表し、通常はTCPプロトコルと関連付けられます。信頼性のある、コネクション指向のバイトストリーム通信を提供します。データの順序が保証され、再送処理が行われます。
  • SOCK_DGRAM: データグラムソケットを表し、通常はUDPプロトコルと関連付けられます。コネクションレスで、信頼性のないデータグラム通信を提供します。データの順序や到達は保証されません。

GoのnetパッケージがTCP接続を確立する際には、SOCK_STREAMタイプのアドレス情報のみが必要となります。

3. Cgo

Cgoは、GoプログラムからC言語のコードを呼び出すためのGoの機能です。Goのnetパッケージは、OSのネイティブな名前解決機能(getaddrinfoなど)を利用するためにCgoを使用しています。C.プレフィックスは、Cgoを通じてインポートされたC言語の型や関数を示します。

技術的詳細

このコミットの技術的変更は、src/pkg/net/cgo_unix.goファイル内のcgoLookupIPCNAME関数に集中しています。この関数は、GoのnetパッケージがCgoを介してgetaddrinfoを呼び出す際のラッパーとして機能します。

変更前は、hints構造体のai_socktypeフィールドが明示的に設定されていませんでした。これは、getaddrinfoがデフォルトで利用可能なすべてのソケットタイプ(TCPとUDPの両方)のアドレス情報を返すことを意味します。

変更後、以下の行が追加されました。

hints.ai_socktype = C.SOCK_STREAM

この行は、getaddrinfoに渡すhints構造体のai_socktypeフィールドにC.SOCK_STREAMを設定しています。これにより、getaddrinfoはTCP接続に特化したアドレス情報のみを検索し、結果として返します。

この変更の具体的な影響は以下の通りです。

  1. getaddrinfoの動作変更: getaddrinfoは、SOCK_STREAMタイプのアドレスのみをフィルタリングして返すようになります。これにより、返されるaddrinfoリンクリストのサイズが小さくなる可能性があります。
  2. ループ処理の最適化: 変更前のコードでは、getaddrinfoから返された結果をイテレートする際に、r.ai_socktype != C.SOCK_STREAMという条件で不要なUDPアドレスをスキップしていました。
    // Everything comes back twice, once for UDP and once for TCP.
    if r.ai_socktype != C.SOCK_STREAM {
        continue
    }
    
    このコメントは、getaddrinfoがTCPとUDPの両方を返すことを前提としていました。 変更後、hints.ai_socktype = C.SOCK_STREAMが設定されたため、getaddrinfoは最初からSOCK_STREAMのアドレスのみを返すことが期待されます。そのため、コメントも以下のように変更されました。
    // We only asked for SOCK_STREAM, but check anyhow.
    if r.ai_socktype != C.SOCK_STREAM {
        continue
    }
    
    新しいコメントは、「SOCK_STREAMのみを要求したが、念のためチェックする」という意図を示しています。これは、getaddrinfoの実装によっては、ヒントが厳密に適用されない場合や、予期せぬ結果が返される可能性を考慮した防御的なプログラミングスタイルです。しかし、理想的にはこのif文のcontinueパスはほとんど実行されなくなるはずです。

この変更により、getaddrinfoの呼び出し自体がより効率的になり、Goのnetパッケージが不要なアドレス情報を処理する手間が省かれるため、全体的なパフォーマンスが向上します。また、Solarisのような特定の環境での互換性問題も解決されます。

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

変更はsrc/pkg/net/cgo_unix.goファイルにあります。

--- a/src/pkg/net/cgo_unix.go
+++ b/src/pkg/net/cgo_unix.go
@@ -83,6 +83,7 @@ func cgoLookupIPCNAME(name string) (addrs []IP, cname string, err error, complet
 	var hints C.struct_addrinfo
 
 	thints.ai_flags = cgoAddrInfoFlags()
+	thints.ai_socktype = C.SOCK_STREAM
 
 	th := C.CString(name)
 	defer C.free(unsafe.Pointer(h))
@@ -109,7 +110,7 @@ func cgoLookupIPCNAME(name string) (addrs []IP, cname string, err error, complet
 		}
 	}
 	for r := res; r != nil; r = r.ai_next {
-		// Everything comes back twice, once for UDP and once for TCP.
+		// We only asked for SOCK_STREAM, but check anyhow.
 		if r.ai_socktype != C.SOCK_STREAM {
 			continue
 		}

コアとなるコードの解説

  • hints.ai_socktype = C.SOCK_STREAM: hintsC.struct_addrinfo型の変数で、getaddrinfo関数に渡す検索条件(ヒント)を格納します。この行は、hints構造体のai_socktypeフィールドにC.SOCK_STREAMという値を設定しています。C.SOCK_STREAMはC言語の定数で、ストリームソケット(通常はTCP)を意味します。これにより、getaddrinfoはTCP接続に適したアドレス情報のみを返すように動作します。

  • コメントの変更: 変更前のコメント「// Everything comes back twice, once for UDP and once for TCP.」は、getaddrinfoがTCPとUDPの両方のアドレスを返すという当時の認識を示していました。 変更後のコメント「// We only asked for SOCK_STREAM, but check anyhow.」は、ai_socktypeを設定したことでSOCK_STREAMのみを要求しているにもかかわらず、念のためai_socktypeのチェックを継続していることを示しています。これは、システムコールが常に厳密にヒントに従うとは限らない、あるいは将来的な互換性を考慮した堅牢なコード設計の一環です。

この小さな変更により、Goのネットワークスタックがアドレス解決を行う際の効率と正確性が向上し、特に特定のOS環境での安定性が確保されました。

関連リンク

参考にした情報源リンク