[インデックス 14478] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnet
パッケージにIPv6スコープアドレスの概念を導入し、関連するアドレス構造体(IPAddr
, IPNet
, TCPAddr
, UDPAddr
)にZone
フィールドを追加するものです。また、既存のコードベースがこの新しいAPIに対応できるよう、cmd/fix
ツールに自動修正機能が追加されています。
コミット
commit e8cf49f701cf9204f51df2557f75e33d2da4b5d9
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Tue Nov 27 00:45:42 2012 +0900
net, cmd/fix: add IPv6 scoped addressing zone to INET, INET6 address structs
This CL starts to introduce IPv6 scoped addressing capability
into the net package.
The Public API changes are:
+pkg net, type IPAddr struct, Zone string
+pkg net, type IPNet struct, Zone string
+pkg net, type TCPAddr struct, Zone string
+pkg net, type UDPAddr struct, Zone string
Update #4234.
R=rsc, bradfitz, iant
CC=golang-dev
https://golang.org/cl/6849045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e8cf49f701cf9204f51df2557f75e33d2da4b5d9
元コミット内容
net, cmd/fix: add IPv6 scoped addressing zone to INET, INET6 address structs
この変更は、net
パッケージにIPv6スコープアドレス機能の導入を開始するものです。
公開APIの変更点は以下の通りです:
pkg net
,type IPAddr struct
,Zone string
pkg net
,type IPNet struct
,Zone string
pkg net
,type TCPAddr struct
,Zone string
pkg net
,type UDPAddr struct
,Zone string
Issue #4234 を更新します。
変更の背景
IPv6アドレスには、そのアドレスが有効な範囲(スコープ)を示す概念があります。特にリンクローカルアドレス(fe80::/10
)のようなスコープを持つアドレスは、複数のネットワークインターフェースに同じアドレスが存在する可能性があります。この場合、単にIPアドレスだけでは通信相手を一意に特定できません。どのインターフェース(ゾーン)を介して通信すべきかを指定する必要があります。
Goのnet
パッケージは、これまでIPv6スコープアドレスの「ゾーン」情報を直接扱うためのAPIを持っていませんでした。このため、リンクローカルアドレスを用いた通信など、ゾーン情報が必須となるシナリオにおいて、GoアプリケーションがネイティブにIPv6の全機能を活用することが困難でした。
このコミットは、net
パッケージの主要なアドレス構造体(IPAddr
, IPNet
, TCPAddr
, UDPAddr
)にZone
フィールドを追加することで、この問題を解決し、IPv6スコープアドレスの完全なサポートを可能にすることを目的としています。これにより、GoプログラムがIPv6のリンクローカルアドレスやサイトローカルアドレスをより適切に処理できるようになります。
前提知識の解説
IPv6アドレスのスコープとゾーンID
IPv6アドレスは、そのアドレスが有効な範囲を示す「スコープ」という概念を持ちます。主なスコープには以下のものがあります。
- ノードローカル (Node-Local): ループバックアドレス(
::1
)など、単一のノード内でのみ有効なアドレス。 - リンクローカル (Link-Local):
fe80::/10
のプレフィックスを持つアドレスで、単一のリンク(ネットワークセグメント)内でのみ有効です。ルーターを越えて転送されることはありません。同じリンク上に複数のインターフェースがある場合、それぞれのインターフェースに同じリンクローカルアドレスが割り当てられることがあります。 - サイトローカル (Site-Local):
fec0::/10
のプレフィックスを持つアドレスで、組織内でのみ有効なアドレスとして定義されていましたが、現在は非推奨です。 - ユニークローカル (Unique Local Address - ULA):
fc00::/7
のプレフィックスを持つアドレスで、サイトローカルアドレスの代替として導入されました。組織内でルーティング可能ですが、グローバルインターネットにはルーティングされません。 - グローバルユニキャスト (Global Unicast): インターネット全体で一意であり、ルーティング可能なアドレス。
ゾーンID (Zone ID) は、特にリンクローカルアドレスのように複数のインターフェースで同じアドレスが使用される可能性がある場合に、どのネットワークインターフェースを指すのかを明確にするために使用されます。例えば、fe80::1%eth0
のように、IPアドレスの後に%
とインターフェース名(またはインデックス)を付加して表現されます。このインターフェース名が「ゾーン」情報に相当します。
Go言語のnet
パッケージ
net
パッケージは、Go言語におけるネットワークI/Oの基本的なインターフェースを提供します。TCP/IP、UDP/IP、Unixドメインソケットなどのネットワークプロトコルを扱うための型や関数が含まれています。IPAddr
, TCPAddr
, UDPAddr
などの構造体は、それぞれIPアドレス、TCPエンドポイント、UDPエンドポイントを表します。
Go言語のcmd/fix
ツール
cmd/fix
は、Goのツールチェーンに含まれるコマンドラインツールです。Go言語のAPIが変更された際に、古いAPIを使用しているソースコードを新しいAPIに自動的に修正するために使用されます。これは、Go言語の互換性保証の一部であり、大規模なAPI変更があっても既存のコードベースの移行を容易にするための重要なメカニズムです。fix
ツールはGoの抽象構文木(AST)を解析し、定義されたルールに基づいてコードを書き換えます。
技術的詳細
このコミットの主要な技術的変更は、以下の点に集約されます。
-
アドレス構造体への
Zone
フィールドの追加:src/pkg/net/ip.go
内のIPNet
構造体src/pkg/net/iprawsock.go
内のIPAddr
構造体src/pkg/net/tcpsock.go
内のTCPAddr
構造体src/pkg/net/udpsock.go
内のUDPAddr
構造体 に、Zone string // IPv6 scoped addressing zone
というフィールドが追加されました。これにより、これらのアドレス構造体がIPv6スコープアドレスのゾーン情報を保持できるようになります。
-
ゾーンIDとインターフェース名の変換関数:
src/pkg/net/ipsock.go
にzoneToString(zone int) string
とzoneToInt(zone string) int
の2つのヘルパー関数が追加されました。zoneToString
は、システムが認識するインターフェースインデックス(整数)をインターフェース名(文字列)に変換します。zoneToInt
は、インターフェース名(文字列)をインターフェースインデックス(整数)に変換します。 これらの関数は、net
パッケージが内部でゾーン情報を扱う際に、OSのシステムコールが要求する数値形式と、GoのAPIが提供する文字列形式の間で変換を行うために使用されます。
-
syscall
パッケージとの連携強化:src/pkg/net/iprawsock_posix.go
,src/pkg/net/ipsock_posix.go
,src/pkg/net/tcpsock_posix.go
,src/pkg/net/udpsock_posix.go
などのOS固有のネットワーク実装ファイルにおいて、syscall.SockaddrInet6
構造体のZoneId
フィールドが利用されるようになりました。ipToSockaddr
関数(IPアドレスとポートからsyscall.Sockaddr
を生成する関数)のシグネチャにzone string
引数が追加され、IPv6アドレスの場合にこのゾーン情報がsyscall.SockaddrInet6.ZoneId
に設定されるようになりました。- 同様に、
sockaddrToIP
,sockaddrToTCP
,sockaddrToUDP
などの関数では、syscall.SockaddrInet6.ZoneId
からゾーン情報を抽出し、zoneToString
関数を使ってIPAddr
,TCPAddr
,UDPAddr
構造体のZone
フィールドに設定するようになりました。 これにより、Goのnet
パッケージは、基盤となるOSのネットワークスタックとIPv6スコープアドレスのゾーン情報を正しくやり取りできるようになります。
-
cmd/fix
ツールの更新:src/cmd/fix/netipv6zone.go
とsrc/cmd/fix/netipv6zone_test.go
が新規追加されました。netipv6zone.go
には、net.IPNet
,net.IPAddr
,net.UDPAddr
,net.TCPAddr
の複合リテラル(net.IPNet{ip, mask}
のような形式)を、新しいフィールド名付きの形式(net.IPNet{IP: ip, Mask: mask}
)に自動的に変換するロジックが実装されています。これは、Zone
フィールドが追加されたことで、既存のコードが位置引数で構造体を初期化している場合に、フィールドの順序が変わることでコンパイルエラーになるのを防ぐためです。fix
ツールはASTを走査し、これらの構造体リテラルを見つけて、Key: Value
形式に変換します。
これらの変更により、Goのnet
パッケージはIPv6スコープアドレスを完全にサポートし、既存のコードベースとの互換性を保ちながら新しいAPIへの移行を支援します。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルで行われています。
-
src/cmd/fix/netipv6zone.go
:netipv6zoneFix
という新しいfix
ルールが登録されています。netipv6zone
関数が、net.IPNet
,net.IPAddr
,net.UDPAddr
,net.TCPAddr
の複合リテラルを走査し、位置引数で初期化されている場合に、IP:
,Mask:
,Port:
といったフィールド名を明示的に追加するよう修正します。
-
src/cmd/fix/netipv6zone_test.go
:netipv6zone.go
で定義されたfix
ルールのテストケースが含まれています。net.IPNet{net.ParseIP("..."), net.IPMask("...")}
のような形式がnet.IPNet{IP: net.ParseIP("..."), Mask: net.IPMask("...")}
に変換されることを確認しています。
-
src/pkg/net/ip.go
:IPNet
構造体にZone string
フィールドが追加されました。ParseCIDR
関数内でIPNet
の初期化が&IPNet{IP: ip.Mask(m), Mask: m}
のようにフィールド名を明示する形式に変更されました。
-
src/pkg/net/iprawsock.go
:IPAddr
構造体にZone string
フィールドが追加されました。resolveIPAddr
関数内でIPAddr
の初期化が&IPAddr{IP: ip}
のようにフィールド名を明示する形式に変更されました。
-
src/pkg/net/iprawsock_posix.go
:sockaddrToIP
関数でsyscall.SockaddrInet6
からZoneId
を抽出し、zoneToString
を使ってIPAddr.Zone
に設定するようになりました。IPAddr.sockaddr
関数がipToSockaddr
を呼び出す際にa.Zone
を渡すようになりました。ReadFromIP
およびReadMsgIP
関数で、受信したIPv6パケットのゾーン情報をIPAddr.Zone
に設定するようになりました。
-
src/pkg/net/ipsock.go
:zoneToString
関数とzoneToInt
関数が新規追加されました。これらはインターフェースインデックスとインターフェース名の相互変換を行います。
-
src/pkg/net/ipsock_posix.go
:ipToSockaddr
関数のシグネチャにzone string
引数が追加され、syscall.SockaddrInet6
のZoneId
フィールドに設定されるようになりました。TCPAddr
とUDPAddr
の初期化がフィールド名を明示する形式に変更されました。
-
src/pkg/net/multicast_posix_test.go
:- テストケース内の
UDPAddr
の初期化が、IP:
とPort:
を明示する形式に変更されました。
- テストケース内の
-
src/pkg/net/tcpsock.go
:TCPAddr
構造体にZone string
フィールドが追加されました。resolveTCPAddr
関数内でTCPAddr
の初期化が&TCPAddr{IP: ip, Port: port}
のようにフィールド名を明示する形式に変更されました。
-
src/pkg/net/tcpsock_posix.go
:sockaddrToTCP
関数でsyscall.SockaddrInet6
からZoneId
を抽出し、zoneToString
を使ってTCPAddr.Zone
に設定するようになりました。TCPAddr.sockaddr
関数がipToSockaddr
を呼び出す際にa.Zone
を渡すようになりました。
-
src/pkg/net/udpsock.go
:UDPAddr
構造体にZone string
フィールドが追加されました。resolveUDPAddr
関数内でUDPAddr
の初期化が&UDPAddr{IP: ip, Port: port}
のようにフィールド名を明示する形式に変更されました。
-
src/pkg/net/udpsock_plan9.go
:ReadFromUDP
関数内でUDPAddr
の初期化がIP:
とPort:
を明示する形式に変更されました。
-
src/pkg/net/udpsock_posix.go
:sockaddrToUDP
関数でsyscall.SockaddrInet6
からZoneId
を抽出し、zoneToString
を使ってUDPAddr.Zone
に設定するようになりました。UDPAddr.sockaddr
関数がipToSockaddr
を呼び出す際にa.Zone
を渡すようになりました。ReadFromUDP
およびReadMsgUDP
関数で、受信したIPv6パケットのゾーン情報をUDPAddr.Zone
に設定するようになりました。joinIPv4GroupUDP
およびjoinIPv6GroupUDP
関数内でIPAddr
の初期化がIP:
を明示する形式に変更されました。
コアとなるコードの解説
このコミットの核心は、Goのネットワークアドレス構造体にIPv6の「ゾーン」情報を組み込むことで、IPv6スコープアドレスの完全なサポートを実現することです。
net
パッケージのアドレス構造体へのZone
フィールド追加
最も直接的な変更は、IPAddr
, IPNet
, TCPAddr
, UDPAddr
の各構造体にZone string
フィールドが追加されたことです。これにより、これらの構造体はIPアドレスだけでなく、そのアドレスが属するネットワークインターフェースの識別子(ゾーンID)も保持できるようになります。これは、特にリンクローカルIPv6アドレスのように、同じアドレスが複数のインターフェースに存在しうる場合に、通信経路を一意に特定するために不可欠です。
zoneToString
とzoneToInt
関数
src/pkg/net/ipsock.go
に追加されたzoneToString
とzoneToInt
は、ゾーン情報の内部的な処理を担います。
zoneToString(zone int)
: オペレーティングシステムがインターフェースを識別するために使用する数値のインデックス(ZoneId
)を、人間が読めるインターフェース名(例: "eth0", "lo0")に変換します。これは、net.InterfaceByIndex
関数を利用して行われます。zoneToInt(zone string)
: その逆で、インターフェース名から数値のインデックスに変換します。これはnet.InterfaceByName
関数を利用します。 これらの関数は、Goのnet
パッケージがOSのシステムコールと連携する際に、ゾーン情報の形式を適切に変換するために使用されます。
syscall
パッケージとの連携
POSIXシステム(Linux, macOSなど)では、ソケットアドレス構造体(例: sockaddr_in6
)にsin6_scope_id
というフィールドがあり、これがIPv6スコープID(インターフェースインデックス)を保持します。
このコミットでは、src/pkg/net/iprawsock_posix.go
やsrc/pkg/net/ipsock_posix.go
などのファイルで、syscall.SockaddrInet6
構造体のZoneId
フィールドが活用されるようになりました。
ipToSockaddr
関数は、GoのIP
アドレスとZone
文字列を受け取り、syscall.SockaddrInet6
構造体を生成する際に、zoneToInt
を使ってZone
文字列を数値のZoneId
に変換し、syscall.SockaddrInet6.ZoneId
に設定します。- 逆に、
sockaddrToIP
などの関数は、syscall.SockaddrInet6
からZoneId
を読み取り、zoneToString
を使ってIPAddr.Zone
などのGoのアドレス構造体のZone
フィールドに設定します。 この連携により、Goのnet
パッケージは、OSレベルでIPv6スコープアドレスのゾーン情報を透過的に処理できるようになります。
cmd/fix
によるコードの自動修正
Zone
フィールドが追加されたことで、既存のGoコードでnet.IPAddr{ip}
やnet.TCPAddr{ip, port}
のように、フィールド名を明示せずに構造体リテラルを初期化している箇所がコンパイルエラーになる可能性があります。これは、新しいフィールドが追加されたことで、位置引数の意味が変わってしまうためです。
src/cmd/fix/netipv6zone.go
に実装されたnetipv6zone
というfix
ルールは、この問題を解決します。このルールは、Goのソースコードの抽象構文木(AST)を解析し、net.IPAddr
, net.IPNet
, net.TCPAddr
, net.UDPAddr
の複合リテラルでフィールド名が省略されているものを見つけ出します。そして、それらを自動的にIP: ip
, Mask: mask
, Port: port
のようにフィールド名を明示する形式に書き換えます。
例えば、net.IPNet{net.ParseIP("2001:DB8::"), net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}
というコードは、net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask("ffff:ffff:ffff::")}
に変換されます。これにより、開発者は手動で大量のコードを修正することなく、新しいAPIにスムーズに移行できます。
これらの変更は、Goのnet
パッケージがIPv6のより高度な機能、特にスコープアドレスを扱う能力を大幅に向上させ、同時に既存のコードベースとの互換性を維持するためのGoエコシステムの設計思想を反映しています。
関連リンク
- Go Code Review: https://golang.org/cl/6849045
- Go Issue: https://golang.org/issue/4234
参考にした情報源リンク
- RFC 4007: IPv6 Scoped Address Architecture: https://datatracker.ietf.org/doc/html/rfc4007
- Go
net
package documentation: https://pkg.go.dev/net - Go
cmd/fix
documentation: https://pkg.go.dev/cmd/fix - Go
syscall
package documentation: https://pkg.go.dev/syscall - IPv6アドレスのスコープとゾーンIDに関する一般的な情報源 (例: Wikipedia, ネットワーク技術解説サイトなど)