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

[インデックス 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パッケージのデータ構造に適切に反映させることが求められました。

この変更により、IPNetIPAddrといった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 の範囲。単一のリンク内でのみ有効。
    • サイトローカル、組織ローカル、グローバルなど、他のスコープも存在します。

リンクローカルアドレスやインターフェースローカルマルチキャストアドレスのように、スコープが狭いアドレスは、複数のインターフェースで同じアドレス値を持つ可能性があります。例えば、fe80::1というリンクローカルアドレスが、eth0eth1の両方のインターフェースに存在し得ます。この場合、単に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フィールドを持つ。

このコミット以前は、IPNetIPAddr構造体はIPv6のゾーン情報を直接保持するフィールドを持っていませんでした。OSから取得したゾーン情報は、内部的に処理されるか、破棄されるかのいずれかでした。

syscallパッケージ

Go言語のsyscallパッケージは、低レベルのOSプリミティブへのアクセスを提供します。ネットワークインターフェースの情報やアドレス情報をOSから取得する際には、このパッケージを通じてシステムコールを呼び出す必要があります。

特に、BSD系のOS(macOS, FreeBSDなど)ではrouteソケットを介してルーティングメッセージをパースし、Linuxではnetlinkソケットを介してネットワーク情報を取得します。これらのシステムコールから返されるデータには、IPアドレスだけでなく、関連するインターフェースのインデックスや、IPv6アドレスに埋め込まれたスコープ情報が含まれることがあります。

技術的詳細

このコミットの主要な技術的変更点は、GoのnetパッケージがOSから取得したIPv6アドレス情報から、そのアドレスが属するゾーン(通常はインターフェースのインデックスまたは名前)を抽出し、IPNetおよびIPAddr構造体のZoneフィールドに設定するようになったことです。

具体的には、以下の処理が追加・変更されています。

  1. IPv6リンクローカルアドレスのゾーンID抽出:

    • BSD系のOS(interface_bsd.go, interface_darwin.go, interface_freebsd.go)において、syscall.SockaddrInet6から取得したIPv6アドレスがリンクローカルユニキャストアドレス (IsLinkLocalUnicast()) またはインターフェース/リンクローカルマルチキャストアドレス (IsInterfaceLocalMulticast(), IsLinkLocalMulticast()) である場合、そのアドレスに埋め込まれたインターフェースインデックスを抽出し、ifa.ZoneIPNetの場合)またはifma.ZoneIPAddrの場合)に設定します。
    • KAMEベースのIPv6プロトコルスタックでは、リンクローカルアドレスの特定のバイト(通常は3番目と4番目のバイト)にインターフェースインデックスが埋め込まれる慣習があります。このコミットでは、int(ifa.IP[2]<<8 | ifa.IP[3]) のようにして、この埋め込まれたインデックスを抽出しています。
    • 抽出後、埋め込まれたインデックスはアドレスからクリアされます(ifa.IP[2], ifa.IP[3] = 0, 0)。これは、アドレス値自体はスコープに依存しない形式で保持し、ゾーン情報は別途Zoneフィールドで管理するためです。
  2. 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)。
  3. コードの簡素化:

    • 複数のファイルで、switch v := m.(type) のようなパターンを switch m := m.(type) に変更しています。これは、vという一時変数を使わずに直接mを型アサーション後の型として使用することで、コードの可読性をわずかに向上させるものです。
    • Linux関連のコード(interface_linux.go)では、goto donedone: ラベルを使用していたループ処理を、break loop とラベル付きループに変更しています。これは、gotoの使用を避け、より構造化された制御フローにすることで、コードの保守性を高める一般的なプラクティスです。

これらの変更により、Goのnetパッケージは、OSから取得したIPv6アドレスのスコープ情報をより正確に表現できるようになり、アプリケーションがIPv6のスコープ付きアドレスを扱う際の堅牢性が向上しました。

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

このコミットのコアとなる変更は、主にsrc/pkg/net/interface_bsd.gosrc/pkg/net/interface_darwin.gosrc/pkg/net/interface_freebsd.gosrc/pkg/net/interface_linux.gonewAddr関数や関連するアドレス取得ロジックに見られます。

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.gointerfaceTable関数とaddrTable関数で、goto文を使ったループ脱出ロジックが、ラベル付きforループとbreak loopに置き換えられました。gotoはコードの可読性や保守性を損なう可能性があるため、より構造化された制御フローへの変更は良いプラクティスとされています。
  • 不要な空行の削除: コードの整形と簡素化の一環として、複数のファイルで不要な空行が削除されています。

これらの変更により、Goのnetパッケージは、OSが提供するIPv6のスコープ情報(ゾーンID)をより適切に利用し、ネットワークアドレスの表現力を向上させています。

関連リンク

参考にした情報源リンク

  • 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.