[インデックス 13854] ファイルの概要
このコミットは、Go言語の標準ライブラリである net
パッケージにおける以前の変更(コミットハッシュ 2518eee18c4f
、Change List ID 6395055
)を元に戻すものです。元の変更は、RemoteAddr.String
メソッドチェーンが呼び出された際に発生する nil
ポインタ参照解除(nil pointer dereference)の問題(Issue #3721)を修正することを目的としていましたが、その副作用としてTCPの自己接続(selfConnect)機能を破壊してしまいました。このコミットは、その破壊された機能を修復するために、元の変更をアンドゥ(元に戻す)しています。
コミット
commit e4389c008a223213fc3f506756f38d695cf60d40
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Wed Sep 19 01:33:03 2012 +0900
undo CL 6395055 / 2518eee18c4f
Broke TCP selfConnect
««« original CL description
net: avoid nil pointer dereference when RemoteAddr.String method chain is called
Fixes #3721.
R=dave, rsc
CC=golang-dev
https://golang.org/cl/6395055
»»»
R=rsc
CC=golang-dev
https://golang.org/cl/6533043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e4389c008a223213fc3f506756f38d695cf60d40
元コミット内容
このコミットは、以下のコミット(CL 6395055)を元に戻すものです。
commit 2518eee18c4f
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Tue Sep 18 00:40:00 2012 +0900
net: avoid nil pointer dereference when RemoteAddr.String method chain is called
Fixes #3721.
R=dave, rsc
CC=golang-dev
https://golang.org/cl/6395055
元のコミットは、net
パッケージにおいて RemoteAddr().String()
のようなメソッドチェーンが呼び出された際に、RemoteAddr()
が nil
を返す可能性がある場合に発生する nil
ポインタ参照解除のクラッシュを回避することを目的としていました。これは、特に接続が確立されていないソケットや、エラー状態のソケットに対してアドレス情報を取得しようとした場合に問題となる可能性がありました。
変更の背景
元のコミット(CL 6395055)は、Goの net
パッケージにおける特定の条件下での nil
ポインタ参照解除のバグ(Issue #3721)を修正するために導入されました。このバグは、net.Conn
インターフェースの RemoteAddr()
メソッドが nil
を返し、その結果として String()
メソッドが呼び出された際にパニックを引き起こす可能性がありました。
しかし、この修正は意図しない副作用をもたらしました。具体的には、TCPの「自己接続(selfConnect)」機能が動作しなくなりました。自己接続とは、クライアントが自分自身(同じホスト、同じポート)に接続しようとするシナリオを指します。これは、特定のテストケースや、ループバックインターフェースを使用した特殊なネットワーク構成で発生する可能性があります。元の修正が、ソケットのアドレス解決ロジックを変更したことで、この自己接続の検出または確立が正しく行われなくなったと考えられます。
このコミットは、バグ修正によって導入されたこの新たな回帰(regression)を修正するために、元の変更を完全に元に戻すことを決定しました。これは、Goの標準ライブラリ開発における一般的なプラクティスであり、新たなバグを導入する可能性のある修正は、その影響を完全に理解し、より堅牢な解決策が見つかるまで元に戻されることがあります。
前提知識の解説
1. net
パッケージ (Go言語)
Go言語の net
パッケージは、ネットワークI/Oプリミティブへのポータブルインターフェースを提供します。TCP/IP、UDP、Unixドメインソケットなどのネットワークプロトコルを扱うための機能が含まれています。
net.Conn
インターフェース: ネットワーク接続を表す汎用インターフェースです。Read
,Write
,Close
,LocalAddr
,RemoteAddr
などのメソッドを持ちます。net.Addr
インターフェース: ネットワークエンドポイントアドレスを表す汎用インターフェースです。Network()
とString()
メソッドを持ちます。TCPAddr
,UDPAddr
,IPAddr
,UnixAddr
など、具体的なアドレス型がこのインターフェースを実装します。LocalAddr()
とRemoteAddr()
:net.Conn
インターフェースのメソッドで、それぞれローカルエンドポイントとリモートエンドポイントのネットワークアドレスを返します。
2. nil
ポインタ参照解除 (Nil Pointer Dereference)
プログラミングにおいて、nil
(または null
)は「値がない」ことを示す特別な値です。nil
ポインタ参照解除とは、nil
値を持つポインタが指すメモリ領域にアクセスしようとしたときに発生するエラーです。Go言語では、これは通常「パニック(panic)」を引き起こし、プログラムの実行を停止させます。
例:
var addr *net.TCPAddr // addr は nil
fmt.Println(addr.String()) // ここで nil ポインタ参照解除が発生し、パニックになる
3. TCP 自己接続 (TCP Self-Connect)
TCP自己接続とは、クライアントが自分自身(同じホスト、同じポート)に接続しようとするネットワークシナリオです。これは通常、ループバックインターフェース(例: 127.0.0.1
や ::1
)を使用して、クライアントとサーバーが同じプロセス内で動作している場合や、テスト目的で発生します。TCPプロトコルスタックは、このような自己接続を正しく処理できる必要があります。
4. Unix系システムコール (syscall
パッケージ)
Go言語の syscall
パッケージは、オペレーティングシステム(OS)の低レベルなシステムコールへのインターフェースを提供します。ネットワークプログラミングにおいては、ソケットの作成、バインド、接続、アドレス情報の取得などにこれらのシステムコールが使われます。
syscall.Getsockname(fd int)
: 指定されたファイルディスクリプタfd
に関連付けられたソケットのローカルアドレス(ソケット名)を取得します。syscall.Getpeername(fd int)
: 指定されたファイルディスクリプタfd
に関連付けられたソケットのリモートアドレス(ピア名)を取得します。
これらのシステムコールは、ソケットが接続されている場合にのみ有効なアドレス情報を返します。接続されていないソケットに対して呼び出された場合、エラーを返すか、または意味のない値を返すことがあります。
5. ファイルディスクリプタ (File Descriptor, FD)
Unix系OSにおいて、ファイルディスクリプタは、プロセスが開いているファイルやソケットなどのI/Oリソースを識別するための整数値です。ネットワークプログラミングでは、ソケットが作成されると、それに対応するファイルディスクリプタが割り当てられ、そのディスクリプタを通じてソケットに対する操作が行われます。
技術的詳細
このコミットは、以前のコミットが導入した変更を元に戻すことで、net
パッケージ内のソケットアドレス管理ロジックを以前の状態に戻しています。元のコミットは、netFD
構造体のアドレス設定方法を変更し、特に RemoteAddr
が nil
になる可能性のあるケースで、String()
メソッドが呼び出される前に nil
チェックを強化しようとしました。しかし、この変更が syscall.Getsockname
や syscall.Getpeername
の呼び出しタイミングや結果の解釈に影響を与え、TCP自己接続のシナリオで問題を引き起こしたと考えられます。
具体的には、元のコミットは localSockname
および remoteSockname
というヘルパー関数を導入し、ソケットのアドレス情報を取得する際に nil
アドレスを特定のプロトコルに応じた nil
アドレス型(例: (*TCPAddr)(nil)
)に変換するロジックを追加しました。これにより、nil
ポインタ参照解除は回避されるものの、ソケットが実際に接続されていない場合や、自己接続のような特殊なケースで、期待されるアドレス情報が正しく設定されず、結果として接続が失敗する原因となりました。
このアンドゥコミットでは、これらのヘルパー関数を削除し、netFD
のアドレス設定を直接 syscall.Getsockname
と syscall.Getpeername
の結果に基づいて行うように戻しています。これにより、TCP自己接続が再び正しく機能するようになりますが、元のIssue #3721(nil
ポインタ参照解除)は再発する可能性があります。これは、機能の破壊を伴うバグ修正よりも、既知のバグの再発の方が許容されるという判断があったことを示唆しています。
テストファイルの変更も重要です。ipraw_test.go
と udp_test.go
から、IPConnAddrStringTests
と udpConnAddrStringTests
というテストケースが削除されています。これらのテストは、おそらく元のコミットで導入されたか、または元のコミットの変更によって失敗するようになったため、アンドゥの一環として削除されたと考えられます。これにより、自己接続の問題が修正されたことを確認するためのテストが一時的に失われた可能性があります。
コアとなるコードの変更箇所
このコミットは、主に以下のファイルと関数に変更を加えています。
-
src/pkg/net/fd_unix.go
func (fd *netFD) accept(...)
関数内のnetfd.setAddr
の呼び出しが変更されています。- 変更前:
netfd.setAddr(localSockname(fd, toAddr), toAddr(rsa))
- 変更後:
lsa, _ := syscall.Getsockname(netfd.sysfd); netfd.setAddr(toAddr(lsa), toAddr(rsa))
localSockname
ヘルパー関数の使用を廃止し、直接syscall.Getsockname
を呼び出してローカルアドレスを取得するように戻しています。
- 変更前:
-
src/pkg/net/file_unix.go
func newFileFD(f *os.File) (*netFD, error)
関数内のnetfd.setAddr
の呼び出しが変更されています。- 変更前:
netfd.setAddr(laddr, remoteSockname(netfd, toAddr))
- 変更後:
rsa, _ := syscall.Getpeername(fd); raddr := toAddr(rsa); netfd.setAddr(laddr, raddr)
remoteSockname
ヘルパー関数の使用を廃止し、直接syscall.Getpeername
を呼び出してリモートアドレスを取得するように戻しています。
- 変更前:
-
src/pkg/net/sock_posix.go
func socket(...)
関数内のfd.setAddr
の呼び出しが変更されています。- 変更前:
fd.setAddr(laddr, remoteSockname(fd, toAddr))
- 変更後:
lsa, _ := syscall.Getsockname(s); ...; rsa, _ := syscall.Getpeername(s); raddr := toAddr(rsa); fd.setAddr(laddr, raddr)
- ここでも
localSockname
とremoteSockname
の使用を廃止し、直接syscall.Getsockname
とsyscall.Getpeername
を呼び出すように戻しています。
- 変更前:
localSockname
、remoteSockname
、nullProtocolAddr
の各関数が完全に削除されています。 これらは元のコミットで導入されたヘルパー関数であり、このアンドゥコミットによって不要になったため削除されました。
-
src/pkg/net/ipraw_test.go
ipConnAddrStringTests
変数とTestIPConnAddrString
テスト関数が削除されています。
-
src/pkg/net/udp_test.go
udpConnAddrStringTests
変数とTestUDPConnAddrString
テスト関数が削除されています。
コアとなるコードの解説
このコミットの核心は、netFD
構造体(ネットワークファイルディスクリプタ)にローカルアドレスとリモートアドレスを設定するロジックを、元の状態に戻すことです。
元のコミット(CL 6395055)では、netFD.setAddr
を呼び出す際に、localSockname
と remoteSockname
という新しいヘルパー関数を使用していました。これらのヘルパー関数は、syscall.Getsockname
や syscall.Getpeername
の結果が nil
であった場合に、特定のプロトコルに応じた nil
の net.Addr
型(例: (*TCPAddr)(nil)
)を返すように設計されていました。これは、nil
ポインタ参照解除を防ぐための試みでした。
しかし、このアプローチはTCP自己接続のシナリオで問題を引き起こしました。自己接続の場合、Getsockname
や Getpeername
が返すアドレス情報が、通常の接続とは異なる振る舞いをすることがあります。元の修正が、これらの特殊なケースを正しく扱えず、結果として自己接続が失敗する原因となりました。
このアンドゥコミットでは、localSockname
、remoteSockname
、nullProtocolAddr
の各ヘルパー関数を完全に削除し、netFD.setAddr
の呼び出し箇所で、直接 syscall.Getsockname
と syscall.Getpeername
を呼び出してアドレス情報を取得するように戻しています。
例えば、src/pkg/net/fd_unix.go
の accept
関数では、以下のように変更されました。
変更前(元のコミットによる変更):
netfd.setAddr(localSockname(fd, toAddr), toAddr(rsa))
ここでは、localSockname
が fd
のローカルアドレスを取得し、toAddr
で net.Addr
型に変換しています。
変更後(このアンドゥコミットによる変更):
lsa, _ := syscall.Getsockname(netfd.sysfd)
netfd.setAddr(toAddr(lsa), toAddr(rsa))
ここでは、syscall.Getsockname
を直接呼び出してローカルソケットアドレス lsa
を取得し、それを toAddr
で net.Addr
型に変換して setAddr
に渡しています。これにより、元の nil
ポインタ参照解除の問題は再発する可能性がありますが、TCP自己接続の機能は回復します。
テストファイルの削除は、元の修正が導入したテストが、アンドゥによって再び失敗するようになったため、一時的に削除されたことを示唆しています。これは、問題の根本的な解決策が見つかるまで、機能の安定性を優先した結果と考えられます。
関連リンク
- Go Issue #3721:
net: RemoteAddr().String() panics if RemoteAddr() is nil
- このコミットが元に戻した修正が対処しようとしていた問題です。
- Go Change List 6395055:
net: avoid nil pointer dereference when RemoteAddr.String method chain is called
- このコミットが元に戻した元の変更です。
- Go Change List 6533043:
undo CL 6395055 / 2518eee18c4f
- このコミット自体のChange Listです。
参考にした情報源リンク
- Go言語の
net
パッケージのドキュメント: https://pkg.go.dev/net - Go言語の
syscall
パッケージのドキュメント: https://pkg.go.dev/syscall - TCP自己接続に関する一般的な情報(例: Stack Overflow, RFCドキュメントなど)
- Go言語における
nil
ポインタとパニックに関する情報- https://go.dev/blog/laws-of-reflection (Goの型システムとnilに関する一般的な情報)
- https://go.dev/doc/effective_go#errors (Goのエラーハンドリングとパニックに関する情報)