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

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

このコミットは、Go言語の syscall パッケージにおけるルーティングソケットパーサーの更新に関するものです。特にNetBSD 6以降のカーネルにおける64ビットアライメント要件に対応するための変更が含まれています。

コミット

commit 9a7947288b55948eeb141ef7020cb542217e54c5
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Sun Aug 25 08:44:31 2013 +0900

    syscall: update routing socket parser for NetBSD 6 and beyond
    
    NetBSD 6 kernel and beyond require 64-bit aligned access to routing
    facilities.
    
    Fixes #6226.
    
    R=golang-dev, bsiegert, bradfitz
    CC=golang-dev
    https://golang.org/cl/13170043

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

https://github.com/golang/go/commit/9a7947288b55948eeb141ef7020cb542217e54c5

元コミット内容

このコミットは、NetBSD 6以降のカーネルがルーティング機能へのアクセスに64ビットアライメントを要求することに対応するため、syscallパッケージ内のルーティングソケットパーサーを更新します。これにより、Issue #6226で報告された問題が修正されます。

変更の背景

Go言語のnetおよびsyscallパッケージは、オペレーティングシステムのネットワーク機能と低レベルのシステムコールにアクセスするためのインターフェースを提供します。ルーティングソケットは、カーネルからルーティングテーブル情報やネットワークインターフェース情報を取得するために使用される特殊なソケットです。

NetBSDは、BSD系のオープンソースオペレーティングシステムの一つです。オペレーティングシステムのカーネルは、データ構造のメモリ配置に関して特定のアライメント(整列)要件を持つことがあります。これは、プロセッサが特定のメモリアドレスからデータを効率的に読み書きするために重要です。アライメントが正しくないと、パフォーマンスの低下や、最悪の場合、クラッシュなどの未定義の動作を引き起こす可能性があります。

このコミットの背景には、NetBSD 6カーネルがルーティング関連のデータ構造に対して、以前のバージョンよりも厳密な64ビットアライメントを要求するようになったという変更があります。Goのsyscallパッケージがこれらのデータ構造をパースする際に、この新しいアライメント要件を考慮していなかったため、NetBSD 6以降のシステムでルーティング情報を正しく取得できない問題(Issue #6226)が発生していました。

具体的には、ルーティングソケットから受信したメッセージ内のsockaddr構造体(ソケットアドレスを表す汎用構造体)のパースにおいて、Goのコードがカーネルの期待するアライメントに従っていなかったことが原因と考えられます。

前提知識の解説

1. ルーティングソケット (Routing Socket)

ルーティングソケットは、Unix系OSにおいてカーネルのルーティングテーブルやネットワークインターフェースの状態に関する情報を取得・設定するために使用される特殊なソケットです。アプリケーションは通常のソケットと同様にルーティングソケットを作成し、特定のメッセージ形式でカーネルと通信することで、ルーティングエントリの追加・削除、インターフェースのアップ・ダウン、IPアドレスの割り当てなどの操作を行うことができます。

2. sockaddr 構造体とアライメント

sockaddrは、ソケットアドレスを表現するための汎用的な構造体です。異なるプロトコルファミリー(IPv4, IPv6など)に対応するため、通常はsockaddr_in(IPv4用)、sockaddr_in6(IPv6用)などの具体的な構造体がsockaddrにキャストされて使用されます。

struct sockaddr {
    uint8_t    sa_len;      // structure length
    sa_family_t sa_family;  // address family
    char       sa_data[14]; // protocol-specific address information
};

メモリのアライメントとは、データがメモリ上で特定のバイト境界に配置されることを指します。例えば、64ビット(8バイト)アライメントを要求するシステムでは、64ビットのデータは8の倍数のアドレスに配置される必要があります。これは、CPUが一度に読み書きできるデータの単位(ワードサイズ)に密接に関連しており、アライメントされていないアクセスは、複数のメモリアクセスを必要としたり、ハードウェア例外を引き起こしたりする可能性があります。

3. AF_UNSPEC とアドレスファミリーの推測

AF_UNSPECは、アドレスファミリーが指定されていないことを示す値です。ルーティングソケットのメッセージでは、sockaddr構造体のアドレスファミリーフィールドがAF_UNSPECとして渡されることがありますが、これは実際のプロトコルファミリー(例: AF_INETAF_INET6)をメッセージの長さや他のコンテキストから推測する必要があることを意味します。

4. RTAX_IFARTAX_NETMASK

ルーティングソケットのメッセージには、様々な種類の情報が含まれます。RTAX_IFAはインターフェースアドレス(Interface Address)を示し、RTAX_NETMASKはネットワークマスク(Netmask)を示します。これらの値は、メッセージ内のどの部分が特定のアドレス情報に対応するかを識別するために使用されます。

5. sizeofPtr

sizeofPtrは、ポインタのサイズ(バイト単位)を表す定数です。32ビットシステムでは4バイト、64ビットシステムでは8バイトになります。これは、システムが32ビットか64ビットかを判断する際の一つの指標となります。

技術的詳細

このコミットの主要な変更点は、src/pkg/syscall/route_bsd.goファイルにおけるrsaAlignOf関数の修正と、InterfaceAddrMessage.sockaddrメソッドのロジック変更です。

rsaAlignOf関数の変更

rsaAlignOf関数は、sockaddr構造体の長さを適切にアライメントするために使用されます。以前は、64ビットDarwinカーネルが32ビットアライメントを要求するという特殊なケースのみを考慮していました。

変更前:

func rsaAlignOf(salen int) int {
	salign := sizeofPtr
	// NOTE: It seems like 64-bit Darwin kernel still requires 32-bit
	// aligned access to BSD subsystem.
	if darwinAMD64 {
		salign = 4
	}
	// ...
}

変更後:

func rsaAlignOf(salen int) int {
	salign := sizeofPtr
	// NOTE: It seems like 64-bit Darwin kernel still requires
	// 32-bit aligned access to BSD subsystem. Also NetBSD 6
	// kernel and beyond require 64-bit aligned access to routing
	// facilities.
	if darwin64Bit {
		salign = 4
	} else if netbsd32Bit { // 新規追加
		salign = 8 // NetBSD 6以降の64ビットアライメント要件に対応
	}
	// ...
}

この変更により、netbsd32Bitという新しい定数が導入され、NetBSD 6以降のカーネルがルーティング施設に対して64ビットアライメントを要求するという要件に対応するため、salignが8に設定されるようになりました。ここでnetbsd32Bitは、runtime.GOOS == "netbsd" && sizeofPtr == 4、つまり32ビットのNetBSDシステムを指します。これは、32ビットシステムであっても、ルーティングソケットのデータ構造自体は64ビットアライメントを要求する場合があるという、カーネル側の設計変更に対応するものです。

InterfaceAddrMessage.sockaddrメソッドの変更

このメソッドは、ルーティングソケットメッセージからSockaddrスライスを抽出する役割を担っています。変更の核心は、AF_UNSPEC(アドレスファミリー未指定)のsockaddr構造体を適切に処理するロジックの追加です。

変更前は、RTAX_NETMASKのアドレスファミリーがAF_UNSPECの場合、その長さに基づいてAF_INETまたはAF_INET6を推測していました。しかし、RTAX_IFAのアドレスファミリーがAF_UNSPECの場合の処理が不十分でした。

変更後:

func (m *InterfaceAddrMessage) sockaddr() (sas []Sockaddr) {
	// ...
	b := m.Data[:]
	// We still see AF_UNSPEC in socket addresses on some
	// platforms. To identify each address family correctly, we
	// will use the address family of RTAX_NETMASK as a preferred
	// one on the 32-bit NetBSD kernel, also use the length of
	// RTAX_NETMASK socket address on the FreeBSD kernel.
	preferredFamily := uint8(AF_UNSPEC) // 新規追加

	for i := uint(0); i < RTAX_MAX; i++ {
		// ...
		rsa := (*RawSockaddr)(unsafe.Pointer(&b[0]))
		switch i {
		case RTAX_IFA:
			if rsa.Family == AF_UNSPEC { // AF_UNSPECの場合の処理を追加
				rsa.Family = preferredFamily // preferredFamilyを使用
			}
			sa, err := anyToSockaddr((*RawSockaddrAny)(unsafe.Pointer(rsa)))
			// ...
		case RTAX_NETMASK:
			switch rsa.Family {
			case AF_UNSPEC:
				switch rsa.Len {
				case SizeofSockaddrInet4:
					rsa.Family = AF_INET
				case SizeofSockaddrInet6:
					rsa.Family = AF_INET6
				default:
					rsa.Family = AF_INET // an old fashion, AF_UNSPEC means AF_INET
				}
			case AF_INET, AF_INET6: // 新規追加
				preferredFamily = rsa.Family // preferredFamilyを更新
			default:
				return nil
			}
			sa, err := anyToSockaddr((*RawSockaddrAny)(unsafe.Pointer(rsa)))
			// ...
		}
		// ...
	}
	// ...
}

この変更のポイントは以下の通りです。

  1. preferredFamily変数の導入: これは、RTAX_NETMASKから得られたアドレスファミリーを記憶し、後続のRTAX_IFAAF_UNSPECであった場合にそのpreferredFamilyを使用することで、正しいアドレスファミリーを推測できるようにします。
  2. RTAX_NETMASK処理の強化: RTAX_NETMASKのアドレスファミリーがAF_INETまたはAF_INET6であった場合、そのファミリーをpreferredFamilyとして設定します。これにより、後続のRTAX_IFAAF_UNSPECであっても、適切なファミリーを割り当てることが可能になります。
  3. RTAX_IFA処理の改善: RTAX_IFAのアドレスファミリーがAF_UNSPECの場合に、preferredFamilyを適用することで、より正確なアドレスファミリーの解決を試みます。

これらの変更は、NetBSD 6以降のカーネルがルーティングソケットメッセージ内でAF_UNSPECを返す場合でも、Goのsyscallパッケージが正しくアドレス情報をパースできるようにするために不可欠です。

その他の変更

  • src/pkg/net/interface_test.go: IPAddrIPNetのテストにおいて、ifa.IPifa.Masknilでないかのチェックが追加されました。これは、ルーティングソケットパーサーの変更に伴う堅牢性の向上を目的としている可能性があります。
  • src/pkg/syscall/sockcmsg_unix.go: cmsgAlignOf関数内のdarwinAMD64darwin64Bitにリネームされました。これは、より一般的な64ビットDarwinシステムを指すようにするための変更です。
  • src/pkg/syscall/syscall_unix.go: darwinAMD64定数がdarwin64Bitにリネームされ、netbsd32Bitという新しい定数が追加されました。これらの定数は、特定のOSとアーキテクチャの組み合わせを識別するために使用されます。

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

src/pkg/syscall/route_bsd.go

--- a/src/pkg/syscall/route_bsd.go
+++ b/src/pkg/syscall/route_bsd.go
@@ -13,10 +13,14 @@ import "unsafe"
 // Round the length of a raw sockaddr up to align it properly.
 func rsaAlignOf(salen int) int {
 	salign := sizeofPtr
-	// NOTE: It seems like 64-bit Darwin kernel still requires 32-bit
-	// aligned access to BSD subsystem.
-	if darwinAMD64 {
+	// NOTE: It seems like 64-bit Darwin kernel still requires
+	// 32-bit aligned access to BSD subsystem. Also NetBSD 6
+	// kernel and beyond require 64-bit aligned access to routing
+	// facilities.
+	if darwin64Bit {
 		salign = 4
+	} else if netbsd32Bit {
+		salign = 8
 	}
 	if salen == 0 {
 		return salign
@@ -142,6 +146,12 @@ func (m *InterfaceAddrMessage) sockaddr() (sas []Sockaddr) {
 		return nil
 	}
 	b := m.Data[:]
+	// We still see AF_UNSPEC in socket addresses on some
+	// platforms. To identify each address family correctly, we
+	// will use the address family of RTAX_NETMASK as a preferred
+	// one on the 32-bit NetBSD kernel, also use the length of
+	// RTAX_NETMASK socket address on the FreeBSD kernel.
+	preferredFamily := uint8(AF_UNSPEC)
 	for i := uint(0); i < RTAX_MAX; i++ {
 		if m.Header.Addrs&rtaIfaMask&(1<<i) == 0 {
 			continue
@@ -149,21 +159,29 @@ func (m *InterfaceAddrMessage) sockaddr() (sas []Sockaddr) {
 		rsa := (*RawSockaddr)(unsafe.Pointer(&b[0]))
 		switch i {
 		case RTAX_IFA:
+			if rsa.Family == AF_UNSPEC {
+				rsa.Family = preferredFamily
+			}
 			sa, err := anyToSockaddr((*RawSockaddrAny)(unsafe.Pointer(rsa)))
 			if err != nil {
 				return nil
 			}
 			sas = append(sas, sa)
 		case RTAX_NETMASK:
-			if rsa.Family == AF_UNSPEC {
+			switch rsa.Family {
+			case AF_UNSPEC:
 				switch rsa.Len {
 				case SizeofSockaddrInet4:
 					rsa.Family = AF_INET
 				case SizeofSockaddrInet6:
 					rsa.Family = AF_INET6
 				default:
-					rsa.Family = AF_INET // an old fasion, AF_UNSPEC means AF_INET
+					rsa.Family = AF_INET // an old fashion, AF_UNSPEC means AF_INET
 				}
+			case AF_INET, AF_INET6:
+				preferredFamily = rsa.Family
+			default:
+				return nil
 			}
 			sa, err := anyToSockaddr((*RawSockaddrAny)(unsafe.Pointer(rsa)))
 			if err != nil {

src/pkg/syscall/syscall_unix.go

--- a/src/pkg/syscall/syscall_unix.go
+++ b/src/pkg/syscall/syscall_unix.go
@@ -18,7 +18,10 @@ var (
 	Stderr = 2
 )
 
-const darwinAMD64 = runtime.GOOS == "darwin" && runtime.GOARCH == "amd64"
+const (
+	darwin64Bit = runtime.GOOS == "darwin" && sizeofPtr == 8
+	netbsd32Bit = runtime.GOOS == "netbsd" && sizeofPtr == 4
+)
 
 func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
 func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)

コアとなるコードの解説

rsaAlignOf関数

この関数は、sockaddr構造体のメモリ上のアライメントを計算します。salensockaddrの長さです。

  • salignはデフォルトでポインタのサイズ(sizeofPtr)に設定されます。これは、システムが32ビットなら4、64ビットなら8です。
  • darwin64Bit(64ビットDarwinシステム)の場合、salignは4に設定されます。これは、64ビットDarwinカーネルがBSDサブシステムへのアクセスに32ビットアライメントを要求するという特殊なケースに対応するためです。
  • 新規追加されたnetbsd32Bitの場合、salignは8に設定されます。 これは、32ビットNetBSDシステムであっても、NetBSD 6以降のカーネルがルーティング機能に対して64ビットアライメントを要求するという要件に対応するためです。この変更が、NetBSDにおけるルーティングソケットのパース問題を解決する鍵となります。

InterfaceAddrMessage.sockaddrメソッド

このメソッドは、ルーティングソケットメッセージからネットワークアドレス情報を抽出します。

  • preferredFamilyという新しい変数が導入され、初期値はAF_UNSPECです。
  • ループ内で、メッセージ内の各アドレス情報(RTAX_IFA, RTAX_NETMASKなど)を処理します。
  • RTAX_IFAの処理:
    • もしrsa.Family(現在のsockaddrのアドレスファミリー)がAF_UNSPECであれば、preferredFamilyの値をrsa.Familyに割り当てます。これにより、RTAX_NETMASKから得られた情報に基づいて、RTAX_IFAの正しいアドレスファミリーを推測できます。
  • RTAX_NETMASKの処理:
    • rsa.FamilyAF_UNSPECの場合、以前と同様にrsa.Lensockaddrの長さ)に基づいてAF_INETまたはAF_INET6を推測します。
    • rsa.FamilyAF_INETまたはAF_INET6の場合、その値をpreferredFamilyに設定します。 これが重要な変更で、RTAX_NETMASKが明確なアドレスファミリーを持っている場合、そのファミリーを後続のRTAX_IFAの推測に利用できるようにします。

これらの変更により、Goのsyscallパッケージは、NetBSD 6以降のカーネルがルーティングソケットメッセージ内でAF_UNSPECを返す場合でも、より堅牢にアドレス情報をパースできるようになります。

関連リンク

参考にした情報源リンク

  • NetBSD Documentation (Routing Sockets): https://www.netbsd.org/docs/kernel/routing.html (一般的なルーティングソケットの概念理解のため)
  • Unix Network Programming, Volume 1, Third Edition: The Sockets Networking API (W. Richard Stevens, Bill Fenner, Andrew M. Rudoff) - sockaddr構造体やルーティングソケットに関する詳細な情報源。
  • Go言語のsyscallパッケージのドキュメント: https://pkg.go.dev/syscall (Goのシステムコールインターフェースの理解のため)
  • Go言語のnetパッケージのドキュメント: https://pkg.go.dev/net (Goのネットワークインターフェースの理解のため)
  • メモリのアライメントに関する一般的な情報源 (例: Wikipedia, 各種OSのドキュメント)