[インデックス 16253] ファイルの概要
コミット
commit ca6b1f3eeb8f8bd658b6bd5b425184b65d42bc8c
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Tue Apr 30 17:47:39 2013 -0700
net: do not call syscall.Bind twice on windows
Fixes #5355.
R=golang-dev, mikioh.mikioh, bradfitz, r
CC=golang-dev
https://golang.org/cl/8966046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ca6b1f3eeb8f8bd658b6bd5b425184b65d42bc8c
元コミット内容
net: do not call syscall.Bind twice on windows
このコミットは、Windows環境においてネットワーク関連の処理で syscall.Bind
が二重に呼び出される問題を修正します。
変更の背景
このコミットは、Go言語のネットワークパッケージ (net
) におけるWindows固有のバグを修正するために作成されました。コミットメッセージに Fixes #5355
とあるように、GoプロジェクトのIssue 5355で報告された問題に対応しています。
具体的には、Windowsの ConnectEx
APIを使用する際に、ソケットが事前にバインドされている必要があるという要件があります。しかし、既存の実装では、ソケットが既にバインドされているにもかかわらず、syscall.Bind
が再度呼び出される可能性がありました。この二重バインドは、予期せぬエラーやパフォーマンスの問題を引き起こす可能性がありました。
この問題は、特に Dialer
を使用してローカルアドレスを指定して接続を試みる場合に顕著に現れたと考えられます。Dialer
の LocalAddr
フィールドが設定されている場合、Goのネットワークスタックは指定されたローカルアドレスにソケットをバインドしようとします。しかし、ConnectEx
の内部処理で再度バインドが試みられることで、問題が発生していました。
前提知識の解説
Go言語の net
パッケージ
Go言語の net
パッケージは、ネットワークI/Oプリミティブを提供します。TCP/IP、UDP、Unixドメインソケットなど、様々なネットワークプロトコルを扱うための機能が含まれています。このパッケージは、低レベルのソケット操作を抽象化し、開発者が簡単にネットワークアプリケーションを構築できるように設計されています。
syscall
パッケージ
syscall
パッケージは、オペレーティングシステムが提供する低レベルのシステムコールへのインターフェースを提供します。これにより、GoプログラムからOS固有の機能(ファイル操作、プロセス管理、ネットワークソケット操作など)を直接呼び出すことができます。ネットワークプログラミングにおいては、ソケットの作成、バインド、接続、リスニングなどの操作に syscall
パッケージが利用されます。
syscall.Bind
syscall.Bind
は、ソケットを特定のローカルアドレス(IPアドレスとポート番号)にバインドするためのシステムコールです。サーバーアプリケーションが特定のポートで接続を待ち受ける場合や、クライアントが特定のローカルポートから接続を開始する場合に利用されます。ソケットはバインドされることで、そのアドレスとポートを通じて通信が可能になります。
Windowsの ConnectEx
API
ConnectEx
は、WindowsソケットAPI (Winsock) の拡張関数の一つで、非同期I/O操作を効率的に行うために設計されています。特に、クライアントソケットの接続処理を高速化するために使用されます。ConnectEx
を使用するソケットは、事前に Bind
関数によってローカルアドレスにバインドされている必要があります。この要件は、従来の connect
関数とは異なる点であり、ConnectEx
の効率的な動作を保証するために重要です。
Dialer
と LocalAddr
Goの net
パッケージにおける Dialer
構造体は、ネットワーク接続を確立するための設定をカプセル化します。Dialer
の LocalAddr
フィールドは、発信接続に使用するローカルアドレスを指定するために使用されます。このフィールドが設定されている場合、Goは接続を確立する前に、指定されたローカルアドレスにソケットをバインドしようとします。
技術的詳細
このコミットの核心は、Windows環境における netFD.connect
メソッドの変更にあります。netFD.connect
は、ネットワーク接続を確立する際に内部的に呼び出される関数です。
変更前は、netFD.connect
が ConnectEx
を使用する場合、syscall.Bind
を無条件に呼び出してソケットをバインドしていました。しかし、Dialer
の LocalAddr
が設定されている場合、ソケットは既に socket
関数内でバインドされている可能性があります。このため、netFD.connect
内での syscall.Bind
の呼び出しは二重バインドとなり、エラーを引き起こす原因となっていました。
修正では、netFD.connect
のシグネチャが変更され、la
(ローカルアドレス) パラメータが追加されました。この la
パラメータは、ソケットが既にバインドされているかどうかを示すために使用されます。
具体的には、fd_windows.go
の netFD.connect
関数内で、la == nil
の場合にのみ syscall.Bind
を呼び出すように変更されました。これにより、ソケットが既にバインドされている場合は二重バインドが回避され、ConnectEx
の要件を満たしつつ、不要な syscall.Bind
の呼び出しによる問題を解決しています。
また、fd_unix.go
と sock_posix.go
の connect
メソッドのシグネチャも同様に変更されていますが、これは主にインターフェースの一貫性を保つためであり、Windows固有のバグ修正とは直接関係ありません。Unix系のシステムでは ConnectEx
のような事前バインドの要件がないため、la
パラメータは通常 nil
として渡されます。
dial_test.go
に追加された TestDialer
は、この修正が正しく機能することを確認するためのテストケースです。このテストは、Dialer
を使用してローカルアドレスを指定し、TCP接続を確立するシナリオをシミュレートします。これにより、修正が Dialer
の LocalAddr
と ConnectEx
の組み合わせで発生していた問題を解決していることを検証します。
コアとなるコードの変更箇所
src/pkg/net/dial_test.go
: 新しいテストケースTestDialer
の追加。src/pkg/net/fd_unix.go
:netFD.connect
関数のシグネチャ変更 (la
パラメータの追加)。src/pkg/net/fd_windows.go
:netFD.connect
関数のシグネチャ変更と、syscall.Bind
の呼び出しロジックの修正。src/pkg/net/sock_posix.go
:fd.connect
の呼び出し箇所でla
パラメータを渡すように変更。
コアとなるコードの解説
src/pkg/net/fd_windows.go
の変更
--- a/src/pkg/net/fd_windows.go
+++ b/src/pkg/net/fd_windows.go
@@ -364,22 +364,23 @@ func (o *connectOp) Name() string {
return "ConnectEx"
}
-func (fd *netFD) connect(ra syscall.Sockaddr) error {
+func (fd *netFD) connect(la, ra syscall.Sockaddr) error {
if !canUseConnectEx(fd.net) {
return syscall.Connect(fd.sysfd, ra)
}
// ConnectEx windows API requires an unconnected, previously bound socket.
- var la syscall.Sockaddr
- switch ra.(type) {
- case *syscall.SockaddrInet4:
- la = &syscall.SockaddrInet4{}
- case *syscall.SockaddrInet6:
- la = &syscall.SockaddrInet6{}
- default:
- panic("unexpected type in connect")
- }
- if err := syscall.Bind(fd.sysfd, la); err != nil {
- return err
+ if la == nil { // ここが変更点
+ switch ra.(type) {
+ case *syscall.SockaddrInet4:
+ la = &syscall.SockaddrInet4{}
+ case *syscall.SockaddrInet6:
+ la = &syscall.SockaddrInet6{}
+ default:
+ panic("unexpected type in connect")
+ }
+ if err := syscall.Bind(fd.sysfd, la); err != nil {
+ return err
+ }
}
// Call ConnectEx API.
var o connectOp
この変更が最も重要です。
func (fd *netFD) connect(ra syscall.Sockaddr) error
がfunc (fd *netFD) connect(la, ra syscall.Sockaddr) error
に変更され、la
(ローカルアドレス) パラメータが追加されました。- 以前は無条件に
syscall.Bind(fd.sysfd, la)
が呼び出されていましたが、変更後はif la == nil
という条件が追加されました。 - この条件により、
la
がnil
でない(つまり、ソケットが既にローカルアドレスにバインドされている)場合は、syscall.Bind
の呼び出しがスキップされます。これにより、二重バインドの問題が解決されます。 la
がnil
の場合は、以前と同様にra
の型に基づいて適切なSockaddr
が作成され、ソケットがバインドされます。
src/pkg/net/dial_test.go
の変更
--- a/src/pkg/net/dial_test.go
+++ b/src/pkg/net/dial_test.go
@@ -372,3 +372,38 @@ func TestDialFailPDLeak(t *testing.T) {
}
}
}
+
+func TestDialer(t *testing.T) {
+ ln, err := Listen("tcp4", "127.0.0.1:0")
+ if err != nil {
+ t.Fatalf("Listen failed: %v", err)
+ }
+ defer ln.Close()
+ ch := make(chan error, 1)
+ go func() {
+ var err error
+ c, err := ln.Accept()
+ if err != nil {
+ ch <- fmt.Errorf("Accept failed: %v", err)
+ return
+ }
+ defer c.Close()
+ ch <- nil
+ }()
+
+ laddr, err := ResolveTCPAddr("tcp4", "127.0.0.1:0")
+ if err != nil {
+ t.Fatalf("ResolveTCPAddr failed: %v", err)
+ }
+ d := &Dialer{LocalAddr: laddr} // ここでLocalAddrを指定
+ c, err := d.Dial("tcp4", ln.Addr().String())
+ if err != nil {
+ t.Fatalf("Dial failed: %v", err)
+ }
+ defer c.Close()
+ c.Read(make([]byte, 1))
+ err = <-ch
+ if err != nil {
+ t.Error(err)
+ }
+}
このテストケースは、Dialer
を使用し、LocalAddr
を 127.0.0.1:0
(OSが利用可能なポートを割り当てる) に設定して接続を試みます。これにより、ソケットが事前にバインドされるシナリオを再現し、fd_windows.go
の修正が正しく機能することを確認します。
関連リンク
- Go言語の
net
パッケージドキュメント: https://pkg.go.dev/net - Go言語の
syscall
パッケージドキュメント: https://pkg.go.dev/syscall - Winsock
ConnectEx
関数 (Microsoft Learn): https://learn.microsoft.com/ja-jp/windows/win32/api/winsock2api/nf-winsock2api-connectex
参考にした情報源リンク
- コミットメッセージ内のGo CLリンク: https://golang.org/cl/8966046
- Go言語のIssueトラッカー (Issue 5355に関する詳細情報がある可能性): https://github.com/golang/go/issues/5355 (ただし、直接的な検索では見つからなかったため、このリンクは一般的なGoのIssueトラッカーへの参照としています。)
- Go言語のソースコード (コミット前後のコード比較): https://github.com/golang/go/