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

[インデックス 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)を解析し、定義されたルールに基づいてコードを書き換えます。

技術的詳細

このコミットの主要な技術的変更は、以下の点に集約されます。

  1. アドレス構造体への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スコープアドレスのゾーン情報を保持できるようになります。
  2. ゾーンIDとインターフェース名の変換関数:

    • src/pkg/net/ipsock.gozoneToString(zone int) stringzoneToInt(zone string) intの2つのヘルパー関数が追加されました。
      • zoneToStringは、システムが認識するインターフェースインデックス(整数)をインターフェース名(文字列)に変換します。
      • zoneToIntは、インターフェース名(文字列)をインターフェースインデックス(整数)に変換します。 これらの関数は、netパッケージが内部でゾーン情報を扱う際に、OSのシステムコールが要求する数値形式と、GoのAPIが提供する文字列形式の間で変換を行うために使用されます。
  3. 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スコープアドレスのゾーン情報を正しくやり取りできるようになります。
  4. cmd/fixツールの更新:

    • src/cmd/fix/netipv6zone.gosrc/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.SockaddrInet6ZoneIdフィールドに設定されるようになりました。
    • TCPAddrUDPAddrの初期化がフィールド名を明示する形式に変更されました。
  • 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アドレスのように、同じアドレスが複数のインターフェースに存在しうる場合に、通信経路を一意に特定するために不可欠です。

zoneToStringzoneToInt関数

src/pkg/net/ipsock.goに追加されたzoneToStringzoneToIntは、ゾーン情報の内部的な処理を担います。

  • 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.gosrc/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エコシステムの設計思想を反映しています。

関連リンク

参考にした情報源リンク