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

[インデックス 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アドレスの解決や処理が TCPAddrUDPAddrIPAddr といった各アドレスタイプごとに個別の関数 (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のアドレス枯渇問題を解決し、より多くのデバイスを接続できるように設計されています。
  • 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のいずれのアドレスを解決するのかを判別します。
  • ホストとポートの分離: tcpudp の場合、host:port 形式のアドレス文字列を hostport に分離します。この際、IPv6アドレスの [host]:port 形式も適切に処理されます。新しい内部関数 splitHostPort が導入され、ゾーンインデックスも分離できるようになりました。
  • ポート番号の解析: parsePort 関数を使用してポート番号を整数に変換します。
  • IPアドレスの解析:
    • まず、ParseIP(host) を使用して、host が直接IPアドレスとして解析できるか試みます。
    • IPアドレスとして解析できない場合、lookupHostDeadline を使用してDNSルックアップを実行し、ホスト名からIPアドレスのリストを取得します。
    • 取得したIPアドレスのリストから、ネットワークタイプ (tcp4, tcp6 など) に応じて適切なIPアドレス(IPv4またはIPv6)を選択します。これは ipv4onlyipv6only といったフィルタリング関数によって行われます。
  • Addr 構造体の生成: 最終的に、解決されたIPアドレス、ポート番号、ゾーンインデックスを使用して、適切な *TCPAddr, *UDPAddr, または *IPAddr 構造体を生成し、Addr インターフェースとして返します。

2. SplitHostPortsplitHostPort の変更

既存の SplitHostPort 関数は、host:port または [host]:port 形式の文字列から hostport を分離するものでした。このコミットでは、内部的に 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
}

この関数は、以下のステップでアドレス解決を行います。

  1. 入力のパース:

    • net 引数(例: "tcp", "udp", "ip")に基づいて、アドレス文字列 addr のパース方法を決定します。
    • "tcp" や "udp" の場合、splitHostPort を使用して host, port, zone を分離し、parsePort でポート番号を数値に変換します。
    • "ip" の場合、addr 全体が host となります。
    • 不明なネットワークタイプの場合はエラーを返します。
  2. inetaddr ヘルパー関数:

    • これはクロージャとして定義されており、解決されたIPアドレス (ip)、ポート番号 (port)、ゾーンインデックス (zone) を受け取り、適切な *TCPAddr, *UDPAddr, または *IPAddr 構造体を生成して Addr インターフェースとして返します。これにより、アドレス構造体の生成ロジックが中央集約されます。
  3. ホストが空の場合:

    • host が空文字列の場合(例: ":80" のようにポートのみが指定された場合)、IPアドレスは nil となり、inetaddr を呼び出して対応するアドレス構造体を生成します。これは、ローカルホストの任意のアドレスにバインドする場合などに使用されます。
  4. IPアドレスとしての直接解析:

    • ParseIP(host) を使用して、host が直接IPアドレスとして解析できるか試みます。成功した場合、そのIPアドレスを使用して inetaddr を呼び出し、結果を返します。
  5. DNSルックアップ:

    • host がIPアドレスとして解析できない場合、DNSルックアップが必要と判断されます。
    • net 引数の末尾が '4' または '6' であるかに基づいて、ipv4only または ipv6only のフィルタ関数を設定します。これにより、DNSルックアップの結果からIPv4またはIPv6アドレスのみを選択するようになります。
    • lookupHostDeadline(host, deadline) を呼び出して、ホスト名からIPアドレスのリストを取得します。
    • firstFavoriteAddr(filter, addrs) を使用して、フィルタリングされたIPアドレスのリストから最適なアドレスを選択します。
    • 適切なIPアドレスが見つからない場合はエラーを返します。
  6. 最終的な Addr の生成:

    • DNSルックアップによってIPアドレスが解決された場合、そのIPアドレス、ポート番号、ゾーンインデックスを使用して inetaddr を呼び出し、最終的な Addr 構造体を生成して返します。

この resolveInternetAddr 関数が導入されたことで、ResolveTCPAddr, ResolveUDPAddr, ResolveIPAddr といった高レベルの関数は、単にこの共通関数を呼び出すだけの薄いラッパーとなり、コードの重複が大幅に削減されました。また、IPv6スコープアドレスのゾーンインデックスの処理もこの関数内で一元的に行われるため、将来的な拡張性も向上しています。

関連リンク

参考にした情報源リンク

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