[インデックス 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
へと移行したことです。
具体的には、以下の点が挙げられます。
-
GetAddrInfoW
の導入:src/pkg/syscall/syscall_windows.go
およびsrc/pkg/syscall/zsyscall_windows_386.go
、src/pkg/syscall/zsyscall_windows_amd64.go
にGetAddrInfoW
およびFreeAddrInfoW
のシステムコール定義が追加されました。これにより、GoプログラムからこれらのWinsock API関数を直接呼び出すことが可能になります。src/pkg/syscall/ztypes_windows.go
には、GetAddrInfoW
が使用するAddrinfoW
構造体と、IPv6アドレスを格納するためのRawSockaddrInet6
構造体が定義されています。
-
IPアドレス解決ロジックの変更:
src/pkg/net/lookup_windows.go
において、lookupIP
関数がoldLookupIP
とnewLookupIP
に分割されました。oldLookupIP
は従来のGetHostByName
を使用するIPv4専用のロジックです。newLookupIP
はGetAddrInfoW
を使用して、IPv4とIPv6の両方のアドレスを解決する新しいロジックです。AddrinfoW
構造体からRawSockaddrInet4
(IPv4) およびRawSockaddrInet6
(IPv6) 構造体を取り出し、GoのIP
型に変換しています。src/pkg/net/fd_windows.go
のsysInit
関数内で、syscall.LoadGetAddrInfo()
が成功した場合(つまり、システムがGetAddrInfoW
をサポートしている場合)に、lookupIP
変数がnewLookupIP
に設定されるようになりました。これにより、実行時に利用可能な最適なIPアドレス解決メカニズムが選択されます。
-
IPv6関連データ構造の追加:
src/pkg/syscall/syscall_windows.go
にRawSockaddrInet6
構造体が追加され、IPv6アドレスとポート番号、スコープIDを格納できるようになりました。SockaddrInet6
構造体も更新され、RawSockaddrInet6
を内部に持つようになりました。また、sockaddr()
メソッドとSockaddr()
メソッドがIPv6アドレスを適切に処理するように実装されています。
-
テストの更新:
src/pkg/net/dialgoogle_test.go
のTestDialGoogleIPv6
関数が更新され、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
:lookupIP
をoldLookupIP
にリネームし、従来のGetHostByName
を使用するIPv4専用のルックアップ関数とする。newLookupIP
関数を新しく追加し、GetAddrInfoW
を使用してIPv4とIPv6の両方のアドレスを解決する。lookupIP
変数を導入し、実行時にoldLookupIP
またはnewLookupIP
のいずれかを指すようにする。
src/pkg/syscall/syscall_windows.go
:GetAddrInfoW
とFreeAddrInfoW
のシステムコール定義を追加。RawSockaddrInet6
構造体を追加し、IPv6ソケットアドレスを表現。SockaddrInet6
構造体の実装を更新し、IPv6アドレスの変換ロジックを追加。RawSockaddrAny
のSockaddr()
メソッドにAF_INET6
のケースを追加。LoadGetAddrInfo()
関数を追加し、GetAddrInfoW
がロード可能かチェックする。
src/pkg/syscall/zsyscall_windows_386.go
: 32ビットWindows向けのGetAddrInfoW
とFreeAddrInfoW
のシステムコール呼び出しスタブを追加。src/pkg/syscall/zsyscall_windows_amd64.go
: 64ビットWindows向けのGetAddrInfoW
とFreeAddrInfoW
のシステムコール呼び出しスタブを追加。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
関数が利用可能かどうかを動的に確認するために使用されます。
関連リンク
- Go言語のコミット: https://github.com/golang/go/commit/eb2e6e59ee4154d0cfa017d1c1a84c52ed2f624b
- Go CL (Change List): https://golang.org/cl/6822045
参考にした情報源リンク
GetAddrInfoW
function (Windows) - Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfowWSAStartup
function (Windows) - Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartupCancelIoEx
function (Windows) - Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-cancelioex- Windows Sockets (Winsock) - Wikipedia: https://en.wikipedia.org/wiki/Winsock
- IPv6 - Wikipedia: https://ja.wikipedia.org/wiki/IPv6