[インデックス 17555] ファイルの概要
このコミットは、Go言語のnet
パッケージにおいて、Linux環境でgetaddrinfo
システムコールが誤動作する可能性に対する防御策を導入するものです。具体的には、getaddrinfo
がEAI_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
に、より具体的なシステムエラーコード(例: ENOMEM
、EMFILE
など)が設定されることが期待されます。
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
の特定のバグ、または少なくともその振る舞いの不整合です。通常、getaddrinfo
がEAI_SYSTEM
を返した場合、それはシステムコールレベルでのエラーを示唆しており、その詳細なエラーコードはerrno
に格納されるべきです。しかし、Go issue #6232で報告されたケースでは、getaddrinfo
がEAI_SYSTEM
を返しているにもかかわらず、Go側でCGOを通じて取得されるerr
(Goのエラーオブジェクト)がnil
であるという矛盾した状態が発生していました。これは、getaddrinfo
がerrno
を適切に設定しなかったか、あるいはGoがerrno
を読み取るタイミングや方法に問題があった可能性を示唆しています。
この問題の具体的な影響は、Goのnet
パッケージがgetaddrinfo
からのエラーを正確に解釈できないことにありました。EAI_SYSTEM
という一般的なエラーは受け取るものの、その背後にある具体的なシステムエラー(例: EMFILE
)が不明なため、アプリケーションは適切なエラーハンドリングやロギングを行うことができませんでした。特に、issue #6232の報告では、この問題が「開いているファイルが多すぎる」状況(ファイルディスクリプタの枯渇)で発生したとされており、これはEMFILE
エラーに相当します。
このコミットによる解決策は、この矛盾した状態を検出し、Go側で適切なエラーを「推測」して割り当てるというものです。具体的には、getaddrinfo
のCGOラッパー内で、gerrno
(getaddrinfo
が返したエラーコード)がC.EAI_SYSTEM
であり、かつGoのエラーオブジェクトerr
がnil
であるという条件が同時に満たされた場合に、err
にsyscall.EMFILE
を明示的に設定します。
なぜsyscall.EMFILE
が選ばれたかというと、issue #6232の報告が「開いているファイルが多すぎる」という状況でこの問題が発生したと具体的に示唆していたためです。コミットメッセージにもあるように、「ほとんどのシステムコールはENFILE
(ファイルが多すぎる)を返すだろうが、少なくともEMFILE
は、この問題が再び発生した場合に認識しやすいだろう」という判断がありました。これは、ENFILE
がシステム全体でのファイルディスクリプタの枯渇を指すのに対し、EMFILE
はプロセスごとのファイルディスクリプタの枯渇を指すという違いがありますが、どちらも「ファイルが多すぎる」という根本原因を示唆しています。
この変更により、Linux上のgetaddrinfo
がerrno
を適切に設定しない場合でも、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
が返したエラーコードgerrno
がC.EAI_SYSTEM
である場合に、Goのエラーオブジェクトerr
のError()
メソッドを呼び出してエラー文字列を取得していました。
追加されたコードブロックは、この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
の条件が、この変更の核心です。
if err == nil
: ここで、Go側で取得されたエラーオブジェクトerr
がnil
(エラーがないことを示す)であるかどうかをチェックしています。通常、gerrno == C.EAI_SYSTEM
であるならば、err
もnil
ではないはずです。この矛盾した状態が、Linux上のgetaddrinfo
のバグまたは不整合を示しています。- コメント: 追加されたコメントは、この問題の背景と、なぜ
syscall.EMFILE
が選択されたかを説明しています。- 「
err
はnil
であるべきではないが、Linuxではgerrno == C.EAI_SYSTEM
でerr == nil
となることがある。」 - 「報告によると、これは開いているファイルが多すぎる場合に発生するため、
syscall.EMFILE
(システム内の開いているファイルが多すぎる)を使用する。」 - 「ほとんどのシステムコールは
ENFILE
(開いているファイルが多すぎる)を返すだろうが、少なくともEMFILE
は、この問題が再び発生した場合に認識しやすいだろう。golang.org/issue/6232
。」
- 「
err = syscall.EMFILE
:err
がnil
であるという矛盾が検出された場合、Goのエラーオブジェクトerr
にsyscall.EMFILE
を明示的に割り当てています。これにより、Goアプリケーションは、getaddrinfo
がerrno
を適切に設定しなかった場合でも、「ファイルが多すぎる」という具体的なエラー情報を受け取ることができるようになります。
この変更は、Goのネットワークスタックが、下層のCライブラリの予期せぬ振る舞いに対してより堅牢になるようにするための防御的なプログラミングの一例です。
関連リンク
- Go issue #6232: https://golang.org/issue/6232
- Go CL 13532045: https://golang.org/cl/13532045
参考にした情報源リンク
getaddrinfo
man page (Linux): https://man7.org/linux/man-pages/man3/getaddrinfo.3.htmlerrno
man page (Linux): https://man7.org/linux/man-pages/man3/errno.3.htmlEMFILE
andENFILE
(Linux): https://man7.org/linux/man-pages/man3/errno.3.html (errno man page内でこれらのエラーコードも説明されています)- Go CGO documentation: https://go.dev/blog/c-go-cgo (CGOの基本的な概念について)
- Go
syscall
package documentation: https://pkg.go.dev/syscall