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

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

このコミットは、Go言語のnetパッケージにおいて、Linux環境でgetaddrinfoシステムコールが誤動作する可能性に対する防御策を導入するものです。具体的には、getaddrinfoEAI_SYSTEMエラーを返したにもかかわらず、関連するシステムエラー(errno)が適切に設定されない場合に、Goランタイムがこれを検出し、適切なエラー(syscall.EMFILE)を割り当てることで、ネットワーク操作の堅牢性を向上させています。

コミット

commit 382738af513a5390620b55a84b9e14f3afd0128e
Author: Russ Cox <rsc@golang.org>
Date:   Wed Sep 11 11:38:56 2013 -0400

    net: defend against broken getaddrinfo on Linux
    
    getaddrinfo is supposed to set errno when it returns
    EAI_SYSTEM, but sometimes it does not.
    
    Fixes #6232.
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/13532045

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

https://github.com/golang/go/commit/382738af513a5390620b55a84b9e14f3afd0128e

元コミット内容

net: defend against broken getaddrinfo on Linux

getaddrinfo is supposed to set errno when it returns
EAI_SYSTEM, but sometimes it does not.

Fixes #6232.

R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/13532045

変更の背景

この変更は、Go言語のネットワークパッケージがLinux上でgetaddrinfoシステムコールを使用する際に発生する特定の問題に対処するために行われました。getaddrinfoは、ホスト名からIPアドレスへの変換(名前解決)を行うための標準的なCライブラリ関数です。通常、この関数がシステムレベルのエラー(例えば、メモリ不足やファイルディスクリプタの枯渇など)に遭遇した場合、EAI_SYSTEMというエラーコードを返し、同時にグローバル変数errnoに具体的なシステムエラーコードを設定することが期待されています。

しかし、Goチームが発見した問題(Go issue #6232)は、Linux環境下の一部のgetaddrinfoの実装において、EAI_SYSTEMが返されるにもかかわらず、errnoが適切に設定されないケースがあるというものでした。これにより、GoのCGO(C言語との連携機能)を介してgetaddrinfoを呼び出した際に、Go側ではEAI_SYSTEMという一般的なエラーは認識できるものの、その根本原因であるシステムエラー(例えば「ファイルが多すぎる」といった具体的な情報)を特定できないという状況が発生していました。

この情報の欠如は、Goアプリケーションがネットワークエラーを適切に処理したり、デバッグ情報を出力したりする上で大きな障害となります。特に、ファイルディスクリプタの枯渇のようなシステムリソースの問題は、アプリケーション全体の安定性に影響を与える可能性があるため、正確なエラー情報の取得は非常に重要です。このコミットは、このようなgetaddrinfoの「壊れた」振る舞いからGoアプリケーションを防御し、より堅牢なエラーハンドリングを可能にすることを目的としています。

前提知識の解説

getaddrinfo

getaddrinfoは、POSIX標準で定義されているネットワークアドレス変換関数です。主に、ホスト名(例: www.example.com)やサービス名(例: http)を、ソケット通信で使用できるIPアドレスとポート番号の構造体(sockaddr構造体)に変換するために使用されます。IPv4とIPv6の両方に対応しており、DNSルックアップ、ローカルの/etc/hostsファイル参照など、様々な名前解決メカニズムを抽象化して提供します。

EAI_SYSTEM

getaddrinfo関数が返すエラーコードの一つで、名前解決処理中にシステムレベルのエラーが発生したことを示します。例えば、メモリ割り当ての失敗、ファイルディスクリプタの枯渇、ネットワークインターフェースの問題などが原因で発生する可能性があります。EAI_SYSTEMが返された場合、通常はグローバル変数errnoに、より具体的なシステムエラーコード(例: ENOMEMEMFILEなど)が設定されることが期待されます。

errno

errnoは、C言語の標準ライブラリ関数がシステムコールを呼び出した際に、エラーが発生した場合にそのエラーの種類を示すために設定するグローバル変数です。各エラーコードは、EPERM(許可されていない操作)、ENOENT(ファイルまたはディレクトリが存在しない)、EMFILE(ファイルが多すぎる)など、特定の意味を持ちます。Go言語のCGOメカニズムを通じてCライブラリ関数を呼び出す場合、Go側でこのerrnoの値を読み取ることができます。

syscall.EMFILE

EMFILEは、"Too many open files"(ファイルが多すぎる)を意味するエラーコードです。これは、プロセスが同時に開くことができるファイルディスクリプタの最大数に達したときに発生します。ファイルディスクリプタは、通常のファイルだけでなく、ネットワークソケット、パイプ、デバイスなども含みます。このエラーは、特に多数のネットワーク接続を扱うサーバーアプリケーションなどで発生しやすい問題です。Go言語のsyscallパッケージには、システムコールに関連する定数や関数が定義されており、syscall.EMFILEはその一つです。

CGO

CGOは、GoプログラムからC言語のコードを呼び出すためのGoの機能です。これにより、Goの標準ライブラリに含まれていない低レベルのシステムコールや、既存のCライブラリをGoアプリケーションから利用することが可能になります。netパッケージの一部は、プラットフォーム固有のネットワーク操作のためにCGOを利用しており、getaddrinfoの呼び出しもその一例です。CGOを介してC関数を呼び出す際、C関数が設定したerrnoの値はGo側で取得できます。

技術的詳細

このコミットが対処している技術的な問題は、Linux環境におけるgetaddrinfoの特定のバグ、または少なくともその振る舞いの不整合です。通常、getaddrinfoEAI_SYSTEMを返した場合、それはシステムコールレベルでのエラーを示唆しており、その詳細なエラーコードはerrnoに格納されるべきです。しかし、Go issue #6232で報告されたケースでは、getaddrinfoEAI_SYSTEMを返しているにもかかわらず、Go側でCGOを通じて取得されるerr(Goのエラーオブジェクト)がnilであるという矛盾した状態が発生していました。これは、getaddrinfoerrnoを適切に設定しなかったか、あるいはGoがerrnoを読み取るタイミングや方法に問題があった可能性を示唆しています。

この問題の具体的な影響は、Goのnetパッケージがgetaddrinfoからのエラーを正確に解釈できないことにありました。EAI_SYSTEMという一般的なエラーは受け取るものの、その背後にある具体的なシステムエラー(例: EMFILE)が不明なため、アプリケーションは適切なエラーハンドリングやロギングを行うことができませんでした。特に、issue #6232の報告では、この問題が「開いているファイルが多すぎる」状況(ファイルディスクリプタの枯渇)で発生したとされており、これはEMFILEエラーに相当します。

このコミットによる解決策は、この矛盾した状態を検出し、Go側で適切なエラーを「推測」して割り当てるというものです。具体的には、getaddrinfoのCGOラッパー内で、gerrnogetaddrinfoが返したエラーコード)がC.EAI_SYSTEMであり、かつGoのエラーオブジェクトerrnilであるという条件が同時に満たされた場合に、errsyscall.EMFILEを明示的に設定します。

なぜsyscall.EMFILEが選ばれたかというと、issue #6232の報告が「開いているファイルが多すぎる」という状況でこの問題が発生したと具体的に示唆していたためです。コミットメッセージにもあるように、「ほとんどのシステムコールはENFILE(ファイルが多すぎる)を返すだろうが、少なくともEMFILEは、この問題が再び発生した場合に認識しやすいだろう」という判断がありました。これは、ENFILEがシステム全体でのファイルディスクリプタの枯渇を指すのに対し、EMFILEはプロセスごとのファイルディスクリプタの枯渇を指すという違いがありますが、どちらも「ファイルが多すぎる」という根本原因を示唆しています。

この変更により、Linux上のgetaddrinfoerrnoを適切に設定しない場合でも、Goアプリケーションはより具体的なエラー情報(この場合はEMFILE)を受け取ることができるようになり、エラーハンドリングの精度とデバッグの容易さが向上しました。

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

--- a/src/pkg/net/cgo_unix.go
+++ b/src/pkg/net/cgo_unix.go
@@ -99,6 +99,16 @@ func cgoLookupIPCNAME(name string) (addrs []IP, cname string, err error, complet
 		if gerrno == C.EAI_NONAME {
 			str = noSuchHost
 		} else if gerrno == C.EAI_SYSTEM {
+			if err == nil {
+				// err should not be nil, but sometimes getaddrinfo returns
+				// gerrno == C.EAI_SYSTEM with err == nil on Linux.
+				// The report claims that it happens when we have too many
+				// open files, so use syscall.EMFILE (too many open files in system).
+				// Most system calls would return ENFILE (too many open files),
+				// so at the least EMFILE should be easy to recognize if this
+				// comes up again. golang.org/issue/6232.
+				err = syscall.EMFILE
+			}
 			str = err.Error()
 		} else {
 			str = C.GoString(C.gai_strerror(gerrno))

コアとなるコードの解説

変更はsrc/pkg/net/cgo_unix.goファイルのcgoLookupIPCNAME関数内で行われています。この関数は、CGOを介してgetaddrinfoを呼び出し、IPアドレスとCNAME(Canonical Name)をルックアップする役割を担っています。

既存のコードでは、getaddrinfoが返したエラーコードgerrnoC.EAI_SYSTEMである場合に、GoのエラーオブジェクトerrError()メソッドを呼び出してエラー文字列を取得していました。

追加されたコードブロックは、このC.EAI_SYSTEMのケースの中にネストされています。

			if err == nil {
				// err should not be nil, but sometimes getaddrinfo returns
				// gerrno == C.EAI_SYSTEM with err == nil on Linux.
				// The report claims that it happens when we have too many
				// open files, so use syscall.EMFILE (too many open files in system).
				// Most system calls would return ENFILE (too many open files),
				// so at the least EMFILE should be easy to recognize if this
				// comes up again. golang.org/issue/6232.
				err = syscall.EMFILE
			}

このif err == nilの条件が、この変更の核心です。

  1. if err == nil: ここで、Go側で取得されたエラーオブジェクトerrnil(エラーがないことを示す)であるかどうかをチェックしています。通常、gerrno == C.EAI_SYSTEMであるならば、errnilではないはずです。この矛盾した状態が、Linux上のgetaddrinfoのバグまたは不整合を示しています。
  2. コメント: 追加されたコメントは、この問題の背景と、なぜsyscall.EMFILEが選択されたかを説明しています。
    • errnilであるべきではないが、Linuxではgerrno == C.EAI_SYSTEMerr == nilとなることがある。」
    • 「報告によると、これは開いているファイルが多すぎる場合に発生するため、syscall.EMFILE(システム内の開いているファイルが多すぎる)を使用する。」
    • 「ほとんどのシステムコールはENFILE(開いているファイルが多すぎる)を返すだろうが、少なくともEMFILEは、この問題が再び発生した場合に認識しやすいだろう。golang.org/issue/6232。」
  3. err = syscall.EMFILE: errnilであるという矛盾が検出された場合、Goのエラーオブジェクトerrsyscall.EMFILEを明示的に割り当てています。これにより、Goアプリケーションは、getaddrinfoerrnoを適切に設定しなかった場合でも、「ファイルが多すぎる」という具体的なエラー情報を受け取ることができるようになります。

この変更は、Goのネットワークスタックが、下層のCライブラリの予期せぬ振る舞いに対してより堅牢になるようにするための防御的なプログラミングの一例です。

関連リンク

参考にした情報源リンク