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

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

このコミットは、Go言語の net パッケージにおけるNetBSD固有のネットワークインターフェースアドレス処理に関するバグ修正です。具体的には、NetBSD環境でインターフェースアドレスを取得する際に、sockaddr_dl 型のアドレスが誤って nil ではないインターフェースアドレスとして返される問題を修正し、テストコードもそれに対応して更新しています。これにより、NetBSD上でのネットワークインターフェース情報の取得がより正確になります。

コミット

  • コミットハッシュ: 12e7397ebb4e8c5444f9b30b84a6b80059d073fb
  • 作者: Mikio Hara mikioh.mikioh@gmail.com
  • コミット日時: 2013年1月23日(水) 07:11:22 +0900
  • コミットメッセージ:
    net: don't return nil interface address on netbsd
    
    On NetBSD routing sockaddrs for interface address contain sockaddr_dl.
    
    R=dave, rsc
    CC=golang-dev
    https://golang.org/cl/7085064
    

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

https://github.com/golang/go/commit/12e7397ebb4e8c5444f9b30b84a6b80059d073fb

元コミット内容

net: don't return nil interface address on netbsd

On NetBSD routing sockaddrs for interface address contain sockaddr_dl.

R=dave, rsc
CC=golang-dev
https://golang.org/cl/7085064

変更の背景

この変更の背景には、NetBSDオペレーティングシステムにおけるネットワークインターフェースアドレスの取得方法の特殊性があります。Unix系システムでは、ネットワークインターフェースに関する情報は通常、ioctl システムコールやルーティングソケットを通じて取得されます。これらのシステムコールは、インターフェースに割り当てられたIPアドレス、ネットマスク、ブロードキャストアドレス、マルチキャストアドレスなどの情報を提供します。

Go言語の net パッケージは、これらのOS固有のメカニズムを抽象化し、クロスプラットフォームでネットワークインターフェース情報を取得できるAPIを提供しています。しかし、NetBSDでは、ルーティングソケットから取得されるインターフェースアドレス情報の中に、sockaddr_dl (datalink socket address) 型のアドレスが含まれることがあります。この sockaddr_dl は、IPアドレスのようなネットワーク層のアドレスではなく、MACアドレスなどのデータリンク層のアドレス情報を含んでいます。

Goの net パッケージの既存の実装では、sockaddr_dl 型のアドレスが渡された場合に、それが有効なIPアドレスとして解釈されず、結果的に nil のインターフェースアドレスが生成されてしまう問題がありました。この nil のアドレスがインターフェースアドレスのリストに追加されてしまうと、後続の処理で nil ポインタ参照などの問題を引き起こす可能性がありました。

このコミットは、NetBSD環境で sockaddr_dl 型のアドレスが検出された場合に、それを有効なIPアドレスとして扱わず、nil を返すように newAddr 関数を修正することで、この問題を解決しています。これにより、interfaceAddrTable 関数が nil のインターフェースアドレスをリストに追加することを防ぎ、NetBSD上での net パッケージの堅牢性と正確性を向上させています。

前提知識の解説

このコミットを理解するためには、以下の前提知識が必要です。

  1. ネットワークインターフェース (Network Interface): コンピュータがネットワークに接続するためのハードウェアまたは仮想的なコンポーネントです。各インターフェースには、IPアドレス、MACアドレス、ネットマスクなどの情報が割り当てられます。

  2. ルーティングソケット (Routing Socket): Unix系OS(特にBSD系)で、カーネルのルーティングテーブルやネットワークインターフェースの状態に関する情報を取得・設定するために使用される特殊なソケットです。アプリケーションはルーティングソケットを通じて、インターフェースの追加・削除、IPアドレスの割り当て、ルーティングエントリの変更などを行うことができます。

  3. sockaddr 構造体: ソケットプログラミングにおいて、ソケットアドレスを表現するための汎用的な構造体です。異なる種類のアドレス(IPv4、IPv6、Unixドメインなど)を扱うために、sockaddr_insockaddr_in6sockaddr_un などの派生構造体が存在します。

  4. sockaddr_dl (Datalink Socket Address): BSD系OSで定義されている sockaddr の一種で、データリンク層のアドレス(例: MACアドレス)やインターフェース名などの情報を含みます。ルーティングソケットからインターフェース情報を取得する際に、この型の情報が返されることがあります。IPアドレスのようなネットワーク層のアドレスとは異なります。

  5. Go言語の net パッケージ: Go言語の標準ライブラリの一部で、ネットワークI/O機能を提供します。TCP/IP、UDP、DNSルックアップ、ネットワークインターフェース情報の取得など、様々なネットワーク関連の操作をサポートします。

  6. syscall パッケージ: Go言語の標準ライブラリの一部で、OS固有のシステムコールへの低レベルなアクセスを提供します。ネットワークインターフェース情報の取得など、OSカーネルと直接やり取りする必要がある場合に利用されます。このコミットでは、syscall.InterfaceAddrMessagesyscall.SockaddrDatalink といったOS固有の構造体や定数が使用されています。

  7. Addr インターフェース (Go言語): net パッケージで定義されているインターフェースで、ネットワークアドレスを表します。IPAddrIPNet など、具体的なアドレス型がこのインターフェースを実装します。

  8. interfaceAddrTable 関数: Goの net パッケージ内で、特定のインターフェースインデックスに対応するインターフェースアドレスのリストを取得する内部関数です。OS固有の実装(例: interface_bsd.go)を持ちます。

  9. newAddr 関数: Goの net パッケージ内で、OSから取得した生のアドレス情報(syscall.InterfaceAddrMessage)をGoの net.Addr インターフェース型に変換する内部関数です。この関数が、OS固有のアドレス型をGoの抽象的なアドレス型にマッピングする役割を担います。

技術的詳細

このコミットは、Go言語の src/pkg/net/interface_bsd.go ファイルと src/pkg/net/interface_test.go ファイルに変更を加えています。

interface_bsd.go は、NetBSDを含むBSD系OSにおけるネットワークインターフェース情報の取得ロジックを実装しています。このファイルには、ルーティングソケットからインターフェースアドレスメッセージを読み取り、それをGoの net.Addr 型に変換する関数が含まれています。

変更の核心は、newAddr 関数にあります。この関数は、syscall.InterfaceAddrMessage からアドレス情報を抽出し、それがIPアドレス(IPv4またはIPv6)である場合に net.IPAddr または net.IPNet 型の Addr を返します。しかし、NetBSDでは、ルーティングソケットが sockaddr_dl 型のアドレスを返すことがあり、これがIPアドレスではないにもかかわらず、以前の実装では適切に処理されていませんでした。

修正前は、newAddr 関数内で syscall.SockaddrDatalink 型のアドレスが渡された場合、それがどの case にもマッチせず、結果的に ifa (インターフェースアドレス) が初期値のまま(nil)返される可能性がありました。そして、この nilinterfaceAddrTable 関数で ifat (インターフェースアドレステーブル) に追加されていました。

今回の修正では、newAddr 関数に default ケースを追加し、syscall.SockaddrDatalink 型のアドレスが渡された場合に明示的に nil, nil (アドレスとエラーの両方が nil) を返すようにしました。これにより、interfaceAddrTable 関数は nil のアドレスを受け取った際に、それを ifat に追加しないように変更されています。

また、テストファイル interface_test.go も更新され、testAddrs および testMulticastAddrs 関数内で、取得したインターフェースアドレスが nil でないことを確認するチェックが追加されました。これにより、将来的に同様の問題が発生した場合にテストで検出できるようになります。

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

diff --git a/src/pkg/net/interface_bsd.go b/src/pkg/net/interface_bsd.go
index 7f090d8d40..df9b3a2f27 100644
--- a/src/pkg/net/interface_bsd.go
+++ b/src/pkg/net/interface_bsd.go
@@ -118,7 +118,9 @@ func interfaceAddrTable(ifindex int) ([]Addr, error) {
 			if err != nil {
 				return nil, err
 			}
-			ifat = append(ifat, ifa)
+			if ifa != nil {
+				ifat = append(ifat, ifa)
+			}
 		}
 	}
 	return ifa, nil
@@ -157,6 +159,8 @@ func newAddr(m *syscall.InterfaceAddrMessage) (Addr, error) {
 					ifa.IP[2], ifa.IP[3] = 0, 0
 				}
 			}
+		default: // Sockaddrs contain syscall.SockaddrDatalink on NetBSD
+			return nil, nil
 		}
 	}
 	return ifa, nil
diff --git a/src/pkg/net/interface_test.go b/src/pkg/net/interface_test.go
index 2fe0f60cae..803c1f4495 100644
--- a/src/pkg/net/interface_test.go
+++ b/src/pkg/net/interface_test.go
@@ -75,9 +75,13 @@ func testInterfaceMulticastAddrs(t *testing.T, ifi *Interface) {
 
 func testAddrs(t *testing.T, ifat []Addr) {
 	for _, ifa := range ifat {
-		switch ifa.(type) {
+		switch v := ifa.(type) {
 		case *IPAddr, *IPNet:
-			t.Logf("	interface address %q", ifa.String())
+			if v == nil {
+				t.Errorf("	unexpected value: %v", ifa)
+			} else {
+				t.Logf("	interface address %q", ifa.String())
+			}
 		default:
 			t.Errorf("	unexpected type: %T", ifa)
 		}
@@ -86,9 +90,13 @@ func testMulticastAddrs(t *T, ifmat []Addr) {
 
 func testMulticastAddrs(t *T, ifmat []Addr) {
 	for _, ifma := range ifmat {
-		switch ifma.(type) {
+		switch v := ifma.(type) {
 		case *IPAddr:
-			t.Logf("	joined group address %q", ifma.String())
+			if v == nil {
+				t.Errorf("	unexpected value: %v", ifma)
+			} else {
+				t.Logf("	joined group address %q", ifma.String())
+			}
 		default:
 			t.Errorf("	unexpected type: %T", ifma)
 		}

コアとなるコードの解説

src/pkg/net/interface_bsd.go の変更

  1. func interfaceAddrTable(ifindex int) ([]Addr, error) 内の変更:

    @@ -118,7 +118,9 @@ func interfaceAddrTable(ifindex int) ([]Addr, error) {
     			if err != nil {
     				return nil, err
     			}
    -			ifat = append(ifat, ifa)
    +			if ifa != nil {
    +				ifat = append(ifat, ifa)
    +			}
     		}
     	}
     	return ifa, nil
    

    この変更は、newAddr 関数から返された ifa (インターフェースアドレス) が nil でない場合にのみ、ifat (インターフェースアドレステーブル) に追加するように修正しています。これにより、newAddrsockaddr_dl のようなIPアドレスではない情報を nil として返した場合に、その nil がリストに含まれることを防ぎます。

  2. func newAddr(m *syscall.InterfaceAddrMessage) (Addr, error) 内の変更:

    @@ -157,6 +159,8 @@ func newAddr(m *syscall.InterfaceAddrMessage) (Addr, error) {
     					ifa.IP[2], ifa.IP[3] = 0, 0
     				}
     			}
    +		default: // Sockaddrs contain syscall.SockaddrDatalink on NetBSD
    +			return nil, nil
     		}
     	}
     	return ifa, nil
    

    この変更は、newAddr 関数に default ケースを追加しています。これは、syscall.InterfaceAddrMessage 内のアドレスタイプが、既存の case (IPv4やIPv6) のいずれにもマッチしない場合に実行されます。NetBSDでは、ルーティングソケットが syscall.SockaddrDatalink 型のアドレスを返すことがあり、これがこの default ケースに該当します。この default ケースでは、明示的に nil, nil を返すことで、データリンク層のアドレスをIPアドレスとして誤って解釈することを防ぎます。

src/pkg/net/interface_test.go の変更

  1. func testAddrs(t *testing.T, ifat []Addr) 内の変更:

    @@ -75,9 +75,13 @@ func testInterfaceMulticastAddrs(t *T, ifi *Interface) {
     
     func testAddrs(t *T, ifat []Addr) {
     	for _, ifa := range ifat {
    -		switch ifa.(type) {
    +		switch v := ifa.(type) {
     		case *IPAddr, *IPNet:
    -			t.Logf("	interface address %q", ifa.String())
    +			if v == nil {
    +				t.Errorf("	unexpected value: %v", ifa)
    +			} else {
    +				t.Logf("	interface address %q", ifa.String())
    +			}
     		default:
     			t.Errorf("	unexpected type: %T", ifa)
     		}
    

    この変更は、switch ステートメントで型アサーションを行う際に、変数 v を導入し、*IPAddr または *IPNet のケースで vnil でないことを明示的にチェックしています。もし vnil であれば、それは予期せぬ値であるとしてエラーを報告します。これにより、nil のインターフェースアドレスがテストを通過してしまうことを防ぎます。

  2. func testMulticastAddrs(t *testing.T, ifmat []Addr) 内の変更:

    @@ -86,9 +90,13 @@ func testMulticastAddrs(t *T, ifmat []Addr) {
     
     func testMulticastAddrs(t *T, ifmat []Addr) {
     	for _, ifma := range ifmat {
    -		switch ifma.(type) {
    +		switch v := ifma.(type) {
     		case *IPAddr:
    -			t.Logf("	joined group address %q", ifma.String())
    +			if v == nil {
    +				t.Errorf("	unexpected value: %v", ifma)
    +			} else {
    +				t.Logf("	joined group address %q", ifma.String())
    +			}
     		default:
     			t.Errorf("	unexpected type: %T", ifma)
     		}
    

    testAddrs と同様に、マルチキャストアドレスのテストでも nil チェックが追加されています。これにより、マルチキャストアドレスの取得においても nil が誤って処理されることを防ぎます。

これらの変更により、NetBSD環境でのGoの net パッケージが、データリンク層のアドレスをIPアドレスとして誤って解釈し、nil のインターフェースアドレスを生成する問題を効果的に解決しています。

関連リンク

参考にした情報源リンク

  • Go言語の net パッケージに関する公式ドキュメント
  • NetBSDのルーティングソケットと sockaddr_dl に関するドキュメント (例: rt_msg(3) manページ)
  • Unix系OSにおけるネットワークプログラミングに関する一般的な情報源