[インデックス 14387] ファイルの概要
このコミットは、Go言語の標準ライブラリである net
パッケージにおけるUDPマルチキャスト接続の LocalAddr
メソッドの振る舞いを修正するものです。具体的には、マルチキャストUDPコネクションの LocalAddr
が、参加しているグループアドレスではなく、実際にパケットを受信しているローカルリスニングアドレスを返すように変更されています。これにより、単一のUDPリスナーが複数のマルチキャストグループに参加できる go.net/ipv4
パッケージの機能と整合性が取れるようになります。
コミット
commit 0ae80785e6c6f34e488be2fb5a76e0c1ab44f4a4
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Tue Nov 13 12:26:20 2012 +0900
net: make LocalAddr on multicast UDPConn return a listening address
The package go.net/ipv4 allows to exist a single UDP listener
that join multiple different group addresses. That means that
LocalAddr on multicast UDPConn returns a first joined group
address is not desirable.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6822108
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0ae80785e6c6f34e488be2fb5a76e0c1ab44f4a4
元コミット内容
net: make LocalAddr on multicast UDPConn return a listening address
The package go.net/ipv4 allows to exist a single UDP listener
that join multiple different group addresses. That means that
LocalAddr on multicast UDPConn returns a first joined group
address is not desirable.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6822108
変更の背景
Go言語の net
パッケージは、ネットワーク通信のための基本的な機能を提供します。その中で、UDP (User Datagram Protocol) はコネクションレスな通信プロトコルであり、マルチキャスト通信にも利用されます。マルチキャストとは、特定のグループに属する複数の受信者に対して、単一の送信元からデータを一度に送信する通信方式です。
このコミットが行われる以前は、Goの net
パッケージにおけるマルチキャストUDPコネクションの LocalAddr()
メソッドは、そのコネクションが最初に結合したマルチキャストグループのアドレスを返していました。しかし、go.net/ipv4
パッケージ(これはGoのネットワーク機能の拡張を提供するパッケージの一つです)は、単一のUDPリスナーが複数の異なるマルチキャストグループアドレスに参加できるように設計されていました。
この設計上の不一致が問題を引き起こしていました。もし LocalAddr()
が単一のグループアドレスしか返さない場合、そのUDPコネクションが実際にリッスンしている(つまり、パケットを受信できる)アドレスを正確に反映しているとは言えません。特に、複数のグループに参加している場合、LocalAddr()
が返すアドレスは、そのコネクションが受信可能なすべてのトラフィックを代表するものではなくなってしまいます。
この問題を解決し、go.net/ipv4
の機能と net
パッケージの振る舞いを整合させるために、LocalAddr()
がマルチキャストグループアドレスではなく、ソケットが実際にバインドされているローカルリスニングアドレス(通常はワイルドカードアドレスとポート番号)を返すように変更する必要がありました。
前提知識の解説
UDP (User Datagram Protocol)
UDPは、TCPと並ぶ主要なトランスポート層プロトコルの一つです。TCPが信頼性のあるコネクション指向の通信を提供するのに対し、UDPはコネクションレスで、データの到達保証や順序保証を行いません。そのため、オーバーヘッドが少なく、高速な通信が可能です。ストリーミングやDNSなど、リアルタイム性や高速性が求められるアプリケーションで利用されます。
マルチキャスト通信
マルチキャストは、ネットワーク上の特定のグループに属する複数のホストに対して、単一のデータグラムを送信する通信方式です。これにより、送信元はデータを一度だけ送信すればよく、帯域幅の効率的な利用が可能になります。IPマルチキャストでは、特定のIPアドレス(マルチキャストアドレス)とポート番号の組み合わせにデータを送信し、そのグループに参加しているホストがデータを受信します。
LocalAddr()
メソッド
Goの net
パッケージにおける Conn
インターフェース(UDPConn
もこれを実装)は、LocalAddr()
メソッドを提供します。このメソッドは、ネットワークコネクションのローカルエンドポイントアドレスを返します。通常、これはローカルマシンのIPアドレスと、そのコネクションが使用しているポート番号の組み合わせになります。
go.net/ipv4
パッケージ
go.net/ipv4
は、Goの標準 net
パッケージを補完し、IPv4固有の高度なネットワーク機能を提供するパッケージです。これには、IPマルチキャストグループへの参加/脱退、IPヘッダーオプションの操作などが含まれます。このパッケージの重要な機能の一つは、単一のUDPソケットが複数のマルチキャストグループに参加できることです。これは、IP_ADD_MEMBERSHIP
や IP_JOIN_GROUP
といったソケットオプションを適切に設定することで実現されます。
ワイルドカードアドレス (Wildcard Address)
ワイルドカードアドレスは、特定のIPアドレスを指定する代わりに、すべての利用可能なIPアドレスを表すために使用される特別なIPアドレスです。IPv4では 0.0.0.0
、IPv6では ::
がこれに該当します。サーバーアプリケーションが特定のネットワークインターフェースに限定されずに、すべての利用可能なインターフェースからの接続を受け入れたい場合に、ソケットをワイルドカードアドレスにバインドします。マルチキャストリスナーの場合、ワイルドカードアドレスにバインドすることで、そのポート宛てのすべてのマルチキャストトラフィックを受信できるようになります。
ソケットバインディング (syscall.Bind
)
ソケットバインディングとは、作成されたソケットを特定のローカルIPアドレスとポート番号に結びつける操作です。これにより、そのソケットは指定されたアドレスとポートでデータを受信できるようになります。Goの net
パッケージの内部では、OSのシステムコールである bind()
(Unix系OSでは syscall.Bind
) が利用されます。
syscall.Getsockname
syscall.Getsockname
は、指定されたソケットのローカルアドレス(ソケットがバインドされているアドレスとポート)を取得するためのシステムコールです。この関数は、ソケットが実際にどのIPアドレスとポートにバインドされているかを確認するために使用されます。
技術的詳細
このコミットの核心は、src/pkg/net/sock_posix.go
ファイル内の socket
関数におけるソケットのバインディングロジックと、LocalAddr
の決定ロジックの変更です。
以前の socket
関数では、ulsa
(user-provided local socket address) とは別に blsa
(bound local socket address) という変数が存在し、listenerSockaddr
関数が ulsa
を基に blsa
を生成し、その blsa
にソケットをバインドしていました。そして、LocalAddr
の決定時には、ulsa
と blsa
が異なる場合に ulsa
を優先して返すようなロジックが含まれていました。
このコミットでは、以下の変更が加えられました。
blsa
変数の削除:sock_posix.go
からblsa
変数が削除されました。これにより、ulsa
が直接バインドされるようになりました。- バインディングロジックの変更:
この変更により、// 変更前 // var blsa syscall.Sockaddr // if ulsa != nil { // if blsa, err = listenerSockaddr(s, f, ulsa, toAddr); err != nil { // closesocket(s) // return nil, err // } // if err = syscall.Bind(s, blsa); err != nil { // closesocket(s) // return nil, err // } // } // 変更後 if ulsa != nil { // We provide a socket that listens to a wildcard // address with reusable UDP port when the given ulsa // is an appropriate UDP multicast address prefix. // This makes it possible for a single UDP listener // to join multiple different group addresses, for // multiple UDP listeners that listen on the same UDP // port to join the same group address. if ulsa, err = listenerSockaddr(s, f, ulsa, toAddr); err != nil { closesocket(s) return nil, err } if err = syscall.Bind(s, ulsa); err != nil { closesocket(s) return nil, err } }
listenerSockaddr
がulsa
を適切に変換した後、その変換されたulsa
に直接ソケットがバインドされるようになりました。追加されたコメントは、この変更の意図を明確にしています。すなわち、適切なUDPマルチキャストアドレスプレフィックスが与えられた場合、ワイルドカードアドレスと再利用可能なUDPポートでリッスンするソケットを提供することで、単一のUDPリスナーが複数の異なるグループアドレスに参加できるようになる、という点です。これは、SO_REUSEADDR
やSO_REUSEPORT
といったソケットオプションが内部的に設定されることを示唆しています。 LocalAddr
決定ロジックの簡素化:// 変更前 // lsa, _ := syscall.Getsockname(s) // var laddr Addr // if ulsa != nil && blsa != ulsa { // laddr = toAddr(ulsa) // } else { // laddr = toAddr(lsa) // } // 変更後 lsa, _ := syscall.Getsockname(s) laddr := toAddr(lsa)
LocalAddr
を決定する際に、常にsyscall.Getsockname(s)
で取得した実際のソケットのローカルアドレス (lsa
) を使用するように変更されました。これにより、LocalAddr
はソケットが実際にバインドされているアドレスを正確に返すことが保証されます。
これらの変更は、src/pkg/net/multicast_posix_test.go
内のテストコードにも反映されています。以前のテストでは、LocalAddr()
がマルチキャストグループアドレスを返すことを期待していましたが、変更後は LocalAddr()
が非ゼロのポート番号を持つ適切なアドレス(ワイルドカードアドレスとポート)を返すことを期待するように修正されています。
例えば、checkMulticastListener
関数では、以前は c.LocalAddr().String() != gaddr.String()
というチェックがありましたが、これは削除され、代わりに a, ok := la.(*UDPAddr); !ok || a.Port == 0
というチェックが追加されました。これは、返されたアドレスが UDPAddr
型であり、かつポート番号がゼロではないことを確認しています。これは、ソケットが実際にリッスンしているアドレス(通常はワイルドカードアドレスと動的に割り当てられたポート)を検証するためのものです。
コアとなるコードの変更箇所
src/pkg/net/multicast_posix_test.go
--- a/src/pkg/net/multicast_posix_test.go
+++ b/src/pkg/net/multicast_posix_test.go
@@ -118,16 +118,29 @@ func TestSimpleMulticastListener(t *testing.T) {
func checkMulticastListener(t *testing.T, err error, c *UDPConn, gaddr *UDPAddr) {
if !multicastRIBContains(t, gaddr.IP) {
- t.Fatalf("%q not found in RIB", gaddr.String())
+ t.Errorf("%q not found in RIB", gaddr.String())
+ return
+ }
+ la := c.LocalAddr()
+ if la == nil {
+ t.Error("LocalAddr failed")
+ return
}
- if c.LocalAddr().String() != gaddr.String() {
- t.Fatalf("LocalAddr returns %q, expected %q", c.LocalAddr().String(), gaddr.String())
+ if a, ok := la.(*UDPAddr); !ok || a.Port == 0 {
+ t.Errorf("got %v; expected a proper address with non-zero port number", la)
+ return
}
}
func checkSimpleMulticastListener(t *testing.T, err error, c *UDPConn, gaddr *UDPAddr) {
- if c.LocalAddr().String() != gaddr.String() {
- t.Fatalf("LocalAddr returns %q, expected %q", c.LocalAddr().String(), gaddr.String())
+ la := c.LocalAddr()
+ if la == nil {
+ t.Error("LocalAddr failed")
+ return
+ }
+ if a, ok := la.(*UDPAddr); !ok || a.Port == 0 {
+ t.Errorf("got %v; expected a proper address with non-zero port number", la)
+ return
}
}
src/pkg/net/sock_posix.go
--- a/src/pkg/net/sock_posix.go
+++ b/src/pkg/net/sock_posix.go
@@ -33,13 +33,19 @@ func socket(net string, f, t, p int, ipv6only bool, ulsa, ursa syscall.Sockaddr,
return nil, err
}
- var blsa syscall.Sockaddr
if ulsa != nil {
- if blsa, err = listenerSockaddr(s, f, ulsa, toAddr); err != nil {
+ // We provide a socket that listens to a wildcard
+ // address with reusable UDP port when the given ulsa
+ // is an appropriate UDP multicast address prefix.
+ // This makes it possible for a single UDP listener
+ // to join multiple different group addresses, for
+ // multiple UDP listeners that listen on the same UDP
+ // port to join the same group address.
+ if ulsa, err = listenerSockaddr(s, f, ulsa, toAddr); err != nil {
closesocket(s)
return nil, err
}
- if err = syscall.Bind(s, blsa); err != nil {
+ if err = syscall.Bind(s, ulsa); err != nil {
closesocket(s)
return nil, err
}
@@ -64,12 +70,7 @@ func socket(net string, f, t, p int, ipv6only bool, ulsa, ursa syscall.Sockaddr,\
}
lsa, _ := syscall.Getsockname(s)
- var laddr Addr
- if ulsa != nil && blsa != ulsa {
- laddr = toAddr(ulsa)
- } else {
- laddr = toAddr(lsa)
- }
+ laddr := toAddr(lsa)
rsa, _ := syscall.Getpeername(s)
raddr := toAddr(rsa)
fd.setAddr(laddr, raddr)
コアとなるコードの解説
src/pkg/net/multicast_posix_test.go
の変更点
checkMulticastListener
およびcheckSimpleMulticastListener
関数において、c.LocalAddr().String() != gaddr.String()
というアサーションが削除されました。これは、LocalAddr
がマルチキャストグループアドレスではなく、実際のリスニングアドレスを返すようになったため、このチェックが不適切になったためです。- 代わりに、以下の新しいチェックが追加されました。
このコードは、la := c.LocalAddr() if la == nil { t.Error("LocalAddr failed") return } if a, ok := la.(*UDPAddr); !ok || a.Port == 0 { t.Errorf("got %v; expected a proper address with non-zero port number", la) return }
LocalAddr()
がnil
でないこと、そして返されたアドレスが*net.UDPAddr
型であり、かつそのポート番号が0
ではないことを検証しています。これは、ソケットが実際にワイルドカードアドレス(例:0.0.0.0
)と、OSによって割り当てられた非ゼロのポート番号にバインドされていることを確認するためのものです。
src/pkg/net/sock_posix.go
の変更点
blsa
変数の削除: 以前はblsa
という一時変数が存在し、listenerSockaddr
の結果を保持していましたが、これが削除され、ulsa
が直接更新されるようになりました。これにより、コードが簡潔になり、ulsa
が最終的にバインドされるアドレスを直接表すようになりました。- ソケットバインディングロジックの変更:
この変更により、if ulsa, err = listenerSockaddr(s, f, ulsa, toAddr); err != nil { ... } if err = syscall.Bind(s, ulsa); err != nil { ... }
listenerSockaddr
関数がulsa
を適切に変換(例えば、マルチキャストアドレスプレフィックスからワイルドカードアドレスと再利用可能なポートを持つアドレスに変換)した後、その変換されたulsa
に直接ソケットがバインドされるようになりました。これにより、単一のソケットが複数のマルチキャストグループからのトラフィックを受信できるようになります。 laddr
決定ロジックの簡素化:
以前は、lsa, _ := syscall.Getsockname(s) laddr := toAddr(lsa)
ulsa
とblsa
の比較に基づいてladdr
を決定する複雑なロジックがありましたが、これが削除されました。新しいロジックでは、常にsyscall.Getsockname(s)
を呼び出して、OSがソケットに実際に割り当てたローカルアドレス (lsa
) を取得し、それをladdr
として設定します。これにより、LocalAddr()
が常にソケットの真のリスニングアドレスを返すことが保証されます。
これらの変更は、Goの net
パッケージがマルチキャスト通信をより柔軟かつ正確に扱えるようにするための重要な改善です。特に、go.net/ipv4
の高度なマルチキャスト機能との整合性を高め、開発者がより堅牢なマルチキャストアプリケーションを構築できるように貢献しています。
関連リンク
- Go言語の
net
パッケージ公式ドキュメント: https://pkg.go.dev/net - Go言語の
go.net/ipv4
パッケージ公式ドキュメント: https://pkg.go.dev/golang.org/x/net/ipv4 - Go言語の
syscall
パッケージ公式ドキュメント: https://pkg.go.dev/syscall - Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージにある
https://golang.org/cl/6822108
は、このGerritの変更リストへのリンクです)
参考にした情報源リンク
- https://github.com/golang/go/commit/0ae80785e6c6f34e488be2fb5a76e0c1ab44f4a4
- https://golang.org/cl/6822108 (Go Gerrit Change-ID)
- UDP Multicast: https://en.wikipedia.org/wiki/IP_multicast
- Socket Options (SO_REUSEADDR, SO_REUSEPORT): https://man7.org/linux/man-pages/man7/socket.7.html
bind()
system call: https://man7.org/linux/man-pages/man2/bind.2.htmlgetsockname()
system call: https://man7.org/linux/man-pages/man2/getsockname.2.html