[インデックス 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.