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

[インデックス 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の文字列スライスとして返す点にあります。

  1. 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で取得したリソースを適切にクリーンアップするために非常に重要です。
  2. TXTレコードのデータ抽出と変換:
    • DnsQuery が成功した場合、r には DNSRecord 構造体へのポインタが格納されます。TXTレコードの場合、r.Typesyscall.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 スライスに追加されます。
  3. ztypes_windows.go への DNSTXTData 構造体の追加:
    • Windows APIの DnsQuery が返すTXTレコードのデータ構造に対応するため、src/pkg/syscall/ztypes_windows.goDNSTXTData 構造体が追加されています。
    • StringCount uint16: TXTレコードに含まれる文字列の数。
    • StringArray [1]*uint16: TXTレコードの各文字列へのポインタの配列。[1] となっているのは、Goの構造体定義の制約上、少なくとも1つの要素が必要なためであり、実際には StringCount の数だけ要素が存在すると解釈されます。

エラーハンドリングの統一

コミットでは、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) != 0if 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レコードを透過的に利用できるようになります。

関連リンク

参考にした情報源リンク

  • 上記のMicrosoft Learnのドキュメント
  • Go言語のソースコードと関連するコミット履歴
  • Go言語の unsafe パッケージに関するドキュメント(ポインタ操作の理解のため)
  • Go言語の syscall パッケージの利用例に関する一般的な情報
  • DNSのTXTレコードに関する一般的な情報(RFCなど)
  • Windows APIにおける文字列エンコーディング(UTF-16)に関する情報
  • Go言語の make 関数における容量の指定に関する情報
  • Go言語の defer ステートメントに関する情報
  • Go言語のテストにおける t.Logftesting.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?