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

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

このコミットは、Go言語のsyscallパッケージにおけるFreeBSD関連のルーティングメッセージ処理を更新し、FreeBSD 10以降のバージョンに対応するための変更を導入しています。具体的には、src/pkg/syscall/route_freebsd.gosrc/pkg/syscall/route_freebsd_32bit.gosrc/pkg/syscall/route_freebsd_64bit.goの3つのファイルが変更されています。

コミット

FreeBSD 10におけるInterfaceMessageの構造変更に対応するため、syscallパッケージを修正しました。これにより、FreeBSD 10およびそれ以前のバージョンでsyscall.InterfaceMessageが正しく機能するようになります。

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

https://github.com/golang/go/commit/25668b9b349cf076f3ac07c02556a77d169f897b

元コミット内容

commit 25668b9b349cf076f3ac07c02556a77d169f897b
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Tue Mar 4 09:26:56 2014 +0900

    syscall: add support for FreeBSD 10
    
    This CL tweaks syscall.InterfaceMessage to support FreeBSD 10 and
    prior to 10.
    
    See http://svnweb.freebsd.org/base?view=revision&revision=254804.
    
    Fixes #7193.
    
    LGTM=iant
    R=golang-codereviews, rsc, minux.ma, gobot, iant
    CC=golang-codereviews
    https://golang.org/cl/56980043

変更の背景

この変更の背景には、FreeBSD 10におけるネットワーク関連の内部データ構造、特にmbuf(memory buffer)の再構築があります。FreeBSDのネットワークスタックは、パケットやソケットバッファを効率的に管理するためにmbufを使用しています。FreeBSD 10では、このmbufのパケットヘッダビューが変更され、これまでのバージョンとは異なる構造になりました。

具体的には、FreeBSDのSVNリビジョン254804(2013年8月22日)を含む一連の変更がmbuf構造に適用されました。これにより、if_msghdr構造体(ネットワークインターフェースメッセージのヘッダ)の内部レイアウトが変更され、特にif_dataフィールド内のHwassistEpochLastchangeといったフィールドのオフセットやサイズが影響を受けました。

Go言語のsyscallパッケージは、オペレーティングシステムの低レベルな機能にアクセスするためのインターフェースを提供しており、FreeBSDのルーティングメッセージ(RTM_IFINFOなど)を処理する際に、これらのカーネル内部構造を直接利用していました。FreeBSD 10でのmbuf構造の変更により、Goのsyscall.InterfaceMessageがFreeBSD 10で正しくパースできなくなる問題が発生しました(Go issue #7193)。

このコミットは、FreeBSDのバージョンを検出し、バージョン10以降であれば新しいmbuf構造に対応したパースロジックを適用することで、この互換性の問題を解決することを目的としています。

前提知識の解説

Go言語のsyscallパッケージ

syscallパッケージは、Goプログラムからオペレーティングシステムの低レベルなプリミティブ(システムコール)にアクセスするための機能を提供します。これにより、プロセス管理、ファイル操作、ネットワーク通信などを非常に基本的なレベルで制御できます。しかし、このパッケージはGo 1.4以降「ロックダウン」されており、新しいコードではgolang.org/x/sysgolang.org/x/netといった外部パッケージの使用が推奨されています。

syscall.InterfaceMessage

syscall.InterfaceMessageは、syscallパッケージ内で定義されているGoの構造体で、ネットワークインターフェースのエントリを含むルーティングメッセージを表します。これは通常、ルーティングに関連する低レベルなネットワーク操作の一部として使用されます。Headerフィールド(IfMsghdr型)にはメッセージのメタデータが含まれ、Dataフィールド([]byte型)にはネットワークインターフェースに関する実際のデータが格納されます。

mbuf (memory buffer)

mbufは、FreeBSDを含む多くのUnix系OSのネットワークスタックにおいて、ネットワークパケットやソケットバッファを効率的に管理するためのメモリバッファ構造です。ネットワークデータを受信・送信する際に、カーネル内でmbufが割り当てられ、パケットデータが格納されます。mbufの構造は、パフォーマンスやメモリ効率に直結するため、OSのバージョンアップに伴って変更されることがあります。

sysctlkern.osreldate

sysctlは、FreeBSD(および他のUnix系OS)において、カーネルパラメータをランタイムで検査・変更するためのメカニズムです。これらのパラメータは、MIB(Management Information Base)形式の名前階層で整理されています。

kern.osreldateは、FreeBSDのsysctl変数の一つで、オペレーティングシステムのリリース日を表す読み取り専用の整数値です。この値は通常、<osreldate.h>ヘッダファイルで定義されている__FreeBSD_versionマクロに対応しており、システムがビルドされたユーザーランドのバージョンを示します。この値を利用することで、Goプログラムは実行中のFreeBSDのバージョンを判別し、それに応じた処理を行うことができます。FreeBSD 10のkern.osreldateの値は1000000以上となります。

技術的詳細

このコミットの主要な技術的アプローチは、FreeBSDのバージョンを動的に検出し、それに基づいてInterfaceMessageのパースロジックを切り替えることです。

  1. FreeBSDバージョンの検出: src/pkg/syscall/route_freebsd.gofreebsdVersionというグローバル変数が追加され、init()関数内でSysctlUint32("kern.osreldate")を呼び出すことで、システムのkern.osreldateの値を取得します。この値は、FreeBSDのバージョンを数値で表したもので、FreeBSD 10以降では1000000以上となります。

  2. InterfaceMessageのパースロジックの分離: 既存のanyMessage.toRoutingMessage関数内でRTM_IFINFOメッセージを処理する部分が変更されました。以前は直接InterfaceMessageにキャストしていましたが、新しいany.parseInterfaceMessage(b)メソッドを呼び出すように変更されました。

  3. アーキテクチャごとのパースロジック: parseInterfaceMessageメソッドの実装は、アーキテクチャ(32bitと64bit)ごとに異なるファイルに分離されました。

    • src/pkg/syscall/route_freebsd_32bit.go: freebsd,386およびfreebsd,armビルドタグを持つファイルで、32bitアーキテクチャ向けです。
    • src/pkg/syscall/route_freebsd_64bit.go: freebsd,amd64ビルドタグを持つファイルで、64bitアーキテクチャ向けです。
  4. FreeBSD 10対応の条件分岐: src/pkg/syscall/route_freebsd_32bit.go内のparseInterfaceMessage関数では、freebsdVersion >= 1000000という条件分岐が追加されています。

    • FreeBSD 10以降の場合: ifMsghdr構造体(FreeBSD 10で変更されたmbufヘッダビューに対応する)にキャストし、Hwassist, Epoch, Lastchangeといったフィールドを新しい構造からInterfaceMessage.Header.Dataにコピーします。これにより、FreeBSD 10の新しいmbuf構造に対応したデータパースが可能になります。
    • FreeBSD 10より前の場合: 従来のInterfaceMessage構造体に直接キャストし、既存のパースロジックを維持します。

    一方、src/pkg/syscall/route_freebsd_64bit.goでは、parseInterfaceMessage関数内にバージョンチェックの条件分岐がありません。これは、64bitアーキテクチャではifMsghdrの変更がInterfaceMessageのパースに影響を与えないか、あるいは別の方法で対応されているためと考えられます。コミットメッセージや変更内容からは、32bit環境でのみmbufの構造変更が直接的な問題を引き起こしていた可能性が示唆されます。

このアプローチにより、GoプログラムはFreeBSDの異なるバージョン間で互換性を保ちながら、ネットワークインターフェース情報を正確に処理できるようになります。

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

src/pkg/syscall/route_freebsd.go

--- a/src/pkg/syscall/route_freebsd.go
+++ b/src/pkg/syscall/route_freebsd.go
@@ -8,14 +8,20 @@ package syscall
 
 import "unsafe"
 
+// See http://www.freebsd.org/doc/en/books/porters-handbook/freebsd-versions.html.
+var freebsdVersion uint32
+
+func init() {
+	freebsdVersion, _ = SysctlUint32("kern.osreldate")
+}
+
 func (any *anyMessage) toRoutingMessage(b []byte) RoutingMessage {
 	switch any.Type {
 	case RTM_ADD, RTM_DELETE, RTM_CHANGE, RTM_GET, RTM_LOSING, RTM_REDIRECT, RTM_MISS, RTM_LOCK, RTM_RESOLVE:
 		p := (*RouteMessage)(unsafe.Pointer(any))
 		return &RouteMessage{Header: p.Header, Data: b[SizeofRtMsghdr:any.Msglen]}
 	case RTM_IFINFO:
-\t\tp := (*InterfaceMessage)(unsafe.Pointer(any))\n-\t\treturn &InterfaceMessage{Header: p.Header, Data: b[SizeofIfMsghdr:any.Msglen]}\n+\t\treturn any.parseInterfaceMessage(b)
 	case RTM_IFANNOUNCE:
 		p := (*InterfaceAnnounceMessage)(unsafe.Pointer(any))
 		return &InterfaceAnnounceMessage{Header: p.Header}

src/pkg/syscall/route_freebsd_32bit.go (新規ファイル)

// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build freebsd,386 freebsd,arm

package syscall

import "unsafe"

func (any *anyMessage) parseInterfaceMessage(b []byte) *InterfaceMessage {
	p := (*InterfaceMessage)(unsafe.Pointer(any))
	// FreeBSD 10 and beyond have a restructured mbuf
	// packet header view.
	// See http://svnweb.freebsd.org/base?view=revision&revision=254804.
	if freebsdVersion >= 1000000 {
		m := (*ifMsghdr)(unsafe.Pointer(any))
		p.Header.Data.Hwassist = uint32(m.Data.Hwassist)
		p.Header.Data.Epoch = m.Data.Epoch
		p.Header.Data.Lastchange = m.Data.Lastchange
		return &InterfaceMessage{Header: p.Header, Data: b[sizeofIfMsghdr:any.Msglen]}
	}
	return &InterfaceMessage{Header: p.Header, Data: b[SizeofIfMsghdr:any.Msglen]}
}

src/pkg/syscall/route_freebsd_64bit.go (新規ファイル)

// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build freebsd,amd64

package syscall

import "unsafe"

func (any *anyMessage) parseInterfaceMessage(b []byte) *InterfaceMessage {
	p := (*InterfaceMessage)(unsafe.Pointer(any))
	return &InterfaceMessage{Header: p.Header, Data: b[SizeofIfMsghdr:any.Msglen]}
}

コアとなるコードの解説

src/pkg/syscall/route_freebsd.goの変更

  • freebsdVersion変数の追加とinit()関数での初期化: var freebsdVersion uint32でFreeBSDのバージョンを格納する変数を宣言し、init()関数内でSysctlUint32("kern.osreldate")を呼び出して、システムのkern.osreldateの値をfreebsdVersionに設定しています。これにより、プログラムの起動時にFreeBSDのバージョンが判別されます。
  • RTM_IFINFO処理の委譲: anyMessage.toRoutingMessage関数内のRTM_IFINFOケースで、以前は直接InterfaceMessageにキャストしていましたが、any.parseInterfaceMessage(b)という新しいメソッドに処理を委譲するように変更されました。これにより、バージョンごとのパースロジックを外部に切り出すことが可能になりました。

src/pkg/syscall/route_freebsd_32bit.goの新規追加と解説

このファイルは、32bitアーキテクチャ(freebsd,386およびfreebsd,arm)向けにコンパイルされます。

  • parseInterfaceMessage関数: この関数は、anyMessage(ルーティングメッセージの汎用ヘッダ)と生バイトスライスbを受け取り、InterfaceMessage構造体を返します。
    1. p := (*InterfaceMessage)(unsafe.Pointer(any)): まず、汎用ヘッダanyInterfaceMessage型にunsafe.Pointerを使ってキャストします。これは、メッセージの共通部分を初期化するためです。
    2. if freebsdVersion >= 1000000: ここがFreeBSD 10以降のバージョンに対応するための重要な条件分岐です。kern.osreldateの値が1000000以上であれば、FreeBSD 10以降と判断します。
    3. FreeBSD 10以降の場合の処理:
      • m := (*ifMsghdr)(unsafe.Pointer(any)): FreeBSD 10で変更されたmbufヘッダビューに対応するifMsghdr構造体にanyをキャストします。
      • p.Header.Data.Hwassist = uint32(m.Data.Hwassist)
      • p.Header.Data.Epoch = m.Data.Epoch
      • p.Header.Data.Lastchange = m.Data.Lastchange: ifMsghdrから、HwassistEpochLastchangeといったフィールドの値をInterfaceMessageHeader.Dataにコピーします。これは、FreeBSD 10でこれらのフィールドのオフセットやレイアウトが変更されたため、新しい構造から正しい値を取得し直す必要があるためです。
      • return &InterfaceMessage{Header: p.Header, Data: b[sizeofIfMsghdr:any.Msglen]}: 新しいifMsghdrのサイズ(sizeofIfMsghdr)を考慮して、データ部分を切り出してInterfaceMessageを構築し返します。
    4. FreeBSD 10より前の場合の処理:
      • return &InterfaceMessage{Header: p.Header, Data: b[SizeofIfMsghdr:any.Msglen]}: 従来のInterfaceMessageのサイズ(SizeofIfMsghdr)を考慮してデータ部分を切り出し、そのままInterfaceMessageを構築して返します。

src/pkg/syscall/route_freebsd_64bit.goの新規追加と解説

このファイルは、64bitアーキテクチャ(freebsd,amd64)向けにコンパイルされます。

  • parseInterfaceMessage関数: この関数は、32bit版と同様にanyMessageと生バイトスライスbを受け取り、InterfaceMessage構造体を返します。
    • p := (*InterfaceMessage)(unsafe.Pointer(any)): 汎用ヘッダanyInterfaceMessage型にキャストします。
    • return &InterfaceMessage{Header: p.Header, Data: b[SizeofIfMsghdr:any.Msglen]}: 64bit環境では、FreeBSD 10のmbuf構造変更がInterfaceMessageのパースに直接的な影響を与えないためか、バージョンチェックの条件分岐なしで、従来のInterfaceMessageのサイズを考慮してデータ部分を切り出し、InterfaceMessageを構築して返しています。これは、32bitと64bitで構造体のアライメントやパディングが異なるため、影響範囲が異なることを示唆しています。

これらの変更により、GoのsyscallパッケージはFreeBSDのバージョンに依存せず、InterfaceMessageを正しく解釈できるようになり、FreeBSD 10以降のシステムでもネットワーク関連の低レベルな操作が適切に行えるようになりました。

関連リンク

参考にした情報源リンク