[インデックス 11791] ファイルの概要
このコミットは、Go言語のネットワークパッケージ(net
)におけるソケットオプションの設定に関する変更です。具体的には、SO_REUSEADDR
および SO_REUSEPORT
オプションが、ストリーム指向のリスナー(TCPやUnixドメインソケット)とマルチキャストリスナーにのみ適用されるように修正されています。これにより、OpenBSDなどのランダムなポート割り当てをサポートするプラットフォームでの接続失敗を軽減することを目的としています。
コミット
commit 0e3514eaac009cbb4cb54e00979df357da234b7e
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Sat Feb 11 11:50:51 2012 +0900
net: enable SO_REUSEADDR, SO_REUSEPORT options on stream, multicast listeners only
This CL changes default SOL_SOCKET settings to mitigate connect
failure on OpenBSD or similar platforms which support randomized
transport protocol port number assignment.
Fixes #2830.
R=rsc, jsing
CC=golang-dev
https://golang.org/cl/5648044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0e3514eaac009cbb4cb54e00979df357da234b7e
元コミット内容
net: enable SO_REUSEADDR, SO_REUSEPORT options on stream, multicast listeners only
このコミットは、デフォルトの SOL_SOCKET
設定を変更し、OpenBSDや同様のプラットフォームでランダムなトランスポートプロトコルポート番号割り当てをサポートするシステムでの接続失敗を軽減することを目的としています。
Fixes #2830.
変更の背景
この変更の背景には、OpenBSDなどの一部のオペレーティングシステムが採用している、トランスポートプロトコル(TCP/UDP)のポート番号のランダム化があります。これはセキュリティ強化のための機能で、ポートスキャンや特定の攻撃を防ぐのに役立ちます。しかし、このランダム化されたポート割り当てが、Goのネットワークライブラリがソケットをバインドする際に問題を引き起こす可能性がありました。
具体的には、以前のGoのネットワーク実装では、SO_REUSEADDR
と SO_REUSEPORT
オプションが、ストリームソケット(TCPなど)だけでなく、すべてのソケットに対して無条件に設定される可能性がありました。ランダムなポート割り当てが行われる環境では、ソケットが閉じられた直後に同じポートを再利用しようとすると、システムがまだそのポートを「使用中」と認識しているためにバインドに失敗することがありました。
コミットメッセージにある #2830
は、この問題に関連するGoのIssueです。このIssueでは、OpenBSD上でGoのプログラムが特定の条件下でbind: address already in use
エラーを頻繁に発生させることが報告されていました。このエラーは、ソケットが閉じられた後も、そのアドレスとポートの組み合わせがTIME_WAIT状態にあるために発生することが一般的です。SO_REUSEADDR
はTIME_WAIT状態のソケットでもアドレスの再利用を許可しますが、ランダムポート割り当てと組み合わせると、意図しない挙動やバインド失敗につながる可能性がありました。
このコミットは、SO_REUSEADDR
とSO_REUSEPORT
の適用範囲をより限定することで、この問題を緩和し、特にリスナーソケット(サーバー側で接続を待ち受けるソケット)が安定して動作するようにすることを目的としています。
前提知識の解説
ソケットオプション (setsockopt
)
ソケットオプションは、ソケットの動作を制御するための設定です。setsockopt
システムコールを使用して設定されます。ここでは、特に重要なSO_REUSEADDR
とSO_REUSEPORT
について解説します。
-
SO_REUSEADDR
:- 目的: 通常、ソケットが閉じられた後、そのソケットが使用していたローカルアドレスとポートの組み合わせは、TCPのTIME_WAIT状態のためにしばらくの間(通常は数分間)再利用できません。これは、ネットワーク上の遅延パケットが新しい接続に影響を与えるのを防ぐためです。
- 効果:
SO_REUSEADDR
を有効にすると、TIME_WAIT状態にあるソケットが使用していたアドレスとポートの組み合わせを、別のソケットがすぐに再利用できるようになります。これは、サーバーアプリケーションの開発において非常に便利です。サーバーを再起動する際に、以前のプロセスがまだTIME_WAIT状態にあるためにバインドできない、という問題を回避できます。 - 注意点:
SO_REUSEADDR
は、同じアドレスとポートに複数のソケットがバインドすることを許可するわけではありません。あくまで、TIME_WAIT状態にあるソケットのアドレスを再利用できるようにするものです。
-
SO_REUSEPORT
:- 目的:
SO_REUSEADDR
とは異なり、SO_REUSEPORT
は複数のソケットが全く同じローカルアドレスとポートの組み合わせにバインドすることを許可します。 - 効果: これにより、複数のプロセスやスレッドが同じポートでリッスンし、カーネルが受信接続をそれらのソケット間で負荷分散できるようになります。これは、高性能なサーバーアプリケーションや、マルチキャストアプリケーションで特に有用です。
- 注意点:
SO_REUSEPORT
は、すべてのオペレーティングシステムでサポートされているわけではありません。特に、4.4BSDの派生システム(FreeBSD, OpenBSD, macOSなど)で広くサポートされていますが、Linuxでは比較的新しいカーネルバージョンで導入されました。WindowsではSO_REUSEADDR
がデフォルトで同様の挙動を示すため、SO_REUSEPORT
は存在しません。
- 目的:
ランダムなポート割り当て
一部のオペレーティングシステム(特にOpenBSD)では、セキュリティ上の理由から、アウトバウンド接続や特定のリスニングソケットに対して、エフェメラルポート(一時的なポート)をランダムに割り当てるポリシーを採用しています。これにより、攻撃者が予測可能なポートを使用してサービスを特定したり、ポートスキャンを容易にしたりすることを防ぎます。
listenerSockaddr
関数
Goのnet
パッケージ内部で使用される関数で、リスナーソケットのアドレスを処理します。この関数は、与えられたネットワークアドレス(例: TCPアドレス、UDPアドレス、Unixドメインソケットアドレス)に基づいて、適切なソケットオプションを設定するための分岐点となります。
setDefaultListenerSockopts
関数
このコミットで新しく導入された(または既存のロジックから分離された)関数で、リスナーソケットに特化したデフォルトのソケットオプションを設定します。具体的には、SO_REUSEADDR
とSO_REUSEPORT
を設定する責任を持ちます。
技術的詳細
このコミットの主要な変更点は、SO_REUSEADDR
とSO_REUSEPORT
の適用ロジックを、setDefaultSockopts
関数からsetDefaultListenerSockopts
という新しい関数に分離し、その呼び出しを特定のリスナータイプに限定したことです。
以前のコードでは、setDefaultSockopts
関数内で、ソケットのファミリー(f
)とタイプ(t
)に基づいてSO_REUSEADDR
とSO_REUSEPORT
が設定されていました。具体的には、AF_UNIX
(Unixドメインソケット)またはAF_INET
/AF_INET6
(IPv4/IPv6)かつSOCK_STREAM
(TCP)の場合にこれらのオプションが設定されていました。
この変更により、以下のようになります。
-
setDefaultListenerSockopts
関数の導入:src/pkg/net/sockopt_bsd.go
src/pkg/net/sockopt_linux.go
src/pkg/net/sockopt_windows.go
これらのファイルに、SO_REUSEADDR
とSO_REUSEPORT
(Windowsの場合はSO_REUSEADDR
は設定せず、SO_BROADCAST
もsetDefaultSockopts
に移動)を設定するsetDefaultListenerSockopts
関数が追加されました。これにより、リスナーに特化したオプション設定が明確に分離されました。
-
listenerSockaddr
での呼び出しの変更:src/pkg/net/sock_bsd.go
src/pkg/net/sock_linux.go
src/pkg/net/sock_windows.go
これらのファイルにあるlistenerSockaddr
関数内で、リスナーのアドレスタイプに応じてsetDefaultListenerSockopts
が呼び出されるようになりました。*TCPAddr
または*UnixAddr
の場合(ストリーム指向のリスナー)にsetDefaultListenerSockopts(s)
が呼び出されます。*UDPAddr
でIP.IsMulticast()
の場合(マルチキャストリスナー)は、引き続きsetDefaultMulticastSockopts(s)
が呼び出されます。これは、マルチキャスト通信ではSO_REUSEADDR
とSO_REUSEPORT
が特に重要であるためです。
-
setDefaultSockopts
からのSO_REUSEADDR
/SO_REUSEPORT
ロジックの削除:src/pkg/net/sockopt_bsd.go
src/pkg/net/sockopt_linux.go
src/pkg/net/sockopt_windows.go
これらのファイルから、SO_REUSEADDR
とSO_REUSEPORT
を設定するロジックがsetDefaultSockopts
関数から削除されました。これにより、これらのオプションはリスナーソケットにのみ適用されることが保証されます。
この変更の意図は、SO_REUSEADDR
とSO_REUSEPORT
がすべてのソケットタイプに無条件に適用されるのではなく、それらが実際に必要とされるリスナーソケット(特にTCPリスナーやマルチキャストリスナー)に限定されるようにすることです。これにより、OpenBSDのようなランダムポート割り当てを行うシステムでの予期せぬバインド失敗を回避し、より堅牢なネットワーク動作を実現します。
Windowsの場合、SO_REUSEADDR
はデフォルトで再利用を許可する挙動を示すため、明示的に設定する必要がない、あるいは設定すべきではないというコメントが追加されています。これは、WindowsのソケットAPIの特性を考慮したものです。
コアとなるコードの変更箇所
src/pkg/net/sock_bsd.go
, src/pkg/net/sock_linux.go
, src/pkg/net/sock_windows.go
これらのファイルでは、listenerSockaddr
関数内で、TCPAddr
またはUnixAddr
の場合にsetDefaultListenerSockopts
を呼び出すロジックが追加されました。
--- a/src/pkg/net/sock_bsd.go
+++ b/src/pkg/net/sock_bsd.go
@@ -38,6 +38,11 @@ func listenerSockaddr(s, f int, la syscall.Sockaddr, toAddr func(syscall.Sockadd
return la, nil
}
switch v := a.(type) {
+ case *TCPAddr, *UnixAddr:
+ err := setDefaultListenerSockopts(s)
+ if err != nil {
+ return nil, err
+ }
case *UDPAddr:
if v.IP.IsMulticast() {
err := setDefaultMulticastSockopts(s)
src/pkg/net/sockopt_bsd.go
, src/pkg/net/sockopt_linux.go
, src/pkg/net/sockopt_windows.go
これらのファイルでは、setDefaultSockopts
関数からSO_REUSEADDR
とSO_REUSEPORT
の設定ロジックが削除され、新たにsetDefaultListenerSockopts
関数が追加されました。
src/pkg/net/sockopt_bsd.go
の例:
--- a/src/pkg/net/sockopt_bsd.go
+++ b/src/pkg/net/sockopt_bsd.go
@@ -20,31 +20,28 @@ func setDefaultSockopts(s, f, t int) error {
// Note that some operating systems never admit this option.
syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 0)
}
-
- if f == syscall.AF_UNIX ||
- (f == syscall.AF_INET || f == syscall.AF_INET6) && t == syscall.SOCK_STREAM {
- // Allow reuse of recently-used addresses.
- err := syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
- if err != nil {
- return os.NewSyscallError("setsockopt", err)
- }
-
- // Allow reuse of recently-used ports.
- // This option is supported only in descendants of 4.4BSD,
- // to make an effective multicast application and an application
- // that requires quick draw possible.
- err = syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEPORT, 1)
- if err != nil {
- return os.NewSyscallError("setsockopt", err)
- }
- }
-
// Allow broadcast.
err := syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1)
if err != nil {
return os.NewSyscallError("setsockopt", err)
}
+ return nil
+}
+
+func setDefaultListenerSockopts(s int) error {
+ // Allow reuse of recently-used addresses.
+ err := syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
+ if err != nil {
+ return os.NewSyscallError("setsockopt", err)
+ }
+ // Allow reuse of recently-used ports.
+ // This option is supported only in descendants of 4.4BSD,
+ // to make an effective multicast application and an application
+ // that requires quick draw possible.
+ err = syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEPORT, 1)
+ if err != nil {
+ return os.NewSyscallError("setsockopt", err)
+ }
return nil
}
コアとなるコードの解説
このコミットの核心は、ソケットオプションの適用範囲をより厳密に制御することにあります。
-
役割の分離: 以前は
setDefaultSockopts
が様々なソケットオプションを一括で設定していましたが、この変更により、リスナーソケットに特有のSO_REUSEADDR
とSO_REUSEPORT
の設定がsetDefaultListenerSockopts
という独立した関数に切り出されました。これにより、コードの可読性と保守性が向上し、各関数の責任が明確になりました。 -
適用範囲の限定:
SO_REUSEADDR
とSO_REUSEPORT
は、主にサーバーが特定のポートで接続を待ち受ける「リスナー」のシナリオでその真価を発揮します。これらのオプションをすべてのソケットに無差別に適用すると、特にランダムポート割り当てを行うシステムにおいて、予期せぬ挙動やバインド失敗を引き起こす可能性がありました。今回の変更により、これらのオプションはTCPAddr
(TCPリスナー)とUnixAddr
(Unixドメインソケットリスナー)の場合にのみ適用されるようになりました。マルチキャストリスナーは引き続きsetDefaultMulticastSockopts
を通じてこれらのオプションを有効にします。 -
プラットフォームごとの差異への対応: Windowsの
sockopt_windows.go
では、setDefaultListenerSockopts
関数内でSO_REUSEADDR
が設定されないように変更されています。これは、WindowsのソケットAPIではSO_REUSEADDR
がデフォルトで再利用を許可する挙動を示すため、明示的に設定すると「別のソケットが使用中のポートに強制的にバインドすることを許可する」という、意図しない、非決定的な挙動につながる可能性があるためです。この変更は、Goが各OSのソケットAPIの特性を考慮して、適切なデフォルト設定を提供しようとしていることを示しています。
この変更は、Goのネットワークスタックが、異なるオペレーティングシステムの微妙な挙動の違いに対応し、より堅牢で予測可能な動作を実現するための継続的な努力の一環と言えます。特に、OpenBSDのようなセキュリティを重視したOSでの互換性と安定性を向上させる上で重要な修正です。
関連リンク
- Go Issue #2830: https://github.com/golang/go/issues/2830 (このコミットが修正したIssue)
- Go CL 5648044: https://golang.org/cl/5648044 (このコミットに対応するGerrit Change List)
参考にした情報源リンク
SO_REUSEADDR
とSO_REUSEPORT
に関する一般的な情報:- https://www.man7.org/linux/man-pages/man7/socket.7.html (Linux man page for socket)
- https://docs.microsoft.com/en-us/windows/win32/winsock/winsock-programming-considerations-2 (Windows Sockets Programming Considerations)
- OpenBSDのポートランダム化に関する情報:
- OpenBSDの公式ドキュメントや関連するセキュリティ記事
- Go言語のネットワークプログラミングに関する一般的な情報:
- Goの公式ドキュメントやブログ記事
- Goの
net
パッケージのソースコード - Goの
syscall
パッケージのドキュメント