[インデックス 12954] ファイルの概要
このコミットは、Go言語のnet
パッケージにおいて、Listen
関数がladdr
(ローカルアドレス)に""
(空文字列)またはnil
が指定された場合に発生するクラッシュを修正するものです。具体的には、favoriteAddrFamily
関数におけるワイルドカードアドレスの処理ロジックを改善し、nil
アドレスの場合に適切なアドレスファミリー(IPv4)を選択するように変更しています。また、この修正を検証するための新しいテストケースが追加されています。
コミット
commit b252fe70026a0e7b4bbf3ec6f4f74cf02e3c73b7
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Wed Apr 25 12:29:14 2012 +0900
net: fix crash of Listen with "" or nil laddr
Fixes #3584.
R=dave, dsymonds, rsc
CC=golang-dev
https://golang.org/cl/6119043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b252fe70026a0e7b4bbf3ec6f4f74cf02e3c73b7
元コミット内容
net: fix crash of Listen with "" or nil laddr
Fixes #3584.
R=dave, dsymonds, rsc
CC=golang-dev
https://golang.org/cl/6119043
変更の背景
Go言語のnet
パッケージにおけるListen
系の関数(例: net.Listen
, net.ListenTCP
, net.ListenUDP
, net.ListenIP
)は、ネットワーク接続をリッスンするために使用されます。これらの関数は通常、リッスンするローカルアドレス(laddr
)を指定しますが、このladdr
に""
(空文字列)またはnil
が渡された場合に、内部でクラッシュが発生するというバグが存在していました。
この問題は、favoriteAddrFamily
関数が、laddr
がワイルドカードアドレスであるかどうかを判断するロジックにおいて、nil
が渡されたケースを適切に処理できていなかったことに起因します。特に、IPv6がサポートされている環境で、laddr
がnil
の場合にladdr.family()
を呼び出そうとすると、nil
ポインタ参照によるパニックが発生していました。このクラッシュは、アプリケーションの安定性に直接影響を与えるため、早急な修正が必要とされていました。
前提知識の解説
net
パッケージ: Go言語の標準ライブラリで、ネットワークI/O機能を提供します。TCP/UDP接続、IPアドレスの解決、DNSルックアップなど、様々なネットワーク関連の操作を行うことができます。Listen
関数:net
パッケージが提供する関数の一つで、指定されたネットワークアドレスとポートで着信接続をリッスンするためのリスナーを作成します。例えば、net.Listen("tcp", ":8080")
は、すべてのインターフェースのポート8080でTCP接続をリッスンします。laddr
(Local Address):Listen
関数に渡される引数で、リスナーがバインドするローカルネットワークアドレスを指定します。通常はIPアドレスとポート番号の組み合わせです。""
やnil
は、システムが自動的に適切なアドレスを選択することを意味する場合があります(ワイルドカードアドレス)。sockaddr
: ソケットアドレスを表すインターフェースまたは構造体です。ネットワークプログラミングにおいて、IPアドレスやポート番号などのアドレス情報を抽象化するために使用されます。isWildcard()
:sockaddr
インターフェースに定義されている可能性のあるメソッドで、そのアドレスがワイルドカードアドレス(例:0.0.0.0
や::
)であるかどうかを判定します。ワイルドカードアドレスは、特定のアドレスではなく、利用可能なすべてのインターフェースからの接続を受け入れることを意味します。syscall.AF_INET
/syscall.AF_INET6
:syscall
パッケージで定義されている定数で、それぞれIPv4アドレスファミリーとIPv6アドレスファミリーを表します。ソケットを作成する際に、どのIPプロトコルバージョンを使用するかを指定するために使われます。supportsIPv4map
: システムがIPv4-mapped IPv6アドレスをサポートしているかどうかを示すフラグです。IPv4-mapped IPv6アドレスは、IPv6ソケットでIPv4接続を処理するためのメカニズムです。runtime.GOOS
: Go言語のruntime
パッケージで提供される変数で、プログラムが実行されているオペレーティングシステム(例:"linux"
,"windows"
,"darwin"
,"plan9"
など)を示す文字列です。テストなどでOS固有の挙動を分岐させる際に利用されます。
技術的詳細
このコミットの核心は、src/pkg/net/ipsock_posix.go
内のfavoriteAddrFamily
関数の修正にあります。この関数は、与えられたネットワークタイプ、ローカルアドレス、リモートアドレス、およびモード("listen"
など)に基づいて、ソケットを作成する際に使用すべき最適なアドレスファミリー(IPv4またはIPv6)を決定します。
修正前のコードでは、mode == "listen"
かつladdr.isWildcard()
の場合に特定のロジックが適用されていました。しかし、laddr
がnil
の場合、laddr.isWildcard()
を呼び出すとnil
ポインタデリファレンスが発生し、プログラムがクラッシュしていました。これは、nil
がワイルドカードアドレスとして扱われるべきケースであるにもかかわらず、そのチェックが不十分だったためです。
修正では、この条件にladdr == nil
というチェックが追加されました。
- if mode == "listen" && laddr.isWildcard() {
+ if mode == "listen" && (laddr == nil || laddr.isWildcard()) {
これにより、laddr
がnil
の場合でも安全に処理が続行されるようになります。
さらに、laddr
がnil
の場合の具体的な挙動として、以下の新しい条件が追加されました。
+ if laddr == nil {
+ return syscall.AF_INET, false
+ }
このコードは、laddr
がnil
である場合に、明示的にsyscall.AF_INET
(IPv4)をアドレスファミリーとして返し、false
(IPv4-mapped IPv6アドレスを使用しない)を返します。これは、nil
アドレスが指定された場合に、デフォルトでIPv4のワイルドカードアドレス(0.0.0.0
)として振る舞うことを意図しています。これにより、Listen
関数がnil
または空文字列のladdr
で呼び出された際に、適切なアドレスファミリーが選択され、クラッシュが回避されます。
また、src/pkg/net/unicast_test.go
にはTestWildWildcardListener
という新しいテスト関数が追加されました。このテストは、Listen
、ListenPacket
、ListenTCP
、ListenUDP
、ListenIP
といった様々なListen
系の関数に対して、""
(空文字列)またはnil
のローカルアドレスを渡した場合に、パニックが発生しないことを確認します。defer func() { if recover() != nil { t.Fatalf("panicked") } }()
という構造により、テスト中にパニックが発生した場合にそれを捕捉し、テストを失敗させることで、修正が正しく機能していることを保証しています。runtime.GOOS
によるplan9
のスキップは、特定のOS環境でのテストの互換性を考慮したものです。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下の2つのファイルにあります。
-
src/pkg/net/ipsock_posix.go
:favoriteAddrFamily
関数の条件分岐が変更されました。- 97行目:
if mode == "listen" && laddr.isWildcard() {
- 97行目:
if mode == "listen" && (laddr == nil || laddr.isWildcard()) {
- 100行目以降に新しい条件分岐が追加されました。
- 100行目:
if laddr == nil {
- 101行目:
return syscall.AF_INET, false
- 102行目:
}
-
src/pkg/net/unicast_test.go
:TestWildWildcardListener
という新しいテスト関数が追加されました。- 536行目以降に、
Listen
系の関数に""
またはnil
を渡して呼び出すテストコードが追加されています。
コアとなるコードの解説
src/pkg/net/ipsock_posix.go
の変更
@@ -97,10 +97,13 @@ func favoriteAddrFamily(net string, laddr, raddr sockaddr, mode string) (family
if mode == "listen" && (laddr == nil || laddr.isWildcard()) {
if supportsIPv4map {
return syscall.AF_INET6, false
}
+ if laddr == nil {
+ return syscall.AF_INET, false
+ }
return laddr.family(), false
}
この変更は、favoriteAddrFamily
関数がリスニングモード(mode == "listen"
)で呼び出された際に、ローカルアドレスladdr
がワイルドカードアドレスであるかどうかの判定ロジックを改善しています。
laddr == nil || laddr.isWildcard()
: 修正前はladdr.isWildcard()
のみがチェックされていましたが、laddr
がnil
の場合にladdr.isWildcard()
を呼び出すとパニックが発生していました。この変更により、まずladdr
がnil
であるかをチェックし、nil
であれば安全に次の処理に進むことができるようになりました。nil
でない場合は、これまで通りladdr.isWildcard()
が呼び出されます。if laddr == nil { return syscall.AF_INET, false }
: この新しいブロックは、laddr
がnil
である場合に、明示的にIPv4アドレスファミリー(syscall.AF_INET
)を返すようにします。これは、nil
アドレスが指定された場合に、システムがデフォルトでIPv4のワイルドカードアドレス(0.0.0.0
)として振る舞うことを意図しています。false
は、IPv4-mapped IPv6アドレスを使用しないことを示します。これにより、Listen
関数がnil
または空文字列のladdr
で呼び出された際に、適切なアドレスファミリーが選択され、クラッシュが回避されます。
src/pkg/net/unicast_test.go
の変更
@@ -536,3 +536,33 @@ func TestProhibitionaryDialArgs(t *testing.T) {
}
}
+
+func TestWildWildcardListener(t *testing.T) {
+ switch runtime.GOOS {
+ case "plan9":
+ t.Logf("skipping test on %q", runtime.GOOS)
+ return
+ }
+
+ defer func() {
+ if recover() != nil {
+ t.Fatalf("panicked")
+ }
+ }()
+
+ if ln, err := Listen("tcp", ""); err != nil {
+ ln.Close()
+ }
+ if ln, err := ListenPacket("udp", ""); err != nil {
+ ln.Close()
+ }
+ if ln, err := ListenTCP("tcp", nil); err != nil {
+ ln.Close()
+ }
+ if ln, err := ListenUDP("udp", nil); err != nil {
+ ln.Close()
+ }
+ if ln, err := ListenIP("ip:icmp", nil); err != nil {
+ ln.Close()
+ }
+}
この新しいテスト関数TestWildWildcardListener
は、Listen
系の関数が""
またはnil
のローカルアドレスで呼び出されたときにパニックが発生しないことを検証します。
switch runtime.GOOS
:plan9
オペレーティングシステムではこのテストをスキップします。これは、plan9
環境でのネットワークスタックの挙動が他のOSと異なる可能性があるため、テストの互換性を確保するためです。defer func() { if recover() != nil { t.Fatalf("panicked") } }()
: このdefer
文は、テスト関数内でパニックが発生した場合にそれを捕捉し、テストを失敗させるためのものです。これにより、Listen
関数が""
やnil
で呼び出されたときにクラッシュしないことが保証されます。Listen("tcp", "")
/ListenPacket("udp", "")
: これらの行は、空文字列のローカルアドレスでTCPおよびUDPのリスナーを作成しようとします。ListenTCP("tcp", nil)
/ListenUDP("udp", nil)
/ListenIP("ip:icmp", nil)
: これらの行は、nil
のローカルアドレスでTCP、UDP、およびIPのリスナーを作成しようとします。
これらのテストケースは、修正が正しく機能し、""
やnil
のローカルアドレスが指定された場合でもListen
系の関数が安定して動作することを確認するために不可欠です。
関連リンク
- Go CL (Code Review) 6119043: https://golang.org/cl/6119043
参考にした情報源リンク
- GitHubコミットページ: https://github.com/golang/go/commit/b252fe70026a0e7b4bbf3ec6f4f74cf02e3c73b7
- Go言語の
net
パッケージに関する公式ドキュメント (一般的な情報源として): https://pkg.go.dev/net - Go言語の
syscall
パッケージに関する公式ドキュメント (一般的な情報源として): https://pkg.go.dev/syscall - Go言語の
runtime
パッケージに関する公式ドキュメント (一般的な情報源として): https://pkg.go.dev/runtime