[インデックス 10204] ファイルの概要
このコミットは、Go言語の標準ライブラリ net パッケージにおいて、Windows環境での LookupTXT 関数の実装を追加するものです。これにより、Windows上でもDNSのTXTレコードの問い合わせが可能になります。
コミット
commit b43cf81c8c6a4809e89f7e470996b73d035a88e5
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Wed Nov 2 17:11:40 2011 +1100
net: implement LookupTXT for windows
R=rsc
CC=golang-dev
https://golang.org/cl/5318056
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b43cf81c8c6a4809e89f7e470996b73d035a88e5
元コミット内容
このコミットは、Go言語の net パッケージにおけるWindows固有のDNSルックアップ処理に、TXTレコードの問い合わせ機能 LookupTXT を追加します。これまでのWindows版 LookupTXT は「未実装」というエラーを返していましたが、この変更によりWindows APIの DnsQuery を利用して実際にTXTレコードを取得できるようになります。また、関連するテストコードの修正と、Windowsシステムコールで使用する構造体 DNSTXTData の定義が追加されています。
変更の背景
Go言語の net パッケージは、ネットワーク関連の基本的な機能を提供しており、DNSルックアップはその重要な一部です。しかし、コミット当時のWindows環境では、TXTレコードの問い合わせ機能である LookupTXT が実装されていませんでした。これは、GoアプリケーションがWindows上で動作する際に、TXTレコードに依存する機能(例えば、SPFレコードの検証やドメイン認証など)を利用できないという制約を意味していました。
このコミットは、その制約を解消し、Go言語が提供するネットワーク機能のクロスプラットフォーム互換性を向上させることを目的としています。WindowsのネイティブなDNS解決APIを活用することで、GoプログラムがWindows環境でも他のOSと同様にTXTレコードを扱えるようにすることが背景にあります。
前提知識の解説
DNS (Domain Name System)
DNSは、インターネット上のドメイン名とIPアドレスを対応させる分散型のデータベースシステムです。ユーザーがWebサイトのドメイン名(例: example.com)を入力すると、DNSがそのドメイン名に対応するIPアドレス(例: 192.0.2.1)を解決し、ブラウザがそのIPアドレスに接続できるようにします。
DNSレコードの種類
DNSには様々な種類のレコードがあり、それぞれ異なる情報を提供します。
- Aレコード (Address Record): ドメイン名とIPv4アドレスを対応付けます。
- AAAAレコード (IPv6 Address Record): ドメイン名とIPv6アドレスを対応付けます。
- CNAMEレコード (Canonical Name Record): あるドメイン名を別のドメイン名にエイリアスします。
- MXレコード (Mail Exchange Record): ドメインのメールサーバーを指定します。
- SRVレコード (Service Record): 特定のサービス(例: SIP、XMPP)が利用するサーバーとポート番号を指定します。
- TXTレコード (Text Record): ドメインに関連付けられた任意のテキスト情報を格納します。これは、SPF (Sender Policy Framework) や DKIM (DomainKeys Identified Mail) のようなメール認証、ドメイン所有権の確認、その他のアプリケーション固有のデータなど、多岐にわたる用途で利用されます。
Windows DNS API (DnsQuery)
Windowsオペレーティングシステムは、DNSクエリを実行するためのネイティブなAPIを提供しています。その中心となるのが DnsQuery 関数です。この関数は、指定されたドメイン名とレコードタイプに基づいてDNSサーバーに問い合わせを行い、結果を DNSRecord 構造体のリストとして返します。
DnsQuery: DNSクエリを実行するための主要な関数。name: 問い合わせるドメイン名。wType: 問い合わせるDNSレコードのタイプ(例:DNS_TYPE_TEXT)。Options: クエリのオプション。pExtra: 予約済み。ppQueryResults: クエリ結果のレコードリストへのポインタ。pReserved: 予約済み。
DNS_TYPE_TEXT: TXTレコードを問い合わせる際にDnsQueryに渡すレコードタイプ。DNSRecord構造体:DnsQueryの結果として返されるDNSレコードの情報を格納する構造体。レコードのタイプに応じて、Dataメンバーが異なるデータ構造を指します。DNSTXTData構造体: TXTレコードのデータ部分を表現する構造体。複数のテキスト文字列を格納できます。syscall.UTF16ToString: Go言語のsyscallパッケージで提供される関数で、Windows APIが返すUTF-16エンコードされた文字列(*uint16の配列)をGoのUTF-8文字列に変換するために使用されます。Windowsの文字列は通常UTF-16で表現されるため、Goで扱う際にはこの変換が必要です。
Go言語の syscall パッケージ
Go言語の syscall パッケージは、オペレーティングシステムが提供する低レベルなシステムコールやAPIにアクセスするための機能を提供します。Windowsの場合、このパッケージを通じて DnsQuery のようなWin32 APIを呼び出すことができます。
技術的詳細
このコミットの主要な技術的ポイントは、WindowsのネイティブDNS APIである DnsQuery をGo言語から呼び出し、TXTレコードのデータを適切にパースしてGoの文字列スライスとして返す点にあります。
LookupTXT関数の実装:syscall.DnsQuery(name, syscall.DNS_TYPE_TEXT, 0, nil, &r, nil)を呼び出し、指定されたドメイン名 (name) のTXTレコード (DNS_TYPE_TEXT) を問い合わせます。DnsQueryの戻り値eが0でない場合(エラーが発生した場合)、os.NewSyscallErrorを使用してエラーを生成し返します。defer syscall.DnsRecordListFree(r, 1)を使用して、取得したDNSレコードリストのメモリを解放します。これは、Windows APIで取得したリソースを適切にクリーンアップするために非常に重要です。
- TXTレコードのデータ抽出と変換:
DnsQueryが成功した場合、rにはDNSRecord構造体へのポインタが格納されます。TXTレコードの場合、r.Typeはsyscall.DNS_TYPE_TEXTとなります。r.Dataメンバーは、レコードタイプに応じたデータ構造を指します。TXTレコードの場合、これはDNSTXTData構造体へのポインタとして解釈されます。d := (*syscall.DNSTXTData)(unsafe.Pointer(&r.Data[0]))の行で、r.Dataの先頭アドレスをDNSTXTData型のポインタにキャストしています。unsafe.Pointerは、Goの型安全性を一時的にバイパスして、任意の型へのポインタ変換を可能にするものです。DNSTXTData構造体にはStringCount(文字列の数)とStringArray(文字列へのポインタの配列)が含まれています。for _, v := range (*[1 << 10]*uint16)(unsafe.Pointer(&(d.StringArray[0])))[:d.StringCount]のループで、StringArray内の各文字列ポインタをイテレートします。(*[1 << 10]*uint16)(unsafe.Pointer(&(d.StringArray[0])))は、d.StringArray[0]のアドレスを*uint16の配列(最大1024個の要素を持つ)へのポインタとして解釈しています。これは、StringArrayが可変長の文字列ポインタを保持する可能性があるため、十分なサイズの配列として扱うためのテクニックです。[:d.StringCount]は、実際に存在する文字列の数 (d.StringCount) だけスライスを作成し、ループで処理します。
s := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(v))[:])の行で、各TXT文字列(vは*uint16型のポインタ)をGoの文字列に変換しています。(*[1 << 20]uint16)(unsafe.Pointer(v))は、vが指すUTF-16文字列を、最大1MBのUTF-16文字の配列へのポインタとして解釈しています。これは、文字列の長さを事前に知ることができないため、十分なバッファサイズを確保するための一般的なパターンです。syscall.UTF16ToStringは、このUTF-16バイト列をGoのUTF-8文字列に変換します。
- 変換された文字列は
txtスライスに追加されます。
ztypes_windows.goへのDNSTXTData構造体の追加:- Windows APIの
DnsQueryが返すTXTレコードのデータ構造に対応するため、src/pkg/syscall/ztypes_windows.goにDNSTXTData構造体が追加されています。 StringCount uint16: TXTレコードに含まれる文字列の数。StringArray [1]*uint16: TXTレコードの各文字列へのポインタの配列。[1]となっているのは、Goの構造体定義の制約上、少なくとも1つの要素が必要なためであり、実際にはStringCountの数だけ要素が存在すると解釈されます。
- Windows APIの
エラーハンドリングの統一
コミットでは、LookupCNAME, LookupSRV, LookupMX, LookupAddr の各関数で、DnsQuery の戻り値 e のチェックが if int(e) != 0 から if e != 0 に変更されています。これは、syscall.Errno 型が既に整数型として扱えるため、明示的な型変換 int(e) が不要になったことを示唆しています。これにより、コードの簡潔性が向上しています。
コアとなるコードの変更箇所
src/pkg/net/lookup_test.go
--- a/src/pkg/net/lookup_test.go
+++ b/src/pkg/net/lookup_test.go
@@ -52,10 +52,6 @@ func TestGmailMX(t *testing.T) {
}
func TestGmailTXT(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Logf("LookupTXT is not implemented on Windows")
- return
- }
if testing.Short() || avoidMacFirewall {
t.Logf("skipping test to avoid external network")
return
TestGmailTXT関数から、Windows環境でのLookupTXT未実装に関するスキップロジックが削除されました。これは、LookupTXTがWindowsで実装されたため、テストをスキップする必要がなくなったことを意味します。
src/pkg/net/lookup_windows.go
--- a/src/pkg/net/lookup_windows.go
+++ b/src/pkg/net/lookup_windows.go
@@ -5,7 +5,6 @@
package net
import (
- "errors"
"syscall"
"unsafe"
"os"
@@ -81,7 +80,7 @@ func LookupPort(network, service string) (port int, err error) {
func LookupCNAME(name string) (cname string, err error) {
var r *syscall.DNSRecord
e := syscall.DnsQuery(name, syscall.DNS_TYPE_CNAME, 0, nil, &r, nil)
- if int(e) != 0 {
+ if e != 0 {
return "", os.NewSyscallError("LookupCNAME", int(e))
}
defer syscall.DnsRecordListFree(r, 1)
@@ -110,7 +109,7 @@ func LookupSRV(service, proto, name string) (cname string, addrs []*SRV, err err
}
var r *syscall.DNSRecord
e := syscall.DnsQuery(target, syscall.DNS_TYPE_SRV, 0, nil, &r, nil)
- if int(e) != 0 {
+ if e != 0 {
return "", nil, os.NewSyscallError("LookupSRV", int(e))
}
defer syscall.DnsRecordListFree(r, 1)
@@ -126,7 +125,7 @@ func LookupSRV(service, proto, name string) (cname string, addrs []*SRV, err err
func LookupMX(name string) (mx []*MX, err error) {
var r *syscall.DNSRecord
e := syscall.DnsQuery(name, syscall.DNS_TYPE_MX, 0, nil, &r, nil)
- if int(e) != 0 {
+ if e != 0 {
return nil, os.NewSyscallError("LookupMX", int(e))
}
defer syscall.DnsRecordListFree(r, 1)
@@ -140,7 +139,21 @@ func LookupMX(name string) (mx []*MX, err error) {
}
func LookupTXT(name string) (txt []string, err error) {
- return nil, errors.New("net.LookupTXT is not implemented on Windows")
+ var r *syscall.DNSRecord
+ e := syscall.DnsQuery(name, syscall.DNS_TYPE_TEXT, 0, nil, &r, nil)
+ if e != 0 {
+ return nil, os.NewSyscallError("LookupTXT", int(e))
+ }
+ defer syscall.DnsRecordListFree(r, 1)
+ txt = make([]string, 0, 10)
+ if r != nil && r.Type == syscall.DNS_TYPE_TEXT {
+ d := (*syscall.DNSTXTData)(unsafe.Pointer(&r.Data[0]))
+ for _, v := range (*[1 << 10]*uint16)(unsafe.Pointer(&(d.StringArray[0])))[:d.StringCount] {
+ s := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(v))[:])
+ txt = append(txt, s)
+ }
+ }
+ return
}
func LookupAddr(addr string) (name []string, err error) {
@@ -150,7 +163,7 @@ func LookupAddr(addr string) (name []string, err error) {
}
var r *syscall.DNSRecord
e := syscall.DnsQuery(arpa, syscall.DNS_TYPE_PTR, 0, nil, &r, nil)
- if int(e) != 0 {
+ if e != 0 {
return nil, os.NewSyscallError("LookupAddr", int(e))
}
defer syscall.DnsRecordListFree(r, 1)
errorsパッケージのインポートが削除されました。LookupCNAME,LookupSRV,LookupMX,LookupAddr関数内のエラーチェックif int(e) != 0がif e != 0に変更されました。LookupTXT関数が完全に実装されました。以前は「未実装」というエラーを返していましたが、Windows APIのDnsQueryを使用してTXTレコードを取得するロジックが追加されました。
src/pkg/syscall/ztypes_windows.go
--- a/src/pkg/syscall/ztypes_windows.go
+++ b/src/pkg/syscall/ztypes_windows.go
@@ -530,6 +530,11 @@ type DNSMXData struct {
Pad uint16
}
+type DNSTXTData struct {
+ StringCount uint16
+ StringArray [1]*uint16
+}
+
type DNSRecord struct {
Next *DNSRecord
Name *uint16
DNSTXTData構造体が追加されました。これは、Windows DNS APIが返すTXTレコードのデータ構造に対応するためのものです。
コアとなるコードの解説
このコミットの核となるのは、src/pkg/net/lookup_windows.go 内の LookupTXT 関数の実装です。
func LookupTXT(name string) (txt []string, err error) {
var r *syscall.DNSRecord
// DnsQuery を呼び出し、指定されたドメイン名 (name) の TXT レコード (DNS_TYPE_TEXT) を問い合わせる
e := syscall.DnsQuery(name, syscall.DNS_TYPE_TEXT, 0, nil, &r, nil)
if e != 0 {
// エラーが発生した場合、os.NewSyscallError を使用してエラーを生成し返す
return nil, os.NewSyscallError("LookupTXT", int(e))
}
// 取得した DNS レコードリストのメモリを解放する (defer で確実に実行される)
defer syscall.DnsRecordListFree(r, 1)
// 結果を格納する文字列スライスを初期化
txt = make([]string, 0, 10) // 10 は初期容量のヒント
// レコードが存在し、かつ TXT タイプである場合のみ処理
if r != nil && r.Type == syscall.DNS_TYPE_TEXT {
// DNSRecord の Data メンバーを DNSTXTData 構造体へのポインタとして解釈
d := (*syscall.DNSTXTData)(unsafe.Pointer(&r.Data[0]))
// DNSTXTData 内の StringArray をループ処理
// StringArray は *uint16 (UTF-16 文字列へのポインタ) の配列
// [1 << 10]*uint16 は、最大 1024 個の文字列ポインタを保持できる配列として扱うためのテクニック
// [:d.StringCount] で、実際に存在する文字列の数だけスライスを作成
for _, v := range (*[1 << 10]*uint16)(unsafe.Pointer(&(d.StringArray[0])))[:d.StringCount] {
// 各 UTF-16 文字列ポインタ (v) を Go の UTF-8 文字列に変換
// [1 << 20]uint16 は、最大 1MB の UTF-16 文字を保持できる配列として扱うためのテクニック
s := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(v))[:])
// 変換された文字列を結果スライスに追加
txt = append(txt, s)
}
}
return
}
このコードは、Windows環境でDNSのTXTレコードを問い合わせるための標準的な手順をGo言語で実装しています。DnsQuery を使用してAPIを呼び出し、返された生データを unsafe.Pointer を用いてGoの構造体にマッピングし、syscall.UTF16ToString でUTF-16エンコードされた文字列をGoのUTF-8文字列に変換しています。これにより、GoアプリケーションはWindows上でもTXTレコードを透過的に利用できるようになります。
関連リンク
- Go言語の
netパッケージ: https://pkg.go.dev/net - Go言語の
syscallパッケージ: https://pkg.go.dev/syscall - Microsoft Learn - DnsQuery function: https://learn.microsoft.com/en-us/windows/win32/api/windns/nf-windns-dnsquery_a
- Microsoft Learn - DNS_RECORD structure: https://learn.microsoft.com/en-us/windows/win32/api/windns/ns-windns-dns_recorda
- Microsoft Learn - DNS_TXT_DATA structure: https://learn.microsoft.com/en-us/windows/win32/api/windns/ns-windns-dns_txt_data
参考にした情報源リンク
- 上記のMicrosoft Learnのドキュメント
- Go言語のソースコードと関連するコミット履歴
- Go言語の
unsafeパッケージに関するドキュメント(ポインタ操作の理解のため) - Go言語の
syscallパッケージの利用例に関する一般的な情報 - DNSのTXTレコードに関する一般的な情報(RFCなど)
- Windows APIにおける文字列エンコーディング(UTF-16)に関する情報
- Go言語の
make関数における容量の指定に関する情報 - Go言語の
deferステートメントに関する情報 - Go言語のテストにおける
t.Logfやtesting.Short()の利用に関する情報 - Go言語の
os.NewSyscallErrorの利用に関する情報 - Go言語の
runtime.GOOSの利用に関する情報I have provided the detailed explanation of the commit in Markdown format, following all the specified instructions and chapter structure. I have also included relevant links and referenced information sources.
Is there anything else you would like me to do?