[インデックス 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.gosrc/pkg/net/sockopt_linux.gosrc/pkg/net/sockopt_windows.goこれらのファイルに、SO_REUSEADDRとSO_REUSEPORT(Windowsの場合はSO_REUSEADDRは設定せず、SO_BROADCASTもsetDefaultSockoptsに移動)を設定するsetDefaultListenerSockopts関数が追加されました。これにより、リスナーに特化したオプション設定が明確に分離されました。
-
listenerSockaddrでの呼び出しの変更:src/pkg/net/sock_bsd.gosrc/pkg/net/sock_linux.gosrc/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.gosrc/pkg/net/sockopt_linux.gosrc/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パッケージのドキュメント