[インデックス 16016] ファイルの概要
このコミットは、Go言語の標準ライブラリ net
パッケージにおけるソケットオプションの適用ロジックの改善に関するものです。具体的には、リスナー(受動的に接続を待つソケット)とダイアラー(能動的に接続を開始するソケット)で、それぞれ適切なソケットオプションが適用されるように修正されています。これにより、特にリスナーソケットにおける SO_REUSEADDR
や SO_REUSEPORT
のようなオプションが、意図しないアクティブオープンソケットに適用されることを防ぎ、ネットワーク処理の堅牢性と正確性を向上させています。
コミット
commit 4b7bf73c4b4f46603d4d89a103d1f797f90bc09e
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Sat Mar 30 15:21:22 2013 +0900
net: avoid use of listener socket options on active open sockets
This CL ensures we use the correct socket options for
passive and active open sockets.
For the passive open sockets created by Listen functions,
additional SO_REUSEADDR, SO_REUSEPORT options are required
for the quick service restart and/or multicasting.
For the active open sockets created by Dial functions, no
additional options are required.
R=golang-dev, dave, bradfitz, rsc
CC=golang-dev
https://golang.org/cl/7795050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4b7bf73c4b4f46603d4d89a103d1f797f90bc09e
元コミット内容
src/pkg/net/sock_posix.go | 7 ++++++-\n 1 file changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/src/pkg/net/sock_posix.go b/src/pkg/net/sock_posix.go\nindex c8a94f5047..e2487c805e 100644\n--- a/src/pkg/net/sock_posix.go\n+++ b/src/pkg/net/sock_posix.go\n@@ -25,7 +25,8 @@ func socket(net string, f, t, p int, ipv6only bool, ulsa, ursa syscall.Sockaddr,\n \t\treturn nil, err\n \t}\n \n-\tif ulsa != nil {\n+\t// This socket is used by a listener.\n+\tif ulsa != nil && ursa == nil {\n \t\t// We provide a socket that listens to a wildcard\n \t\t// address with reusable UDP port when the given ulsa\n \t\t// is an appropriate UDP multicast address prefix.\n@@ -37,6 +38,9 @@ func socket(net string, f, t, p int, ipv6only bool, ulsa, ursa syscall.Sockaddr,\n \t\t\tclosesocket(s)\n \t\t\treturn nil, err\n \t\t}\n+\t}\n+\n+\tif ulsa != nil {\n \t\tif err = syscall.Bind(s, ulsa); err != nil {\n \t\t\tclosesocket(s)\n \t\t\treturn nil, err\n@@ -48,6 +52,7 @@ func socket(net string, f, t, p int, ipv6only bool, ulsa, ursa syscall.Sockaddr,\n \t\treturn nil, err\n \t}\n \n+\t// This socket is used by a dialer.\n \tif ursa != nil {\n \t\tif !deadline.IsZero() {\n \t\t\tsetWriteDeadline(fd, deadline)\n```
## 変更の背景
この変更の背景には、ネットワークプログラミングにおけるソケットの「再利用」に関する一般的な課題があります。特に、サーバーアプリケーションがクラッシュしたり、再起動したりする際に、以前使用していたポートがまだTIME_WAIT状態にあるために、新しいプロセスが同じポートをすぐにバインドできないという問題があります。これを解決するために `SO_REUSEADDR` や `SO_REUSEPORT` といったソケットオプションが利用されます。
しかし、これらのオプションは主にリスナーソケット(サーバー側)に適用されるべきものです。アクティブに接続を開始するダイアラーソケット(クライアント側)には通常、これらのオプションは不要であり、誤って適用されると予期せぬ挙動を引き起こす可能性があります。
このコミット以前のGoの `net` パッケージでは、リスナーソケットとダイアラーソケットの区別が曖昧なまま、ソケットオプションが適用される可能性がありました。その結果、ダイアラーソケットに `SO_REUSEADDR` や `SO_REUSEPORT` が適用され、意図しない副作用が生じる可能性がありました。このコミットは、この問題を解決し、ソケットの役割に応じて適切なオプションのみが適用されるようにすることで、ネットワークコードの堅牢性と予測可能性を高めることを目的としています。
## 前提知識の解説
### ソケット (Socket)
ソケットは、ネットワーク上でプログラムが通信を行うためのエンドポイントです。ファイルディスクリプタに似た抽象化を提供し、アプリケーションがネットワークプロトコル(TCP/IPなど)を介してデータを送受信できるようにします。
### パッシブオープンソケット (Passive Open Sockets)
パッシブオープンソケットは、主にサーバー側で使用され、クライアントからの接続要求を「待ち受ける」役割を持つソケットです。`Listen` 関数などによって作成され、特定のIPアドレスとポートにバインドされ、着信接続をリッスンします。
### アクティブオープンソケット (Active Open Sockets)
アクティブオープンソケットは、主にクライアント側で使用され、サーバーへの接続を「開始する」役割を持つソケットです。`Dial` 関数などによって作成され、リモートのIPアドレスとポートに接続を試みます。
### `SO_REUSEADDR`
`SO_REUSEADDR` は、ソケットオプションの一つで、ソケットが閉じられた後、そのソケットが使用していたローカルアドレスとポートをすぐに再利用できるようにします。通常、ソケットが閉じられた後、そのポートは一定期間(TIME_WAIT状態)再利用できない状態になりますが、このオプションを設定することで、この制限を緩和し、サーバーの迅速な再起動を可能にします。
### `SO_REUSEPORT`
`SO_REUSEPORT` は、複数のソケットが同じローカルアドレスとポートにバインドできるようにするソケットオプションです。これにより、複数のプロセスやスレッドが同じポートでリッスンし、着信接続を共有して処理できるようになります。これは、マルチキャストアプリケーションや、複数のサーバーインスタンスが同じポートで負荷分散を行う場合に特に有用です。
### `syscall.Sockaddr`
`syscall.Sockaddr` は、Go言語の `syscall` パッケージで定義されているインターフェースで、ソケットアドレス(IPアドレスとポート番号など)を表します。`Bind` や `Connect` のようなシステムコールに渡され、ソケットがどのネットワークアドレスにバインドされるか、またはどのリモートアドレスに接続するかを指定します。
- `ulsa` (Unix Local Socket Address): ローカルソケットアドレス。主に `Listen` 関数でソケットをバインドする際に使用されます。
- `ursa` (Unix Remote Socket Address): リモートソケットアドレス。主に `Dial` 関数でソケットを接続する際に使用されます。
## 技術的詳細
このコミットは、`src/pkg/net/sock_posix.go` ファイル内の `socket` 関数に焦点を当てています。この関数は、Goの `net` パッケージが内部的にソケットを作成する際に呼び出される低レベルの関数です。
変更前は、`ulsa != nil` という条件だけで、リスナーソケットに適用されるべきソケットオプション(`SO_REUSEADDR` や `SO_REUSEPORT` など)が設定されていました。しかし、`ulsa` が `nil` でない場合でも、それが必ずしもリスナーソケットであるとは限りませんでした。例えば、特定のシナリオでは、ダイアラーソケットでも `ulsa` が設定される可能性があり、その結果、不要なソケットオプションが適用されてしまうことがありました。
このコミットでは、この問題を解決するために、条件を `ulsa != nil && ursa == nil` に変更しています。
- `ulsa != nil`: ローカルアドレスが指定されていることを意味します。これは、ソケットが特定のローカルアドレスにバインドされることを示唆します。
- `ursa == nil`: リモートアドレスが指定されていないことを意味します。これは、ソケットがリモートへの接続を開始するのではなく、着信接続を待機していることを強く示唆します。
この二つの条件を組み合わせることで、「ローカルアドレスにバインドされており、かつリモートアドレスへの接続を意図していないソケット」、すなわち「リスナーソケット」にのみ、`SO_REUSEADDR` や `SO_REUSEPORT` などのオプションが適用されるようにしています。
また、`syscall.Bind(s, ulsa)` の呼び出しを、ソケットオプションの設定ブロックの外に移動し、`ulsa != nil` の条件で再度囲んでいます。これにより、`Bind` 処理は、ソケットがリスナーであるかダイアラーであるかに関わらず、ローカルアドレスが指定されていれば常に実行されるようになります。
さらに、`ursa != nil` の条件で囲まれたブロックが追加され、その中に「This socket is used by a dialer.」というコメントが追加されています。これは、リモートアドレスが指定されているソケット(ダイアラーソケット)に対する処理であることを明確に示しています。このブロック内では、`setWriteDeadline` のようなダイアラーに特化した処理が行われます。
これらの変更により、ソケットの役割(リスナーかダイアラーか)がより明確に区別され、それぞれの役割に応じた適切なソケットオプションと処理が適用されるようになり、Goのネットワークスタックの堅牢性が向上しました。
## コアとなるコードの変更箇所
`src/pkg/net/sock_posix.go` ファイルの `socket` 関数内の以下の部分が変更されています。
```diff
--- a/src/pkg/net/sock_posix.go
+++ b/src/pkg/net/sock_posix.go
@@ -25,7 +25,8 @@ func socket(net string, f, t, p int, ipv6only bool, ulsa, ursa syscall.Sockaddr,\
return nil, err
}\
- if ulsa != nil {
+ // This socket is used by a listener.
+ if ulsa != nil && ursa == nil {
// We provide a socket that listens to a wildcard
// address with reusable UDP port when the given ulsa
// is an appropriate UDP multicast address prefix.
@@ -37,6 +38,9 @@ func socket(net string, f, t, p int, ipv6only bool, ulsa, ursa syscall.Sockaddr,\
closesocket(s)
return nil, err
}\
+ }\
+
+ if ulsa != nil {
if err = syscall.Bind(s, ulsa); err != nil {
closesocket(s)
return nil, err
@@ -48,6 +52,7 @@ func socket(net string, f, t, p int, ipv6only bool, ulsa, ursa syscall.Sockaddr,\
return nil, err
}\
+ // This socket is used by a dialer.
if ursa != nil {
if !deadline.IsZero() {
setWriteDeadline(fd, deadline)
コアとなるコードの解説
-
リスナーソケットの条件変更: 変更前:
if ulsa != nil {
変更後:if ulsa != nil && ursa == nil {
この変更が最も重要です。以前はulsa
(ローカルソケットアドレス) が存在すれば、ソケットオプション(SO_REUSEADDR
など)を設定していました。しかし、ulsa
が存在しても、それがダイアラーソケットである可能性がありました。新しい条件ulsa != nil && ursa == nil
は、「ローカルアドレスにバインドされており、かつリモートアドレスが指定されていない」という、リスナーソケットに特有の条件を明示的に指定しています。これにより、Listen
関数によって作成されるソケットにのみ、再利用オプションが適用されるようになります。 -
syscall.Bind
の移動と再条件付け: 以前は、syscall.Bind(s, ulsa)
はif ulsa != nil
のブロック内にありました。変更後、このBind
呼び出しは、リスナーソケットのオプション設定ブロックの外に移動し、独立したif ulsa != nil {
ブロックで囲まれています。これは、ソケットがリスナーであるかダイアラーであるかに関わらず、ローカルアドレスが指定されていればBind
処理は常に実行されるべきであることを示しています。例えば、クライアントが特定のローカルポートから接続を開始したい場合などです。 -
ダイアラーソケットの明示的な識別: 新しく追加された
// This socket is used by a dialer.
というコメントと、それに続くif ursa != nil {
ブロックは、リモートソケットアドレス (ursa
) が指定されている場合に、そのソケットがダイアラー(接続を開始する側)であることを明確に示しています。このブロック内では、setWriteDeadline
のような、ダイアラーに特化した処理が行われます。
これらの変更により、socket
関数は、ソケットがリスナーとして使用されるのか、それともダイアラーとして使用されるのかをより正確に判断し、それぞれの役割に応じた適切なソケットオプションの設定と処理を行うことができるようになりました。これにより、Goのネットワークスタックの内部的なロジックがより堅牢で、意図した通りに動作するようになります。
関連リンク
- Go言語の
net
パッケージに関する公式ドキュメント: https://pkg.go.dev/net syscall
パッケージに関する公式ドキュメント: https://pkg.go.dev/syscallSO_REUSEADDR
とSO_REUSEPORT
に関する一般的な情報(Linux man pagesなど):socket(7)
man page: https://man7.org/linux/man-pages/man7/socket.7.html
参考にした情報源リンク
- Go言語の公式ドキュメント
- Linux man pages (socket(7))
- ネットワークプログラミングに関する一般的な知識
- コミットメッセージとコードの差分
- Go CL 7795050: https://golang.org/cl/7795050 (これはコミットメッセージに記載されているリンクであり、この変更のレビューページです)