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

[インデックス 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がサポートされている環境で、laddrnilの場合に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()の場合に特定のロジックが適用されていました。しかし、laddrnilの場合、laddr.isWildcard()を呼び出すとnilポインタデリファレンスが発生し、プログラムがクラッシュしていました。これは、nilがワイルドカードアドレスとして扱われるべきケースであるにもかかわらず、そのチェックが不十分だったためです。

修正では、この条件にladdr == nilというチェックが追加されました。

-	if mode == "listen" && laddr.isWildcard() {
+	if mode == "listen" && (laddr == nil || laddr.isWildcard()) {

これにより、laddrnilの場合でも安全に処理が続行されるようになります。

さらに、laddrnilの場合の具体的な挙動として、以下の新しい条件が追加されました。

+		if laddr == nil {
+			return syscall.AF_INET, false
+		}

このコードは、laddrnilである場合に、明示的にsyscall.AF_INET(IPv4)をアドレスファミリーとして返し、false(IPv4-mapped IPv6アドレスを使用しない)を返します。これは、nilアドレスが指定された場合に、デフォルトでIPv4のワイルドカードアドレス(0.0.0.0)として振る舞うことを意図しています。これにより、Listen関数がnilまたは空文字列のladdrで呼び出された際に、適切なアドレスファミリーが選択され、クラッシュが回避されます。

また、src/pkg/net/unicast_test.goにはTestWildWildcardListenerという新しいテスト関数が追加されました。このテストは、ListenListenPacketListenTCPListenUDPListenIPといった様々なListen系の関数に対して、""(空文字列)またはnilのローカルアドレスを渡した場合に、パニックが発生しないことを確認します。defer func() { if recover() != nil { t.Fatalf("panicked") } }()という構造により、テスト中にパニックが発生した場合にそれを捕捉し、テストを失敗させることで、修正が正しく機能していることを保証しています。runtime.GOOSによるplan9のスキップは、特定のOS環境でのテストの互換性を考慮したものです。

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

このコミットにおける主要なコード変更は以下の2つのファイルにあります。

  1. 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行目: }
  2. 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()のみがチェックされていましたが、laddrnilの場合にladdr.isWildcard()を呼び出すとパニックが発生していました。この変更により、まずladdrnilであるかをチェックし、nilであれば安全に次の処理に進むことができるようになりました。nilでない場合は、これまで通りladdr.isWildcard()が呼び出されます。
  • if laddr == nil { return syscall.AF_INET, false }: この新しいブロックは、laddrnilである場合に、明示的に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系の関数が安定して動作することを確認するために不可欠です。

関連リンク

参考にした情報源リンク