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

[インデックス 14331] ファイルの概要

このコミットは、Go言語のネットワークパッケージにおいて、Windows環境でのIPv6サポートを実装するものです。具体的には、Windows Sockets API (Winsock) の GetAddrInfoW 関数を利用して、ホスト名からIPv6アドレスを解決する機能を追加し、関連するシステムコールやデータ構造を更新しています。

コミット

commit eb2e6e59ee4154d0cfa017d1c1a84c52ed2f624b
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Wed Nov 7 16:58:20 2012 +1100

    net: implement IPv6 support for windows
    
    Thank you zhoumichaely for original CL 5175042.
    
    Fixes #1740.
    Fixes #2315.
    
    R=golang-dev, bradfitz, mikioh.mikioh
    CC=golang-dev, zhoumichaely
    https://golang.org/cl/6822045

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/eb2e6e59ee4154d0cfa017d1c1a84c52ed2f624b

元コミット内容

net: implement IPv6 support for windows

このコミットは、WindowsにおけるGo言語のネットワークスタックにIPv6のサポートを追加します。元のCL (Change List) は zhoumichaely によって提供され、Alex Brainman がそれを統合しました。この変更は、GoのIssue #1740 と #2315 を修正することを目的としています。

変更の背景

Go言語の初期のバージョンでは、Windows環境におけるネットワーク操作は主にIPv4に限定されていました。しかし、インターネットの普及とIPv4アドレスの枯渇に伴い、IPv6への移行が世界的に進められていました。Go言語がクロスプラットフォームで動作する現代的な言語として、Windows環境でもIPv6をネイティブにサポートすることは、アプリケーションの互換性と将来性を確保するために不可欠でした。

このコミットは、GoのネットワークパッケージがWindows上でIPv6アドレスを適切に解決し、IPv6ネットワーク通信を行えるようにするための基盤を構築することを目的としています。コミットメッセージに記載されているIssue #1740 と #2315 は、おそらくWindowsにおけるIPv6サポートの欠如や関連するバグを報告していたものと推測されます。

前提知識の解説

IPv6 (Internet Protocol Version 6)

IPv6は、インターネットプロトコルの最新バージョンであり、IPv4の後継として設計されました。IPv4が32ビットアドレスを使用し、約43億個のアドレスしか提供できないのに対し、IPv6は128ビットアドレスを使用し、事実上無限のアドレス空間を提供します。これにより、インターネットに接続されるデバイスの爆発的な増加に対応できます。IPv6アドレスは通常、2001:0db8:85a3:0000:0000:8a2e:0370:7334 のように16進数で表現されます。

Windows Sockets API (Winsock)

Winsockは、Windowsオペレーティングシステム上でネットワークアプリケーションを開発するための標準的なプログラミングインターフェースです。TCP/IPプロトコルスタックへのアクセスを提供し、ソケットの作成、接続、データの送受信、名前解決などの機能を提供します。Go言語の net パッケージは、Windows上ではこのWinsock APIを内部的に利用してネットワーク操作を実現しています。

WSAStartup 関数

WSAStartup はWinsock APIを使用する前に必ず呼び出す必要がある関数です。この関数はWinsock DLLを初期化し、アプリケーションが使用するWinsockのバージョンをネゴシエートします。これにより、アプリケーションとWinsock実装間の互換性が確保されます。

GetAddrInfoW 関数

GetAddrInfoW はWinsock APIの一部であり、ホスト名(例: www.example.com)やサービス名(例: http、ポート番号 80)を、ネットワークアドレス構造体(sockaddr 構造体)のリストに変換するプロトコル非依存の関数です。この関数はIPv4とIPv6の両方をサポートしており、アプリケーションが特定のIPバージョンに依存することなく、ホスト名を解決できるようにします。W サフィックスは、この関数がUnicode文字列を引数として受け取ることを示します。

CancelIoEx 関数

CancelIoEx は、指定されたファイルハンドルに対する保留中のI/O操作をキャンセルするために使用されるWindows API関数です。Winsockのコンテキストでは、ソケットもファイルハンドルとして扱われるため、非同期ソケット操作のキャンセルに利用できます。この関数は、呼び出し元のスレッドとは異なるスレッドによって開始されたI/O要求もキャンセルできるため、柔軟性が高いです。

Go言語の syscall パッケージ

Go言語の syscall パッケージは、オペレーティングシステムの低レベルなシステムコールへのインターフェースを提供します。これにより、GoプログラムからOS固有の機能(ファイル操作、プロセス管理、ネットワーク操作など)を直接呼び出すことができます。このコミットでは、WindowsのWinsock APIをGoから呼び出すために syscall パッケージが拡張されています。

技術的詳細

このコミットの主要な技術的変更点は、WindowsにおけるIPアドレス解決のメカニズムを、従来の GetHostByName (IPv4のみをサポート) から、IPv4とIPv6の両方をサポートする GetAddrInfoW へと移行したことです。

具体的には、以下の点が挙げられます。

  1. GetAddrInfoW の導入:

    • src/pkg/syscall/syscall_windows.go および src/pkg/syscall/zsyscall_windows_386.gosrc/pkg/syscall/zsyscall_windows_amd64.goGetAddrInfoW および FreeAddrInfoW のシステムコール定義が追加されました。これにより、GoプログラムからこれらのWinsock API関数を直接呼び出すことが可能になります。
    • src/pkg/syscall/ztypes_windows.go には、GetAddrInfoW が使用する AddrinfoW 構造体と、IPv6アドレスを格納するための RawSockaddrInet6 構造体が定義されています。
  2. IPアドレス解決ロジックの変更:

    • src/pkg/net/lookup_windows.go において、lookupIP 関数が oldLookupIPnewLookupIP に分割されました。
    • oldLookupIP は従来の GetHostByName を使用するIPv4専用のロジックです。
    • newLookupIPGetAddrInfoW を使用して、IPv4とIPv6の両方のアドレスを解決する新しいロジックです。AddrinfoW 構造体から RawSockaddrInet4 (IPv4) および RawSockaddrInet6 (IPv6) 構造体を取り出し、Goの IP 型に変換しています。
    • src/pkg/net/fd_windows.gosysInit 関数内で、syscall.LoadGetAddrInfo() が成功した場合(つまり、システムが GetAddrInfoW をサポートしている場合)に、lookupIP 変数が newLookupIP に設定されるようになりました。これにより、実行時に利用可能な最適なIPアドレス解決メカニズムが選択されます。
  3. IPv6関連データ構造の追加:

    • src/pkg/syscall/syscall_windows.goRawSockaddrInet6 構造体が追加され、IPv6アドレスとポート番号、スコープIDを格納できるようになりました。
    • SockaddrInet6 構造体も更新され、RawSockaddrInet6 を内部に持つようになりました。また、sockaddr() メソッドと Sockaddr() メソッドがIPv6アドレスを適切に処理するように実装されています。
  4. テストの更新:

    • src/pkg/net/dialgoogle_test.goTestDialGoogleIPv6 関数が更新され、IPv6がサポートされていない環境ではテストをスキップするようになりました。これにより、テストの実行がより堅牢になります。

これらの変更により、Go言語のネットワークパッケージはWindows環境でIPv6をネイティブに扱い、IPv6アドレスへの接続や名前解決を適切に行えるようになりました。

コアとなるコードの変更箇所

このコミットで変更された主要なファイルと、その変更の概要は以下の通りです。

  • src/pkg/net/dialgoogle_test.go: IPv6テストのロジックを調整し、IPv6がサポートされていない環境でのテストスキップを改善。
  • src/pkg/net/fd_unix.go: sysInit() 関数が追加されたが、Unix環境では空の実装。
  • src/pkg/net/fd_windows.go: sysInit() 関数が追加され、Winsockの初期化 (WSAStartup) と GetAddrInfoW のロードチェックを行う。GetAddrInfoW が利用可能であれば、IPルックアップ関数を新しい実装に切り替える。
  • src/pkg/net/ipsock.go: sysInit() の呼び出しを init() 関数に追加し、IPv6サポートのプローブを初期化時に行うように変更。
  • src/pkg/net/ipsock_plan9.go: sysInit() 関数が追加されたが、Plan 9環境では空の実装。
  • src/pkg/net/lookup_windows.go:
    • lookupIPoldLookupIP にリネームし、従来の GetHostByName を使用するIPv4専用のルックアップ関数とする。
    • newLookupIP 関数を新しく追加し、GetAddrInfoW を使用してIPv4とIPv6の両方のアドレスを解決する。
    • lookupIP 変数を導入し、実行時に oldLookupIP または newLookupIP のいずれかを指すようにする。
  • src/pkg/syscall/syscall_windows.go:
    • GetAddrInfoWFreeAddrInfoW のシステムコール定義を追加。
    • RawSockaddrInet6 構造体を追加し、IPv6ソケットアドレスを表現。
    • SockaddrInet6 構造体の実装を更新し、IPv6アドレスの変換ロジックを追加。
    • RawSockaddrAnySockaddr() メソッドに AF_INET6 のケースを追加。
    • LoadGetAddrInfo() 関数を追加し、GetAddrInfoW がロード可能かチェックする。
  • src/pkg/syscall/zsyscall_windows_386.go: 32ビットWindows向けの GetAddrInfoWFreeAddrInfoW のシステムコール呼び出しスタブを追加。
  • src/pkg/syscall/zsyscall_windows_amd64.go: 64ビットWindows向けの GetAddrInfoWFreeAddrInfoW のシステムコール呼び出しスタブを追加。
  • src/pkg/syscall/ztypes_windows.go: AddrinfoW 構造体と関連する定数 (AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST) を追加。

コアとなるコードの解説

src/pkg/net/lookup_windows.go の変更

// oldLookupIP は従来の GetHostByName を使用するIPv4専用のルックアップ関数
func oldLookupIP(name string) (addrs []IP, err error) {
	hostentLock.Lock()
	defer hostentLock.Unlock()
	h, err := syscall.GetHostByName(name)
	if err != nil {
		return nil, os.NewSyscallError("GetHostByName", err)
	}
	// ... (IPv4アドレスの処理) ...
	return addrs, nil
}

// newLookupIP は GetAddrInfoW を使用してIPv4とIPv6の両方のアドレスを解決する新しいルックアップ関数
func newLookupIP(name string) (addrs []IP, err error) {
	hints := syscall.AddrinfoW{
		Family:   syscall.AF_UNSPEC, // IPv4とIPv6の両方を許可
		Socktype: syscall.SOCK_STREAM,
		Protocol: syscall.IPPROTO_IP,
	}
	var result *syscall.AddrinfoW
	e := syscall.GetAddrInfoW(syscall.StringToUTF16Ptr(name), nil, &hints, &result)
	if e != nil {
		return nil, os.NewSyscallError("GetAddrInfoW", e)
	}
	defer syscall.FreeAddrInfoW(result) // 取得したメモリを解放

	addrs = make([]IP, 0, 5)
	for ; result != nil; result = result.Next {
		addr := unsafe.Pointer(result.Addr)
		switch result.Family {
		case syscall.AF_INET: // IPv4アドレスの場合
			a := (*syscall.RawSockaddrInet4)(addr).Addr
			addrs = append(addrs, IPv4(a[0], a[1], a[2], a[3]))
		case syscall.AF_INET6: // IPv6アドレスの場合
			a := (*syscall.RawSockaddrInet6)(addr).Addr
			addrs = append(addrs, IP{a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]})
		default:
			return nil, os.NewSyscallError("LookupIP", syscall.EWINDOWS)
		}
	}
	return addrs, nil
}

// lookupIP は、システムが GetAddrInfoW をサポートしているかどうかに応じて、
// oldLookupIP または newLookupIP のいずれかを指す変数
var lookupIP = oldLookupIP

この変更は、WindowsにおけるIPアドレス解決の柔軟性を大幅に向上させます。GetAddrInfoW を使用することで、GoアプリケーションはIPv4とIPv6の両方のアドレスを透過的に解決できるようになり、デュアルスタック環境での動作が改善されます。

src/pkg/syscall/syscall_windows.go の変更

//sys	GetAddrInfoW(nodename *uint16, servicename *uint16, hints *AddrinfoW, result **AddrinfoW) (sockerr error) = ws2_32.GetAddrInfoW
//sys	FreeAddrInfoW(addrinfo *AddrinfoW) = ws2_32.FreeAddrInfoW

type RawSockaddrInet6 struct {
	Family   uint16
	Port     uint16
	Flowinfo uint32
	Addr     [16]byte /* in6_addr */
	Scope_id uint32
}

type SockaddrInet6 struct {
	Port   int
	ZoneId uint32
	Addr   [16]byte
	raw    RawSockaddrInet6
}

func (sa *SockaddrInet6) sockaddr() (uintptr, int32, error) {
	// SockaddrInet6 から RawSockaddrInet6 への変換ロジック
	// ポート番号、スコープID、IPv6アドレスを RawSockaddrInet6 に設定
	// ...
	return uintptr(unsafe.Pointer(&sa.raw)), int32(unsafe.Sizeof(sa.raw)), nil
}

func (rsa *RawSockaddrAny) Sockaddr() (Sockaddr, error) {
	switch rsa.Family {
	// ... AF_INET のケース ...
	case AF_INET6:
		pp := (*RawSockaddrInet6)(unsafe.Pointer(rsa))
		sa := new(SockaddrInet6)
		// RawSockaddrInet6 から SockaddrInet6 への変換ロジック
		// ...
		return sa, nil
	}
	return nil, EAFNOSUPPORT
}

func LoadGetAddrInfo() error {
	return procGetAddrInfoW.Find() // GetAddrInfoW がシステムに存在するかチェック
}

この部分では、Windowsの低レベルなシステムコールをGoから呼び出すためのインターフェースが定義されています。RawSockaddrInet6 はWinsockが内部で使用するIPv6アドレス構造体をGoで表現したものであり、SockaddrInet6 はGoのネットワークパッケージが扱うIPv6ソケットアドレスの抽象化です。これらの構造体と関連するメソッドの実装により、GoはWindows上でIPv6アドレスを適切にパースし、操作できるようになります。LoadGetAddrInfo() は、実行時に GetAddrInfoW 関数が利用可能かどうかを動的に確認するために使用されます。

関連リンク

参考にした情報源リンク