[インデックス 12040] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnet
パッケージにおけるマルチキャストUDP接続のLocalAddr
メソッドの挙動を修正するものです。具体的には、マルチキャストグループアドレスを正しく返すように変更されています。変更は主にsrc/pkg/net/sock.go
、src/pkg/net/multicast_test.go
、src/pkg/net/udpsock_posix.go
の3つのファイルにわたります。
コミット
commit e91bf2e9d1c3bf5e03340eb86bc6e34f82bb205f
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Sun Feb 19 10:50:03 2012 +0900
net: make LocalAddr on multicast return group address
Fixes #3067.
R=golang-dev, rsc, rsc
CC=golang-dev
https://golang.org/cl/5656098
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e91bf2e9d1c3bf5e03340eb86bc6e34f82bb205f
元コミット内容
net: make LocalAddr on multicast return group address
Fixes #3067.
R=golang-dev, rsc, rsc
CC=golang-dev
https://golang.org/cl/5656098
変更の背景
この変更は、Go言語のIssue #3067「net: UDPConn.LocalAddr of ListenMulticastUDP returns 0.0.0.0:12345, not 224.x.x.x:12345」を修正するために行われました。
問題は、net
パッケージのListenMulticastUDP
関数を使用してマルチキャストグループに参加し、その結果得られるUDPConn
オブジェクトのLocalAddr()
メソッドを呼び出した際に、期待されるマルチキャストグループアドレス(例: 224.x.x.x:ポート番号
)ではなく、ワイルドカードアドレス(0.0.0.0:ポート番号
)が返されるというものでした。
通常、LocalAddr()
はソケットがバインドされているローカルアドレスを返します。マルチキャストリスナーの場合、ソケットは特定のマルチキャストグループアドレスに「参加」し、そのグループからのトラフィックを受信します。しかし、内部的なソケットのバインド処理では、多くの場合、特定のインターフェースやアドレスではなく、INADDR_ANY
(0.0.0.0) のようなワイルドカードアドレスにバインドされます。これは、複数のインターフェースからのマルチキャストトラフィックを受信できるようにするためです。
しかし、ユーザーやアプリケーションの視点から見ると、マルチキャストリスナーの「ローカルアドレス」は、そのリスナーが参加しているマルチキャストグループアドレスであると期待されます。0.0.0.0
が返されると、アプリケーションがどのマルチキャストグループをリッスンしているのかをプログラム的に判断するのが難しくなります。このコミットは、この期待される挙動と実際の挙動の乖離を修正することを目的としています。
前提知識の解説
マルチキャスト (Multicast)
マルチキャストは、ネットワーク通信の一種で、単一の送信元から特定のグループに属する複数の受信者に対してデータを送信する方式です。ユニキャスト(1対1)とブロードキャスト(1対全員)の中間に位置します。IPマルチキャストでは、特定のIPアドレス範囲(IPv4では224.0.0.0から239.255.255.255)がマルチキャストグループアドレスとして予約されています。受信者はこれらのグループアドレスに参加(Join)することで、そのグループ宛のデータを受信できるようになります。
net.UDPConn
Go言語のnet
パッケージにおけるUDPConn
は、UDP(User Datagram Protocol)ソケットを表す型です。UDPはコネクションレスなプロトコルであり、信頼性よりも速度を重視するアプリケーション(例: ストリーミング、ゲーム)でよく使用されます。UDPConn
は、データの送受信、ローカルアドレスやリモートアドレスの取得などの機能を提供します。
LocalAddr()
メソッド
net.Conn
インターフェース(UDPConn
もこれを実装)が持つメソッドで、接続のローカルエンドポイントのアドレスを返します。UDPソケットの場合、これはソケットがバインドされているローカルIPアドレスとポート番号を指します。
syscall.Sockaddr
Go言語のsyscall
パッケージは、オペレーティングシステム(OS)のシステムコールへの低レベルなインターフェースを提供します。syscall.Sockaddr
は、ソケットアドレスを表すインターフェースで、OS固有のソケットアドレス構造体(例: syscall.SockaddrInet4
、syscall.SockaddrInet6
)を抽象化します。
syscall.Getsockname()
syscall
パッケージの関数で、指定されたソケットのローカルアドレスを取得します。OSのシステムコールgetsockname(2)
に対応します。通常、ソケットがバインドされた後に、実際にOSが割り当てたアドレス(特にポート番号が0でバインドされた場合)を取得するために使用されます。
syscall.Bind()
syscall
パッケージの関数で、ソケットを特定のローカルアドレスにバインドします。OSのシステムコールbind(2)
に対応します。これにより、ソケットは指定されたIPアドレスとポート番号でデータを受信できるようになります。
ワイルドカードアドレス (Wildcard Address)
IPアドレス0.0.0.0
(IPv4の場合)は、ワイルドカードアドレスまたは「任意のアドレス」を意味します。ソケットをこのアドレスにバインドすると、そのソケットは、そのホストの利用可能なすべてのネットワークインターフェースからの接続やデータグラムを受け入れるようになります。マルチキャストリスナーの場合、特定のインターフェースに縛られずにマルチキャストトラフィックを受信するために、しばしばワイルドカードアドレスにバインドされます。
技術的詳細
このコミットの核心は、net
パッケージの内部関数であるsocket
の挙動変更にあります。socket
関数は、新しいソケットを作成し、必要に応じてローカルアドレスにバインドする役割を担っています。
従来のsocket
関数では、ソケットがバインドされた後、syscall.Getsockname(s)
を呼び出してソケットのローカルアドレスを取得し、それをladdr
として設定していました。しかし、マルチキャストリスニングの場合、ListenMulticastUDP
は内部的にソケットをワイルドカードアドレス(0.0.0.0
)にバインドすることがあります。このため、Getsockname
が返すアドレスも0.0.0.0
となり、これがUDPConn.LocalAddr()
の戻り値となっていました。
この修正では、socket
関数内でla
(リスナーが指定したローカルアドレス、マルチキャストの場合はマルチキャストグループアドレス)がnil
でなく、かつsyscall.Bind
に実際に渡されたアドレスbla
と異なる場合に、laddr
としてla
を使用するように変更されました。
具体的には、マルチキャストソケットの場合、ListenMulticastUDP
はgaddr
(マルチキャストグループアドレス)をla
としてsocket
関数に渡します。socket
関数内で、ソケットはbla
(通常はワイルドカードアドレス)にバインドされます。このとき、la
とbla
は異なるため、laddr
にはla
、つまりマルチキャストグループアドレスが設定されるようになります。これにより、UDPConn.LocalAddr()
が正しいマルチキャストグループアドレスを返すようになります。
また、multicast_test.go
にテストケースが追加され、LocalAddr()
が期待されるマルチキャストグループアドレスを返すことを検証しています。udpsock_posix.go
では、エラーメッセージの修正が行われています。
コアとなるコードの変更箇所
src/pkg/net/multicast_test.go
--- a/src/pkg/net/multicast_test.go
+++ b/src/pkg/net/multicast_test.go
@@ -81,6 +81,9 @@ func TestListenMulticastUDP(t *testing.T) {
if !found {
t.Fatalf("%q not found in RIB", tt.gaddr.String())
}
+ if c.LocalAddr().String() != tt.gaddr.String() {
+ t.Fatalf("LocalAddr returns %q, expected %q", c.LocalAddr().String(), tt.gaddr.String())
+ }
}
}
@@ -114,6 +117,9 @@ func TestSimpleListenMulticastUDP(t *testing.T) {
if err != nil {
t.Fatalf("ListenMulticastUDP failed: %v", err)
}
+ if c.LocalAddr().String() != tt.gaddr.String() {
+ t.Fatalf("LocalAddr returns %q, expected %q", c.LocalAddr().String(), tt.gaddr.String())
+ }
c.Close()
}
}
src/pkg/net/sock.go
--- a/src/pkg/net/sock.go
+++ b/src/pkg/net/sock.go
@@ -33,13 +33,14 @@ func socket(net string, f, t, p int, la, ra syscall.Sockaddr, toAddr func(syscal
return nil, err
}
+ var bla syscall.Sockaddr
if la != nil {
- la, err = listenerSockaddr(s, f, la, toAddr)
+ bla, err = listenerSockaddr(s, f, la, toAddr)
if err != nil {
closesocket(s)
return nil, err
}
- err = syscall.Bind(s, la)
+ err = syscall.Bind(s, bla)
if err != nil {
closesocket(s)
return nil, err
@@ -61,7 +62,12 @@ func socket(net string, f, t, p int, la, ra syscall.Sockaddr, toAddr func(syscal
}
sa, _ := syscall.Getsockname(s)
- laddr := toAddr(sa)
+ var laddr Addr
+ if la != nil && bla != la {
+ laddr = toAddr(la)
+ } else {
+ laddr = toAddr(sa)
+ }
sa, _ = syscall.Getpeername(s)
raddr := toAddr(sa)
src/pkg/net/udpsock_posix.go
--- a/src/pkg/net/udpsock_posix.go
+++ b/src/pkg/net/udpsock_posix.go
@@ -262,7 +262,7 @@ func ListenMulticastUDP(net string, ifi *Interface, gaddr *UDPAddr) (*UDPConn, e
return nil, UnknownNetworkError(net)
}\n \tif gaddr == nil || gaddr.IP == nil {\n-\t\treturn nil, &OpError{\"listenmulticastudp\", \"udp\", nil, errMissingAddress}\n+\t\treturn nil, &OpError{\"listenmulticast\", net, nil, errMissingAddress}\n \t}\n \tfd, err := internetSocket(net, gaddr.toAddr(), nil, syscall.SOCK_DGRAM, 0, \"listen\", sockaddrToUDP)\n \tif err != nil {\n```
## コアとなるコードの解説
### `src/pkg/net/multicast_test.go` の変更
- `TestListenMulticastUDP` と `TestSimpleListenMulticastUDP` の両方に、`c.LocalAddr().String() != tt.gaddr.String()` というアサーションが追加されました。
- これは、`ListenMulticastUDP`で取得した`UDPConn`オブジェクト`c`の`LocalAddr()`メソッドが返す文字列が、テストケースで期待されるマルチキャストグループアドレス`tt.gaddr`の文字列と一致するかどうかを検証しています。
- このテストの追加により、`LocalAddr()`が正しくマルチキャストグループアドレスを返すという新しい挙動が保証されるようになりました。
### `src/pkg/net/sock.go` の変更
このファイルは、ソケットの作成とバインドに関する低レベルな処理を担う`socket`関数を含んでいます。
1. **`bla`変数の導入**:
```go
+ var bla syscall.Sockaddr
if la != nil {
- la, err = listenerSockaddr(s, f, la, toAddr)
+ bla, err = listenerSockaddr(s, f, la, toAddr)
// ...
- err = syscall.Bind(s, la)
+ err = syscall.Bind(s, bla)
```
- 以前は`la`変数が`listenerSockaddr`の戻り値を受け取り、そのまま`syscall.Bind`に渡されていました。
- 新しいコードでは、`listenerSockaddr`の戻り値(実際にソケットがバインドされるアドレス、マルチキャストの場合はワイルドカードアドレスになることが多い)を`bla`("bind local address"の略か)という新しい変数で受け取るように変更されました。
- これにより、`la`(ユーザーが指定したローカルアドレス、マルチキャストの場合はマルチキャストグループアドレス)と`bla`(実際にソケットがバインドされたアドレス)を区別できるようになります。
2. **`laddr`の決定ロジックの変更**:
```go
sa, _ := syscall.Getsockname(s)
- laddr := toAddr(sa)
+ var laddr Addr
+ if la != nil && bla != la {
+ laddr = toAddr(la)
+ } else {
+ laddr = toAddr(sa)
+ }
```
- 以前は、`syscall.Getsockname(s)`(ソケットが実際にバインドされているアドレスを取得)の結果を直接`laddr`として設定していました。これがIssue #3067の原因でした。
- 新しいロジックでは、`laddr`の決定に条件分岐が導入されました。
- `la != nil` かつ `bla != la` の場合:
- これは、ユーザーが特定のローカルアドレス(`la`)を指定したが、ソケットが内部的に異なるアドレス(`bla`、例えばワイルドカードアドレス)にバインドされたケース(マルチキャストの場合がこれに該当)を意味します。
- この場合、`laddr`にはユーザーが指定した`la`が設定されます。これにより、`LocalAddr()`はマルチキャストグループアドレスを返すようになります。
- それ以外の場合:
- `la`が`nil`(ユーザーがローカルアドレスを指定しなかった場合)や、`bla`と`la`が同じ場合(ソケットが指定されたアドレスに直接バインドされた場合)は、これまで通り`syscall.Getsockname(s)`の結果が`laddr`として設定されます。
- この変更が、マルチキャストの`LocalAddr()`が正しくなるための主要な修正点です。
### `src/pkg/net/udpsock_posix.go` の変更
```diff
--- a/src/pkg/net/udpsock_posix.go
+++ b/src/pkg/net/udpsock_posix.go
@@ -262,7 +262,7 @@ func ListenMulticastUDP(net string, ifi *Interface, gaddr *UDPAddr) (*UDPConn, e
return nil, UnknownNetworkError(net)
}\n \tif gaddr == nil || gaddr.IP == nil {\n-\t\treturn nil, &OpError{\"listenmulticastudp\", \"udp\", nil, errMissingAddress}\n+\t\treturn nil, &OpError{\"listenmulticast\", net, nil, errMissingAddress}\n \t}\n \tfd, err := internetSocket(net, gaddr.toAddr(), nil, syscall.SOCK_DGRAM, 0, \"listen\", sockaddrToUDP)\n \tif err != nil {\n```
- `ListenMulticastUDP`関数内のエラーメッセージが修正されました。
- 以前は`OpError`の最初の引数が`"listenmulticastudp"`でしたが、`"listenmulticast"`に変更され、2番目の引数に`net`(ネットワークタイプ、例: "udp")が追加されました。
- これは機能的な変更ではなく、エラーメッセージの整合性や詳細度を向上させるための修正です。
## 関連リンク
* Go Issue #3067: [https://github.com/golang/go/issues/3067](https://github.com/golang/go/issues/3067)
* Go CL 5656098: [https://golang.org/cl/5656098](https://golang.org/cl/5656098)
## 参考にした情報源リンク
* Web search results for "golang issue 3067 LocalAddr multicast": [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF7D9pLOpTnia_thssSSizJjetVTT1tbGTZTyqZ3MygVGrmi_17_PJPdWhgWjnW1za8mfXu37s_7IdbqnONHMSRzNAMvgbTM1jAry4zKmHDx5WaWe_E7iDGqD47ZMXu8FCFL50=](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF7D9pLOpTnia_thssSSizJjetVTT1tbGTZTyqZ3MygVGrmi_17_PJPdWhgWjnW1za8mfXu37s_7IdbqnONHMSRzNAMvgbTM1jAry4zKmHDx5WaWe_E7iDGqD47ZMXu8FCFL50=)
* Go言語の`net`パッケージのドキュメント (当時のバージョンに基づく)
* Unix系OSのソケットプログラミングに関する一般的な知識 (`bind(2)`, `getsockname(2)`など)