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

[インデックス 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_MEMBERSHIPIP_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 の決定時には、ulsablsa が異なる場合に ulsa を優先して返すようなロジックが含まれていました。

このコミットでは、以下の変更が加えられました。

  1. blsa 変数の削除: sock_posix.go から blsa 変数が削除されました。これにより、ulsa が直接バインドされるようになりました。
  2. バインディングロジックの変更:
    // 変更前
    // 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
        }
    }
    
    この変更により、listenerSockaddrulsa を適切に変換した後、その変換された ulsa に直接ソケットがバインドされるようになりました。追加されたコメントは、この変更の意図を明確にしています。すなわち、適切なUDPマルチキャストアドレスプレフィックスが与えられた場合、ワイルドカードアドレスと再利用可能なUDPポートでリッスンするソケットを提供することで、単一のUDPリスナーが複数の異なるグループアドレスに参加できるようになる、という点です。これは、SO_REUSEADDRSO_REUSEPORT といったソケットオプションが内部的に設定されることを示唆しています。
  3. 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)
    
    以前は、ulsablsa の比較に基づいて laddr を決定する複雑なロジックがありましたが、これが削除されました。新しいロジックでは、常に syscall.Getsockname(s) を呼び出して、OSがソケットに実際に割り当てたローカルアドレス (lsa) を取得し、それを laddr として設定します。これにより、LocalAddr() が常にソケットの真のリスニングアドレスを返すことが保証されます。

これらの変更は、Goの net パッケージがマルチキャスト通信をより柔軟かつ正確に扱えるようにするための重要な改善です。特に、go.net/ipv4 の高度なマルチキャスト機能との整合性を高め、開発者がより堅牢なマルチキャストアプリケーションを構築できるように貢献しています。

関連リンク

参考にした情報源リンク