Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 11822] ファイルの概要

このコミットは、Go言語のネットワークパッケージにおいて、BSD系のOSにおけるSO_REUSEPORTソケットオプションの「野放図な使用(wild use)」を無効化することを目的としています。具体的には、src/pkg/net/sockopt_bsd.goファイル内で、リスナーソケットに対するSO_REUSEPORTの設定を削除し、マルチキャストソケットにのみ限定することで、以前に修正されたはずの問題(Issue #2830)が再発するのを防いでいます。

コミット

commit 6fa2296e839d7cea090fd9bddc9831ea6186c30e
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Mon Feb 13 12:45:59 2012 +0900

    net: disable wild use of SO_REUSEPORT on BSD variants
    
    Fixes #2830 (again).
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/5651083

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/6fa2296e839d7cea090fd9bddc9831ea6186c30e

元コミット内容

net: disable wild use of SO_REUSEPORT on BSD variants

Fixes #2830 (again).

R=rsc
CC=golang-dev
https://golang.org/cl/5651083

変更の背景

このコミットの背景には、Go言語のネットワークパッケージがBSD系のオペレーティングシステム(macOS、FreeBSDなど)でSO_REUSEPORTソケットオプションをどのように扱うかという問題があります。コミットメッセージにある「Fixes #2830 (again)」という記述から、この問題が過去にも発生し、一度修正されたにもかかわらず再発したことが示唆されます。

SO_REUSEPORTは、複数のソケットが同じIPアドレスとポートにバインドすることを許可するオプションです。これは、特にマルチキャストアプリケーションや、サーバーのダウンタイムなしでの再起動(graceful restart)を可能にするために有用です。しかし、その動作はOSによって異なり、特にBSD系とLinux系では振る舞いが異なります。

Goの標準ライブラリが、BSD系OSでリスナーソケットに対して無条件にSO_REUSEPORTを設定していたことが、何らかの予期せぬ問題を引き起こしていたと考えられます。この「野放図な使用」とは、おそらく、特定のユースケース(マルチキャストなど)に限定されるべきSO_REUSEPORTが、一般的なTCPリスナーなどにも適用されていたことを指しているでしょう。これにより、ポートの競合や接続の予期せぬルーティングなど、安定性や予測可能性に影響を与える問題が発生した可能性があります。

このコミットは、SO_REUSEPORTの適用範囲をマルチキャストソケットに限定することで、この問題を根本的に解決しようとしています。

前提知識の解説

ソケットオプション SO_REUSEADDRSO_REUSEPORT

ネットワークプログラミングにおいて、ソケットオプションはソケットの振る舞いを制御するために使用されます。特に、SO_REUSEADDRSO_REUSEPORTは、ポートの再利用に関連する重要なオプションです。

  • SO_REUSEADDR:

    • このオプションは、ソケットがTIME_WAIT状態にあるアドレスにバインドすることを許可します。
    • TCP接続が終了すると、ソケットはしばらくの間TIME_WAIT状態にとどまります。これは、ネットワーク上の遅延パケットが新しい接続に影響を与えないようにするためです。
    • SO_REUSEADDRを設定することで、サーバーがクラッシュしたり、再起動したりした際に、以前の接続がTIME_WAIT状態であってもすぐに同じポートにバインドできるようになります。これにより、「Address already in use」エラーを防ぎ、サーバーの迅速な再起動を可能にします。
    • Go言語のnetパッケージでは、TCPListenerに対してデフォルトでSO_REUSEADDRが設定されます。
  • SO_REUSEPORT:

    • このオプションは、複数の異なるソケット(異なるプロセスによって所有されている場合も含む)が、全く同じIPアドレスとポートの組み合わせに同時にバインドすることを許可します。
    • 主な用途は、複数のリスナープロセス間で受信接続をロードバランシングすることです。カーネルが受信接続を、同じポートにバインドしている複数のリスニングソケットのいずれかに分散します。
    • OSによる動作の違い:
      • BSD系(FreeBSD, macOSなど): 伝統的に、SO_REUSEPORTは、複数のプロセスが同じポートにバインドすることを許可し、特にUDP/IPマルチキャストやブロードキャストデータグラムを受信するために使用されます。TCPソケットの場合、BSDのSO_REUSEPORTは、接続を最も新しくバインドされたソケットにキューイングするLIFO(後入れ先出し)のような動作をすることがありました。これにより、新しいプロセスがポートにバインドして新しい接続を引き継ぎ、古いプロセスが既存の接続を優雅に処理してからシャットダウンすることで、シームレスなサーバー再起動が可能になります。FreeBSDでは、ロードバランシングを目的としたSO_REUSEPORT_LBも導入されています。
      • Linux: Linuxカーネル3.9以降で導入されたSO_REUSEPORTは、主にロードバランサーとして機能し、複数のリスナー間で受信接続を分散します。これは、マルチスレッドサーバーアプリケーションの「thundering herd problem」(多数のプロセスが同時に同じイベントを待機し、イベントが発生した際にすべてが起動して競合する問題)を軽減するのに役立ちます。

このコミットは、特にBSD系OSにおけるSO_REUSEPORTの挙動と、それがGoのネットワークスタックに与える影響に焦点を当てています。

技術的詳細

このコミットは、GoのnetパッケージがBSD系のOSでSO_REUSEPORTをどのように設定するかを変更しています。変更前のコードでは、setDefaultListenerSockopts関数内で、リスナーソケットに対して無条件にSO_REUSEPORTが設定されていました。コメントには「最近使用されたポートの再利用を許可する」とあり、その目的が「効果的なマルチキャストアプリケーションと迅速な描画を必要とするアプリケーションを可能にするため」と説明されています。

しかし、この無条件な設定が問題を引き起こしていました。SO_REUSEPORTは強力なオプションであり、その動作はOSによって微妙に異なります。特にBSD系OSでは、複数のソケットが同じポートにバインドできるため、意図しないポートの競合や、接続がどのリスナーにルーティングされるかの予測不能性につながる可能性がありました。一般的なTCPリスナーに対してこのオプションが常に有効になっていると、アプリケーションの設計によっては予期せぬ振る舞いを引き起こすことがあります。

コミットメッセージの「Fixes #2830 (again)」は、この問題が以前にも報告され、修正が試みられたものの、何らかの理由で再発したことを示しています。おそらく、以前の修正では根本的な原因が解決されていなかったか、あるいは別の変更によって問題が再燃したのでしょう。

このコミットの解決策は、SO_REUSEPORTの設定をより限定的にすることです。具体的には、setDefaultListenerSockopts(一般的なリスナーソケット用)からSO_REUSEPORTの設定を削除し、setDefaultMulticastSockopts(マルチキャストソケット用)にのみ残すことで、このオプションの適用範囲を、その本来の目的であるマルチキャストアプリケーションに限定しています。これにより、一般的なTCPリスナーがSO_REUSEPORTの予期せぬ影響を受けることを防ぎ、ネットワークスタックの安定性と予測可能性を向上させています。

コアとなるコードの変更箇所

diff --git a/src/pkg/net/sockopt_bsd.go b/src/pkg/net/sockopt_bsd.go
index 519d2fb05a..79e0e57e21 100644
--- a/src/pkg/net/sockopt_bsd.go
+++ b/src/pkg/net/sockopt_bsd.go
@@ -34,14 +34,6 @@ func setDefaultListenerSockopts(s int) error {
 	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
 }
 
@@ -52,6 +44,10 @@ func setDefaultMulticastSockopts(s int) error {
 	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 that requires
+	// quick draw possible.
 	err = syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEPORT, 1)
 	if err != nil {
 		return os.NewSyscallError("setsockopt", err)

コアとなるコードの解説

このコミットは、src/pkg/net/sockopt_bsd.goファイル内の2つの関数に変更を加えています。

  1. setDefaultListenerSockopts(s int) error:

    • この関数は、一般的なリスナーソケット(例: net.Listenで作成されるTCPリスナー)に対してデフォルトのソケットオプションを設定するために呼び出されます。
    • 変更点: 以前は、この関数内でsyscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEPORT, 1)が呼び出され、SO_REUSEPORTオプションが無条件に設定されていました。このコミットでは、この8行のコードブロックが完全に削除されています。
    • 影響: これにより、BSD系のOSにおいて、Goの一般的なリスナーソケットはデフォルトでSO_REUSEPORTオプションを設定しなくなります。これにより、複数のプロセスが同じポートにバインドしようとした際の予期せぬ挙動や競合が回避され、より予測可能なソケットの振る舞いが保証されます。
  2. setDefaultMulticastSockopts(s int) error:

    • この関数は、マルチキャスト通信に使用されるソケットに対してデフォルトのソケットオプションを設定するために呼び出されます。
    • 変更点: 以前のコードでは、この関数内でもSO_REUSEPORTが設定されていましたが、その前のコメントが短縮されていました。このコミットでは、SO_REUSEPORTを設定する行自体は残しつつ、その前のコメントがsetDefaultListenerSockoptsから削除されたコメントと同じ内容(// Allow reuse of recently-used ports. ...)に更新されています。
    • 影響: マルチキャストソケットに対しては引き続きSO_REUSEPORTが設定されます。これは、マルチキャストアプリケーションが同じポートで複数のソケットをリッスンする必要があるというSO_REUSEPORTの本来の目的と合致しています。コメントの更新は、このオプションがマルチキャストの文脈でなぜ重要であるかを明確にするためのものです。

要するに、この変更はSO_REUSEPORTの適用範囲を、一般的なリスナーソケットからマルチキャストソケットに限定することで、BSD系OSにおけるGoのネットワークスタックの堅牢性と予測可能性を高めています。

関連リンク

参考にした情報源リンク