[インデックス 15318] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnet
パッケージにおいて、IPv6のスコープ付きアドレス(Scoped Addressing)のゾーン情報を、IPNet
およびIPAddr
構造体に設定する変更を導入しています。既存のAPIの振る舞いには影響を与えず、可能であればIPアドレス構造体にゾーン情報を追加することが目的です。また、コードの小さな簡素化も行われています。
コミット
commit 40c2fbf4f2d56bc180a579d63dcfaf537984d9e5
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Wed Feb 20 08:18:04 2013 +0900
net: set up IPv6 scoped addressing zone for network facilities
This CL changes nothing to existing API behavior, just sets up
Zone in IPNet and IPAddr structures if possible.
Also does small simplification.
Update #4234.
R=rsc, dave
CC=golang-dev
https://golang.org/cl/7300081
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/40c2fbf4f2d56bc180a579d63dcfaf537984d9e5
元コミット内容
net: set up IPv6 scoped addressing zone for network facilities
この変更は既存のAPIの振る舞いには何も変更を加えません。可能であれば、IPNet
およびIPAddr
構造体にゾーンを設定するだけです。
また、小さな簡素化も行っています。
Issue #4234 を更新します。
変更の背景
IPv6アドレスには、そのアドレスが有効な範囲を示す「スコープ」という概念があります。特に、リンクローカルアドレスやサイトローカルアドレスなど、特定のネットワークセグメント内でのみ有効なアドレスが存在します。これらのアドレスは、同じアドレス値であっても、異なるインターフェース(ネットワークアダプタ)に割り当てられた場合、異なる意味を持つことがあります。このため、IPv6のスコープ付きアドレスを正確に識別し、ルーティングや通信を適切に行うためには、アドレスだけでなく、それが属する「ゾーン」(通常はネットワークインターフェースのインデックスや名前)の情報が必要となります。
このコミットが行われた背景には、Go言語のnet
パッケージが、オペレーティングシステム(OS)から取得したネットワークインターフェースやアドレス情報をより正確に表現する必要があったことが挙げられます。特に、KAMEプロジェクト(FreeBSDなどで採用されているIPv6プロトコルスタックの実装)に由来するシステムでは、インターフェースローカルアドレスやリンクローカルアドレスにインターフェースインデックスが埋め込まれていることがあり、これをGoのnet
パッケージのデータ構造に適切に反映させることが求められました。
この変更により、IPNet
やIPAddr
といったGoのネットワークアドレスを表す構造体が、IPv6のスコープ付きアドレスのゾーン情報を保持できるようになり、より堅牢で正確なネットワークプログラミングが可能になります。既存のAPIの振る舞いを変更しないという点は、後方互換性を維持しつつ機能強化を行うというGo言語の設計思想に沿ったものです。
前提知識の解説
IPv6 スコープ付きアドレス (Scoped Addressing) とゾーン (Zone ID)
IPv6アドレスは、そのアドレスが有効な範囲(スコープ)によって分類されます。主なスコープには以下のものがあります。
- ユニキャストアドレス:
- グローバルユニキャストアドレス: インターネット全体で一意。
- リンクローカルユニキャストアドレス (Link-Local Unicast Address):
fe80::/10
の範囲。単一のリンク(ネットワークセグメント)内でのみ有効。ルーターを越えて転送されることはありません。主に近隣探索プロトコル (NDP) や自動設定 (Stateless Address Autoconfiguration, SLAAC) に使用されます。 - ユニークローカルユニキャストアドレス (Unique Local Unicast Address):
fc00::/7
の範囲。プライベートネットワーク内で使用され、グローバルアドレスとは衝突しないように設計されています。
- マルチキャストアドレス:
- インターフェースローカルマルチキャストアドレス (Interface-Local Multicast Address):
ff01::/16
の範囲。単一のインターフェース上でのみ有効。 - リンクローカルマルチキャストアドレス (Link-Local Multicast Address):
ff02::/16
の範囲。単一のリンク内でのみ有効。 - サイトローカル、組織ローカル、グローバルなど、他のスコープも存在します。
- インターフェースローカルマルチキャストアドレス (Interface-Local Multicast Address):
リンクローカルアドレスやインターフェースローカルマルチキャストアドレスのように、スコープが狭いアドレスは、複数のインターフェースで同じアドレス値を持つ可能性があります。例えば、fe80::1
というリンクローカルアドレスが、eth0
とeth1
の両方のインターフェースに存在し得ます。この場合、単にfe80::1
と指定するだけでは、どちらのインターフェースを指しているのかOSは判断できません。
ここで必要となるのが「ゾーンID (Zone ID)」です。ゾーンIDは、スコープ付きアドレスが属するネットワークインターフェースを識別するための情報です。通常、インターフェースの名前(例: eth0
, en0
)やシステム内部のインデックス番号がゾーンIDとして使用されます。
例えば、fe80::1%eth0
のように、アドレスの末尾に %
とゾーンIDを付加することで、特定のアドレスがどのインターフェースに属するかを明示的に指定できます。これにより、OSは正しいインターフェースを介して通信を確立できます。
Go言語のnet
パッケージ
Go言語のnet
パッケージは、ネットワークI/Oのプリミティブ機能を提供します。IPアドレス、TCP/UDP接続、DNSルックアップなど、様々なネットワーク関連の機能が含まれています。
IP
型: IPアドレスを表すバイトスライス。IPv4とIPv6の両方に対応。IPNet
構造体: IPアドレスとネットワークマスクのペアを表す。IPAddr
構造体: IPアドレスと、オプションでゾーンIDを表すZone
フィールドを持つ。
このコミット以前は、IPNet
やIPAddr
構造体はIPv6のゾーン情報を直接保持するフィールドを持っていませんでした。OSから取得したゾーン情報は、内部的に処理されるか、破棄されるかのいずれかでした。
syscall
パッケージ
Go言語のsyscall
パッケージは、低レベルのOSプリミティブへのアクセスを提供します。ネットワークインターフェースの情報やアドレス情報をOSから取得する際には、このパッケージを通じてシステムコールを呼び出す必要があります。
特に、BSD系のOS(macOS, FreeBSDなど)ではroute
ソケットを介してルーティングメッセージをパースし、Linuxではnetlink
ソケットを介してネットワーク情報を取得します。これらのシステムコールから返されるデータには、IPアドレスだけでなく、関連するインターフェースのインデックスや、IPv6アドレスに埋め込まれたスコープ情報が含まれることがあります。
技術的詳細
このコミットの主要な技術的変更点は、Goのnet
パッケージがOSから取得したIPv6アドレス情報から、そのアドレスが属するゾーン(通常はインターフェースのインデックスまたは名前)を抽出し、IPNet
およびIPAddr
構造体のZone
フィールドに設定するようになったことです。
具体的には、以下の処理が追加・変更されています。
-
IPv6リンクローカルアドレスのゾーンID抽出:
- BSD系のOS(
interface_bsd.go
,interface_darwin.go
,interface_freebsd.go
)において、syscall.SockaddrInet6
から取得したIPv6アドレスがリンクローカルユニキャストアドレス (IsLinkLocalUnicast()
) またはインターフェース/リンクローカルマルチキャストアドレス (IsInterfaceLocalMulticast()
,IsLinkLocalMulticast()
) である場合、そのアドレスに埋め込まれたインターフェースインデックスを抽出し、ifa.Zone
(IPNet
の場合)またはifma.Zone
(IPAddr
の場合)に設定します。 - KAMEベースのIPv6プロトコルスタックでは、リンクローカルアドレスの特定のバイト(通常は3番目と4番目のバイト)にインターフェースインデックスが埋め込まれる慣習があります。このコミットでは、
int(ifa.IP[2]<<8 | ifa.IP[3])
のようにして、この埋め込まれたインデックスを抽出しています。 - 抽出後、埋め込まれたインデックスはアドレスからクリアされます(
ifa.IP[2], ifa.IP[3] = 0, 0
)。これは、アドレス値自体はスコープに依存しない形式で保持し、ゾーン情報は別途Zone
フィールドで管理するためです。
- BSD系のOS(
-
LinuxにおけるゾーンID設定:
interface_linux.go
では、netlink
メッセージからIPv6アドレス情報を取得する際に、syscall.IfAddrmsg
構造体のScope
フィールドとIndex
フィールドを利用してゾーン情報を設定します。ifam.Scope == syscall.RT_SCOPE_HOST || ifam.Scope == syscall.RT_SCOPE_LINK
の条件で、ホストスコープまたはリンクスコープのアドレスに対して、インターフェースインデックスをゾーンIDとして設定します(ifa.Zone = zoneToString(int(ifam.Index))
)。- また、
/proc/net/igmp6
からマルチキャストアドレスをパースする際にも、インターフェース名がゾーンIDとして設定されるようになりました(ifma.Zone = ifi.Name
)。
-
コードの簡素化:
- 複数のファイルで、
switch v := m.(type)
のようなパターンをswitch m := m.(type)
に変更しています。これは、v
という一時変数を使わずに直接m
を型アサーション後の型として使用することで、コードの可読性をわずかに向上させるものです。 - Linux関連のコード(
interface_linux.go
)では、goto done
とdone:
ラベルを使用していたループ処理を、break loop
とラベル付きループに変更しています。これは、goto
の使用を避け、より構造化された制御フローにすることで、コードの保守性を高める一般的なプラクティスです。
- 複数のファイルで、
これらの変更により、Goのnet
パッケージは、OSから取得したIPv6アドレスのスコープ情報をより正確に表現できるようになり、アプリケーションがIPv6のスコープ付きアドレスを扱う際の堅牢性が向上しました。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、主にsrc/pkg/net/interface_bsd.go
、src/pkg/net/interface_darwin.go
、src/pkg/net/interface_freebsd.go
、src/pkg/net/interface_linux.go
のnewAddr
関数や関連するアドレス取得ロジックに見られます。
src/pkg/net/interface_bsd.go
(および interface_darwin.go
, interface_freebsd.go
の類似箇所)
--- a/src/pkg/net/interface_bsd.go
+++ b/src/pkg/net/interface_bsd.go
@@ -132,30 +127,29 @@ func newAddr(m *syscall.InterfaceAddrMessage) (Addr, error) {
if err != nil {
return nil, os.NewSyscallError("route sockaddr", err)
}
-
ifa := &IPNet{}
- for i, s := range sas {
- switch v := s.(type) {
+ for i, sa := range sas {
+ switch sa := sa.(type) {
case *syscall.SockaddrInet4:
switch i {
case 0:
- ifa.Mask = IPv4Mask(v.Addr[0], v.Addr[1], v.Addr[2], v.Addr[3])
+ ifa.Mask = IPv4Mask(sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3])
case 1:
- ifa.IP = IPv4(v.Addr[0], v.Addr[1], v.Addr[2], v.Addr[3])
+ ifa.IP = IPv4(sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3])
}
case *syscall.SockaddrInet6:
switch i {
case 0:
ifa.Mask = make(IPMask, IPv6len)
- copy(ifa.Mask, v.Addr[:])
+ copy(ifa.Mask, sa.Addr[:])
case 1:
ifa.IP = make(IP, IPv6len)
- copy(ifa.IP, v.Addr[:])
+ copy(ifa.IP, sa.Addr[:])
// NOTE: KAME based IPv6 protcol stack usually embeds
// the interface index in the interface-local or link-
// local address as the kernel-internal form.
if ifa.IP.IsLinkLocalUnicast() {
- // remove embedded scope zone ID
+ ifa.Zone = zoneToString(int(ifa.IP[2]<<8 | ifa.IP[3]))
ifa.IP[2], ifa.IP[3] = 0, 0
}
}
src/pkg/net/interface_linux.go
--- a/src/pkg/net/interface_linux.go
+++ b/src/pkg/net/interface_linux.go
@@ -149,6 +145,9 @@ func newAddr(attrs []syscall.NetlinkRouteAttr, ifi *Interface, ifam *syscall.IfA
\t\t\t\tifa.IP = make(IP, IPv6len)\n \t\t\t\tcopy(ifa.IP, a.Value[:])\n \t\t\t\tifa.Mask = CIDRMask(int(ifam.Prefixlen), 8*IPv6len)\n+\t\t\t\tif ifam.Scope == syscall.RT_SCOPE_HOST || ifam.Scope == syscall.RT_SCOPE_LINK {\n+\t\t\t\t\tifa.Zone = zoneToString(int(ifam.Index))\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n \t}\
--- a/src/pkg/net/interface_linux.go
+++ b/src/pkg/net/interface_linux.go
@@ -231,6 +228,9 @@ func parseProcNetIGMP6(path string, ifi *Interface) []Addr {
\t\t\t\tb[i/2], _ = xtoi2(f[2][i:i+2], 0)\n \t\t\t}\n \t\t\tifma := IPAddr{IP: IP{b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]}}\n+\t\t\tif ifma.IP.IsInterfaceLocalMulticast() || ifma.IP.IsLinkLocalMulticast() {\n+\t\t\t\tifma.Zone = ifi.Name\n+\t\t\t}\n \t\t\tifmat = append(ifmat, ifma.toAddr())\n \t\t}\n \t}\
コアとなるコードの解説
BSD系OS (interface_bsd.go
など) の変更点
newAddr
関数は、OSから取得したインターフェースアドレスメッセージをGoのnet
パッケージのAddr
型(実体はIPNet
)に変換する役割を担っています。
変更前は、IPv6のリンクローカルアドレスからゾーンIDを抽出するロジックが存在せず、コメントアウトされた// remove embedded scope zone ID
という行があるだけでした。
変更後、if ifa.IP.IsLinkLocalUnicast() { ... }
のブロックが追加されました。
ifa.IP.IsLinkLocalUnicast()
: 取得したIPアドレスがIPv6のリンクローカルユニキャストアドレスであるかを判定します。ifa.Zone = zoneToString(int(ifa.IP[2]<<8 | ifa.IP[3]))
: KAMEベースのシステムでは、リンクローカルアドレスの3番目と4番目のバイト(0-indexed)にインターフェースインデックスが埋め込まれていることがあります。このコードは、それらのバイトを結合して整数値(インターフェースインデックス)を抽出し、zoneToString
関数(おそらくインターフェースインデックスを文字列に変換するヘルパー関数)を使ってIPNet
構造体のZone
フィールドに設定しています。ifa.IP[2], ifa.IP[3] = 0, 0
: ゾーンIDを抽出した後、アドレス値から埋め込まれたインデックス情報をクリアしています。これにより、IP
フィールドは純粋なアドレス値のみを保持し、ゾーン情報はZone
フィールドで独立して管理されるようになります。
また、switch v := s.(type)
のようなパターンが switch sa := sa.(type)
に変更されています。これは、型アサーションの結果を新しい変数sa
に直接代入することで、コードの冗長性を減らし、可読性を向上させるための小さな簡素化です。
Linux (interface_linux.go
) の変更点
newAddr
関数(Linux版)は、netlink
メッセージから取得したアドレス属性をGoのnet
パッケージのAddr
型に変換します。
ifam.Scope == syscall.RT_SCOPE_HOST || ifam.Scope == syscall.RT_SCOPE_LINK
: 取得したアドレスのスコープがホストスコープまたはリンクスコープであるかを判定します。これらのスコープのアドレスは、通常、ゾーンIDを必要とします。ifa.Zone = zoneToString(int(ifam.Index))
:syscall.IfAddrmsg
構造体からインターフェースのインデックス(ifam.Index
)を取得し、それをzoneToString
関数で文字列に変換してIPNet
構造体のZone
フィールドに設定しています。
parseProcNetIGMP6
関数は、/proc/net/igmp6
ファイルからIPv6マルチキャストグループ情報をパースします。
if ifma.IP.IsInterfaceLocalMulticast() || ifma.IP.IsLinkLocalMulticast() { ... }
: 取得したマルチキャストアドレスがインターフェースローカルまたはリンクローカルスコープであるかを判定します。ifma.Zone = ifi.Name
: これらのスコープのマルチキャストアドレスに対して、関連するインターフェースの名前(ifi.Name
)をIPAddr
構造体のZone
フィールドに設定しています。
全体的な簡素化
goto done
の削除とbreak loop
の導入:interface_linux.go
のinterfaceTable
関数とaddrTable
関数で、goto
文を使ったループ脱出ロジックが、ラベル付きfor
ループとbreak loop
に置き換えられました。goto
はコードの可読性や保守性を損なう可能性があるため、より構造化された制御フローへの変更は良いプラクティスとされています。- 不要な空行の削除: コードの整形と簡素化の一環として、複数のファイルで不要な空行が削除されています。
これらの変更により、Goのnet
パッケージは、OSが提供するIPv6のスコープ情報(ゾーンID)をより適切に利用し、ネットワークアドレスの表現力を向上させています。
関連リンク
- Go言語の
net
パッケージドキュメント: https://pkg.go.dev/net - Go言語の
syscall
パッケージドキュメント: https://pkg.go.dev/syscall - Go言語の変更リスト (CL) 7300081: https://golang.org/cl/7300081
- Go Issue #4234: https://github.com/golang/go/issues/4234 (このコミットが解決または更新するIssue)
参考にした情報源リンク
- RFC 4007: IPv6 Scoped Address Architecture: https://datatracker.ietf.org/doc/html/rfc4007
- KAMEプロジェクト: https://www.kame.net/ (IPv6プロトコルスタックの実装に関する情報源)
- Linux
netlink
プロトコルに関する情報 (例:man 7 netlink
) - BSD
route
ソケットに関する情報 (例:man 4 route
)I have provided the detailed technical explanation in Markdown format, as requested. I have ensured all sections from the "章構成" are included, in the specified order, and the content is in Japanese, with maximum detail on background, prerequisite knowledge, and technical specifics. I have also included relevant links.
I am now done with this request.