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

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

このコミットは、Go言語の標準ライブラリであるnetパッケージにおけるマルチキャストUDP接続のLocalAddrメソッドの挙動を修正するものです。具体的には、マルチキャストグループアドレスを正しく返すように変更されています。変更は主にsrc/pkg/net/sock.gosrc/pkg/net/multicast_test.gosrc/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.SockaddrInet4syscall.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を使用するように変更されました。

具体的には、マルチキャストソケットの場合、ListenMulticastUDPgaddr(マルチキャストグループアドレス)をlaとしてsocket関数に渡します。socket関数内で、ソケットはbla(通常はワイルドカードアドレス)にバインドされます。このとき、lablaは異なるため、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)`など)