[インデックス 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_INET
、AF_INET6
)をメッセージの長さや他のコンテキストから推測する必要があることを意味します。
4. RTAX_IFA
と RTAX_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)))
// ...
}
// ...
}
// ...
}
この変更のポイントは以下の通りです。
preferredFamily
変数の導入: これは、RTAX_NETMASK
から得られたアドレスファミリーを記憶し、後続のRTAX_IFA
がAF_UNSPEC
であった場合にそのpreferredFamily
を使用することで、正しいアドレスファミリーを推測できるようにします。RTAX_NETMASK
処理の強化:RTAX_NETMASK
のアドレスファミリーがAF_INET
またはAF_INET6
であった場合、そのファミリーをpreferredFamily
として設定します。これにより、後続のRTAX_IFA
がAF_UNSPEC
であっても、適切なファミリーを割り当てることが可能になります。RTAX_IFA
処理の改善:RTAX_IFA
のアドレスファミリーがAF_UNSPEC
の場合に、preferredFamily
を適用することで、より正確なアドレスファミリーの解決を試みます。
これらの変更は、NetBSD 6以降のカーネルがルーティングソケットメッセージ内でAF_UNSPEC
を返す場合でも、Goのsyscall
パッケージが正しくアドレス情報をパースできるようにするために不可欠です。
その他の変更
src/pkg/net/interface_test.go
:IPAddr
とIPNet
のテストにおいて、ifa.IP
やifa.Mask
がnil
でないかのチェックが追加されました。これは、ルーティングソケットパーサーの変更に伴う堅牢性の向上を目的としている可能性があります。src/pkg/syscall/sockcmsg_unix.go
:cmsgAlignOf
関数内のdarwinAMD64
がdarwin64Bit
にリネームされました。これは、より一般的な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
構造体のメモリ上のアライメントを計算します。salen
はsockaddr
の長さです。
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.Family
がAF_UNSPEC
の場合、以前と同様にrsa.Len
(sockaddr
の長さ)に基づいてAF_INET
またはAF_INET6
を推測します。rsa.Family
がAF_INET
またはAF_INET6
の場合、その値をpreferredFamily
に設定します。 これが重要な変更で、RTAX_NETMASK
が明確なアドレスファミリーを持っている場合、そのファミリーを後続のRTAX_IFA
の推測に利用できるようにします。
これらの変更により、Goのsyscall
パッケージは、NetBSD 6以降のカーネルがルーティングソケットメッセージ内でAF_UNSPEC
を返す場合でも、より堅牢にアドレス情報をパースできるようになります。
関連リンク
- Go Issue #6226: https://github.com/golang/go/issues/6226
- Go CL 13170043: https://golang.org/cl/13170043
参考にした情報源リンク
- 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のドキュメント)