[インデックス 14514] ファイルの概要
このコミットは、Go言語の net パッケージにおけるIPアドレス関連の処理を統合し、特にIPv6スコープアドレス機能の導入を継続するための変更です。net パッケージは、ネットワークI/Oのプリミティブを提供し、TCP/IP、UDP、Unixドメインソケットなどのネットワークプロトコルを扱うための機能を提供します。
コミット
commit 4f74bbd24ca2cecdd24dada8d1d6af0f24ebb211
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Wed Nov 28 06:36:05 2012 +0900
net: consoldate literal target address into IP address functions
This CL continues with introducing IPv6 scoped addressing capability
into the net package.
Update #4234.
R=rsc
CC=golang-dev
https://golang.org/cl/6842053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4f74bbd24ca2cecdd24dada8d1d6af0f24ebb211
元コミット内容
net: consoldate literal target address into IP address functions
この変更は、net パッケージにIPv6スコープアドレス機能の導入を継続するものです。
関連するIssue: #4234
変更の背景
このコミットの主な背景は、Go言語の net パッケージにおけるIPv6スコープアドレス機能のサポートを強化することにあります。IPv6スコープアドレスは、特定のネットワークインターフェースやリンクローカルな範囲に限定されたアドレスであり、その適切な処理はネットワークプログラミングにおいて重要です。
以前のGoの net パッケージでは、IPアドレスの解決や処理が TCPAddr、UDPAddr、IPAddr といった各アドレスタイプごとに個別の関数 (resolveTCPAddr, resolveUDPAddr, resolveIPAddr) で行われていました。これらの関数は、ホスト名からIPアドレスへの解決(DNSルックアップ)やポート番号の解析など、共通のロジックを多く含んでいました。
このような重複したロジックは、コードの保守性を低下させ、IPv6スコープアドレスのような新しい機能を追加する際に、各関数に個別に変更を加える必要がありました。特にIPv6スコープアドレスは、アドレスにゾーンインデックス(インターフェースID)が含まれる場合があり、これを適切に処理するためには、アドレス解決ロジックの一元化が望ましいと判断されました。
このコミットは、これらの共通ロジックを resolveInternetAddr という単一の関数に統合することで、コードの重複を排除し、IPv6スコープアドレスのサポートをより容易かつ堅牢にすることを目的としています。これにより、将来的なネットワーク機能の拡張やバグ修正が効率的に行えるようになります。
また、コミットメッセージにある Update #4234 は、GoのIssueトラッカーにおける特定の課題(Issue 4234: net: IPv6 scoped addressing)に関連するもので、このコミットがその課題解決の一環であることを示しています。
前提知識の解説
1. IPアドレスとネットワークプロトコル
- IPアドレス: インターネットプロトコル (IP) において、ネットワーク上のデバイスを一意に識別するための番号です。IPv4とIPv6の2つの主要なバージョンがあります。
- IPv4: 32ビットのアドレス空間を持ち、
192.168.1.1のような形式で表現されます。アドレス枯渇の問題が顕在化しています。 - IPv6: 128ビットのアドレス空間を持ち、
2001:0db8:85a3:0000:0000:8a2e:0370:7334のような形式で表現されます。IPv4のアドレス枯渇問題を解決し、より多くのデバイスを接続できるように設計されています。
- IPv4: 32ビットのアドレス空間を持ち、
- TCP (Transmission Control Protocol): 信頼性の高いコネクション指向のプロトコルで、データの順序保証や再送制御を行います。Webブラウジング (HTTP/HTTPS) やファイル転送 (FTP) などに利用されます。
- UDP (User Datagram Protocol): コネクションレス型のプロトコルで、高速なデータ転送を目的としますが、信頼性や順序保証は提供しません。DNSやストリーミング、オンラインゲームなどに利用されます。
- IP (Internet Protocol): TCPやUDPの下位層に位置し、パケットのルーティングを担当します。
2. IPv6スコープアドレス
IPv6アドレスには、その有効範囲(スコープ)によっていくつかの種類があります。
- グローバルユニキャストアドレス: インターネット全体で一意であり、ルーティング可能です。
- ユニークローカルアドレス: プライベートネットワーク内で使用され、インターネットにはルーティングされません。
- リンクローカルアドレス: 単一のリンク(ネットワークセグメント)内でのみ有効なアドレスです。ルーターを越えて転送されることはありません。
fe80::/10のプレフィックスを持ちます。
IPv6スコープアドレス、特にリンクローカルアドレスを使用する場合、同じアドレスが複数のインターフェースに割り当てられる可能性があります。このため、どのインターフェースを介して通信を行うかを明示的に指定する必要があります。これを「ゾーンインデックス」または「スコープID」と呼び、アドレスの末尾に %<zone_id> の形式で付加されます(例: fe80::1%eth0)。
3. Go言語の net パッケージ
Go言語の標準ライブラリである net パッケージは、ネットワークプログラミングの基本的な機能を提供します。
Addrインターフェース: ネットワークアドレスを表すインターフェースで、Network()とString()メソッドを持ちます。TCPAddr: TCPエンドポイントのアドレスを表す構造体。IPアドレスとポート番号、ゾーンインデックスを含みます。UDPAddr: UDPエンドポイントのアドレスを表す構造体。IPアドレスとポート番号、ゾーンインデックスを含みます。IPAddr: IPアドレスのみを表す構造体。IPアドレスとゾーンインデックスを含みます。ResolveTCPAddr,ResolveUDPAddr,ResolveIPAddr: それぞれTCP、UDP、IPアドレスを解決するための関数。ホスト名やポート番号を含む文字列から、対応する*TCPAddr,*UDPAddr,*IPAddr構造体を生成します。Dial/Listen/ListenPacket: ネットワーク接続の確立やリッスンを行うための高レベルAPI。
4. DNSルックアップ
ドメイン名(例: example.com)をIPアドレスに変換するプロセスです。Goの net パッケージは、内部的にDNSルックアップを実行してホスト名をIPアドレスに解決します。
技術的詳細
このコミットの核心は、net パッケージ内のアドレス解決ロジックの統合と、それに伴うIPv6スコープアドレスの処理の改善です。
1. resolveInternetAddr 関数の導入
以前は resolveTCPAddr, resolveUDPAddr, resolveIPAddr といった個別の関数が、それぞれTCP、UDP、IPのアドレス解決を行っていました。これらの関数は、ホスト名からIPアドレスへの変換、ポート番号の解析、IPv4/IPv6のフィルタリングなど、多くの共通処理を含んでいました。
このコミットでは、これらの共通処理を resolveInternetAddr という新しい内部関数に集約しました。この関数は、ネットワークタイプ (tcp, udp, ip など)、アドレス文字列、およびデッドラインを受け取り、解決された Addr インターフェース(具体的には *TCPAddr, *UDPAddr, *IPAddr のいずれか)を返します。
resolveInternetAddr の主なロジックは以下の通りです。
- ネットワークタイプの判別: 引数
netに応じて、TCP、UDP、IPのいずれのアドレスを解決するのかを判別します。 - ホストとポートの分離:
tcpやudpの場合、host:port形式のアドレス文字列をhostとportに分離します。この際、IPv6アドレスの[host]:port形式も適切に処理されます。新しい内部関数splitHostPortが導入され、ゾーンインデックスも分離できるようになりました。 - ポート番号の解析:
parsePort関数を使用してポート番号を整数に変換します。 - IPアドレスの解析:
- まず、
ParseIP(host)を使用して、hostが直接IPアドレスとして解析できるか試みます。 - IPアドレスとして解析できない場合、
lookupHostDeadlineを使用してDNSルックアップを実行し、ホスト名からIPアドレスのリストを取得します。 - 取得したIPアドレスのリストから、ネットワークタイプ (
tcp4,tcp6など) に応じて適切なIPアドレス(IPv4またはIPv6)を選択します。これはipv4onlyやipv6onlyといったフィルタリング関数によって行われます。
- まず、
Addr構造体の生成: 最終的に、解決されたIPアドレス、ポート番号、ゾーンインデックスを使用して、適切な*TCPAddr,*UDPAddr, または*IPAddr構造体を生成し、Addrインターフェースとして返します。
2. SplitHostPort と splitHostPort の変更
既存の SplitHostPort 関数は、host:port または [host]:port 形式の文字列から host と port を分離するものでした。このコミットでは、内部的に splitHostPort という新しい関数を導入し、host, port に加えて zone (ゾーンインデックス) も分離できるように拡張しました。
splitHostPort は、IPv6スコープアドレスの [::1%eth0]:80 のような形式から、::1 (host), 80 (port), eth0 (zone) を適切に抽出できるようになります。これにより、IPv6スコープアドレスの処理がより正確に行えるようになります。
3. 各 Resolve*Addr 関数の簡素化
ResolveTCPAddr, ResolveUDPAddr, ResolveIPAddr の各関数は、resolveInternetAddr を呼び出すように変更されました。これにより、各関数内の重複したアドレス解決ロジックが削除され、コードが大幅に簡素化されました。
例えば、ResolveTCPAddr は、単に resolveInternetAddr を呼び出し、その結果を *TCPAddr に型アサートするだけになりました。これにより、アドレス解決の共通ロジックが resolveInternetAddr にカプセル化され、保守性が向上しました。
4. テストケースの追加
ipraw_test.go, tcp_test.go, udp_test.go に、ResolveIPAddr, ResolveTCPAddr, ResolveUDPAddr の新しいテストケースが追加されました。これらのテストケースは、様々なネットワークタイプとアドレス形式(特にIPv6アドレスやポート番号を含む形式)が正しく解決されることを検証します。これにより、変更が正しく機能し、既存の動作が損なわれていないことが保証されます。
コアとなるコードの変更箇所
src/pkg/net/dial.go
parseDialNetwork関数にip,ip4,ip6ネットワークタイプが追加され、resolveAfnetAddr関数内でこれらのタイプがresolveInternetAddrを呼び出すように変更されました。resolveAfnetAddr関数がresolveTCPAddr,resolveUDPAddr,resolveIPAddrの個別の呼び出しを削除し、resolveInternetAddrの単一の呼び出しに統合されました。ListenPacket関数の引数名がaddrからladdrに変更されました(機能的な変更ではなく、命名の一貫性のため)。
src/pkg/net/ipraw_test.go
resolveIPAddrTestsという新しいテストデータ構造と、TestResolveIPAddr関数が追加されました。これにより、ResolveIPAddrが様々なIPアドレス形式(IPv4, IPv6)とネットワークタイプに対して正しく動作するかを検証します。
src/pkg/net/iprawsock.go
ResolveIPAddr関数が大幅に簡素化され、内部のresolveIPAddr関数とhostToIP関数が削除されました。代わりに、parseDialNetworkでネットワークタイプを検証した後、resolveInternetAddrを呼び出すようになりました。
src/pkg/net/ipsock.go
SplitHostPort関数が、内部の新しい関数splitHostPortを呼び出すように変更されました。- 新しい内部関数
splitHostPortが追加されました。この関数は、hostport文字列からhost,port,zoneを分離する役割を担います。特にIPv6スコープアドレスのゾーンインデックスを処理できるようになりました。 hostPortToIP関数が削除され、そのロジックが新しいresolveInternetAddr関数に統合されました。resolveInternetAddrという新しい内部関数が追加されました。この関数は、TCP, UDP, IPのすべてのアドレス解決ロジックを統合したものです。ホスト名解決、ポート解析、IPアドレスのフィルタリング、そして最終的なAddr構造体の生成を行います。
src/pkg/net/tcp_test.go
resolveTCPAddrTestsという新しいテストデータ構造と、TestResolveTCPAddr関数が追加されました。これにより、ResolveTCPAddrが様々なTCPアドレス形式(IPv4, IPv6, ポート番号)とネットワークタイプに対して正しく動作するかを検証します。
src/pkg/net/tcpsock.go
ResolveTCPAddr関数が簡素化され、内部のresolveTCPAddr関数が削除されました。代わりに、ネットワークタイプを検証した後、resolveInternetAddrを呼び出すようになりました。
src/pkg/net/udp_test.go
resolveUDPAddrTestsという新しいテストデータ構造と、TestResolveUDPAddr関数が追加されました。これにより、ResolveUDPAddrが様々なUDPアドレス形式(IPv4, IPv6, ポート番号)とネットワークタイプに対して正しく動作するかを検証します。
src/pkg/net/udpsock.go
ResolveUDPAddr関数が簡素化され、内部のresolveUDPAddr関数が削除されました。代わりに、ネットワークタイプを検証した後、resolveInternetAddrを呼び出すようになりました。
コアとなるコードの解説
このコミットの最も重要な変更は、src/pkg/net/ipsock.go に追加された resolveInternetAddr 関数です。
func resolveInternetAddr(net, addr string, deadline time.Time) (Addr, error) {
var (
err error
host, port, zone string
portnum int
)
switch net {
case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6":
if addr != "" {
if host, port, zone, err = splitHostPort(addr); err != nil {
return nil, err
}
if portnum, err = parsePort(net, port); err != nil {
return nil, err
}
}
case "ip", "ip4", "ip6":
if addr != "" {
host = addr
}
default:
return nil, UnknownNetworkError(net)
}
inetaddr := func(net string, ip IP, port int, zone string) Addr {
switch net {
case "tcp", "tcp4", "tcp6":
return &TCPAddr{IP: ip, Port: port, Zone: zone}
case "udp", "udp4", "udp6":
return &UDPAddr{IP: ip, Port: port, Zone: zone}
case "ip", "ip4", "ip6":
return &IPAddr{IP: ip, Zone: zone}
}
return nil
}
if host == "" {
return inetaddr(net, nil, portnum, zone), nil
}
// Try as an IP address.
if ip := ParseIP(host); ip != nil {
return inetaddr(net, ip, portnum, zone), nil
}
var filter func(IP) IP
if net != "" && net[len(net)-1] == '4' {
filter = ipv4only
}
if net != "" && net[len(net)-1] == '6' {
filter = ipv6only
}
// Try as a DNS name.
addrs, err := lookupHostDeadline(host, deadline)
if err != nil {
return nil, err
}
ip := firstFavoriteAddr(filter, addrs)
if ip == nil {
// should not happen
return nil, &AddrError{"LookupHost returned no suitable address", addrs[0]}
}
return inetaddr(net, ip, portnum, zone), nil
}
この関数は、以下のステップでアドレス解決を行います。
-
入力のパース:
net引数(例: "tcp", "udp", "ip")に基づいて、アドレス文字列addrのパース方法を決定します。- "tcp" や "udp" の場合、
splitHostPortを使用してhost,port,zoneを分離し、parsePortでポート番号を数値に変換します。 - "ip" の場合、
addr全体がhostとなります。 - 不明なネットワークタイプの場合はエラーを返します。
-
inetaddrヘルパー関数:- これはクロージャとして定義されており、解決されたIPアドレス (
ip)、ポート番号 (port)、ゾーンインデックス (zone) を受け取り、適切な*TCPAddr,*UDPAddr, または*IPAddr構造体を生成してAddrインターフェースとして返します。これにより、アドレス構造体の生成ロジックが中央集約されます。
- これはクロージャとして定義されており、解決されたIPアドレス (
-
ホストが空の場合:
hostが空文字列の場合(例:":80"のようにポートのみが指定された場合)、IPアドレスはnilとなり、inetaddrを呼び出して対応するアドレス構造体を生成します。これは、ローカルホストの任意のアドレスにバインドする場合などに使用されます。
-
IPアドレスとしての直接解析:
ParseIP(host)を使用して、hostが直接IPアドレスとして解析できるか試みます。成功した場合、そのIPアドレスを使用してinetaddrを呼び出し、結果を返します。
-
DNSルックアップ:
hostがIPアドレスとして解析できない場合、DNSルックアップが必要と判断されます。net引数の末尾が '4' または '6' であるかに基づいて、ipv4onlyまたはipv6onlyのフィルタ関数を設定します。これにより、DNSルックアップの結果からIPv4またはIPv6アドレスのみを選択するようになります。lookupHostDeadline(host, deadline)を呼び出して、ホスト名からIPアドレスのリストを取得します。firstFavoriteAddr(filter, addrs)を使用して、フィルタリングされたIPアドレスのリストから最適なアドレスを選択します。- 適切なIPアドレスが見つからない場合はエラーを返します。
-
最終的な
Addrの生成:- DNSルックアップによってIPアドレスが解決された場合、そのIPアドレス、ポート番号、ゾーンインデックスを使用して
inetaddrを呼び出し、最終的なAddr構造体を生成して返します。
- DNSルックアップによってIPアドレスが解決された場合、そのIPアドレス、ポート番号、ゾーンインデックスを使用して
この resolveInternetAddr 関数が導入されたことで、ResolveTCPAddr, ResolveUDPAddr, ResolveIPAddr といった高レベルの関数は、単にこの共通関数を呼び出すだけの薄いラッパーとなり、コードの重複が大幅に削減されました。また、IPv6スコープアドレスのゾーンインデックスの処理もこの関数内で一元的に行われるため、将来的な拡張性も向上しています。
関連リンク
- Go言語の
netパッケージのドキュメント: https://pkg.go.dev/net - Go Issue 4234:
net: IPv6 scoped addressing: https://github.com/golang/go/issues/4234 - Go CL 6842053:
net: consoldate literal target address into IP address functions: https://golang.org/cl/6842053
参考にした情報源リンク
- Go言語の公式ドキュメント
- GitHubのGoリポジトリのコミット履歴とIssueトラッカー
- IPv6スコープアドレスに関する一般的なネットワーク知識
- TCP/IPプロトコルに関する一般的な知識
- DNSルックアップに関する一般的な知識
- Go言語の
netパッケージのソースコード分析 - https://www.rfc-editor.org/rfc/rfc4007 (IPv6 Scoped Address Architecture)
- https://www.rfc-editor.org/rfc/rfc4291 (IP Version 6 Addressing Architecture)
- https://www.rfc-editor.org/rfc/rfc3493 (Basic Socket Interface Extensions for IPv6)
- https://www.rfc-editor.org/rfc/rfc6874 (Representing IPv6 Zone Identifiers in Address Literals and URIs)I have generated the detailed explanation of the commit as requested. I have included all the specified sections and provided a comprehensive technical analysis, including background, prerequisite knowledge, and core code changes. The output is in Markdown format and in Japanese. I have also included relevant links and references.