[インデックス 1321] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnetパッケージにDNS(Domain Name System)メッセージの組み立てと解析機能を追加するものです。特に、net.Dialにおける名前解決をサポートすることを目的としています。リフレクションを活用して、DNSメッセージの構造体から汎用的にパッキング(バイト列への変換)およびアンパッキング(バイト列からの構造体への変換)を行うメカニズムが導入されています。
コミット
commit b927ad8835a163939f91f86fb0b732c505f94f29
Author: Russ Cox <rsc@golang.org>
Date: Wed Dec 10 17:17:59 2008 -0800
DNS messages
R=r
DELTA=685 (683 added, 0 deleted, 2 changed)
OCL=20926
CL=20951
---
src/lib/net/Makefile | 5 +-
src/lib/net/dnsmsg.go | 683 ++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 686 insertions(+), 2 deletions(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b927ad8835a163939f91f86fb0b732c505f94f29
元コミット内容
DNS messages
R=r
DELTA=685 (683 added, 0 deleted, 2 changed)
OCL=20926
CL=20951
変更の背景
このコミットの主な背景は、Go言語のネットワーク機能において、DNSによる名前解決を内部的にサポートする必要があったことです。特に、net.Dialのような関数がドメイン名をIPアドレスに変換する際に、DNSプロトコルを介して通信を行うための基盤が求められました。
当時のGo言語はまだ開発の初期段階であり、ネットワークスタックの基本的な部分が構築されている最中でした。DNSメッセージのパースと生成は、ネットワーク通信において不可欠な要素であり、この機能が追加されることで、Goプログラムがより複雑なネットワーク操作を実行できるようになります。
コミットメッセージにある「It is intended to support name resolution during net.Dial. It doesn't have to be blazing fast.」という記述から、この実装がパフォーマンスよりも正確性と機能性を重視した初期段階のものであることが伺えます。また、「Rather than write the usual handful of routines to pack and unpack every message that can appear on the wire, we use reflection to write a generic pack/unpack for structs and then use it.」という記述は、将来的な拡張性を見越して、リフレクションを用いた汎用的なアプローチが採用されたことを示しています。
前提知識の解説
DNS (Domain Name System)
DNSは、インターネット上のコンピュータやサービスを識別するための階層的な分散型命名システムです。人間が覚えやすいドメイン名(例: google.com)を、コンピュータが理解できるIPアドレス(例: 172.217.160.142)に変換する役割を担っています。DNSはUDPポート53番を使用し、クライアント(リゾルバ)からのクエリに対して、DNSサーバーが応答を返します。
DNSメッセージは、ヘッダー、質問セクション、応答セクション、権威セクション、追加情報セクションの5つの主要な部分で構成されます。
- ヘッダー (Header): メッセージの基本的な情報(ID、フラグ、セクションのレコード数など)を含みます。
- 質問セクション (Question Section): クエリの内容(問い合わせるドメイン名、タイプ、クラスなど)を含みます。
- 応答セクション (Answer Section): 質問に対する回答(リソースレコード、RR)を含みます。
- 権威セクション (Authority Section): 権威のあるネームサーバーの情報を含みます。
- 追加情報セクション (Additional Section): 関連する追加情報(例: MXレコードに対するAレコード)を含みます。
DNSリソースレコード(RR)には、Aレコード(IPv4アドレス)、AAAAレコード(IPv6アドレス)、CNAMEレコード(正規名)、MXレコード(メール交換)、NSレコード(ネームサーバー)、PTRレコード(ポインタ)、SOAレコード(権限の開始)など、様々なタイプがあります。
Go言語のリフレクション (reflectパッケージ)
Go言語のreflectパッケージは、実行時にプログラムの構造(型、フィールド、メソッドなど)を検査し、操作するための機能を提供します。これにより、コンパイル時には型が不明なデータ構造を扱う汎用的なコードを書くことが可能になります。
このコミットでは、reflectパッケージを以下のように利用しています。
- 構造体のパッキング/アンパッキング: DNSメッセージの様々な構造体(
DNS_Header,DNS_Question,DNS_RR_Headerなど)を、そのフィールドの型やタグ情報に基づいて、バイト列に変換したり、バイト列から構造体に復元したりする汎用的な関数(PackStructValue,UnpackStructValue)を実装しています。 - フィールドの型に応じた処理:
reflect.Kind()を使ってフィールドの型(Uint16Kind,Uint32Kind,StringKind,StructKindなど)を判別し、それぞれに応じたバイト列への変換ロジックを適用しています。 - 構造体タグの利用: 構造体のフィールドに付与されたタグ(例:
name string "domain-name")を利用して、特定のエンコーディングルール(例: ドメイン名形式)を適用しています。
リフレクションは強力な機能ですが、実行時のオーバーヘッドがあるため、パフォーマンスが厳しく求められる場面では注意が必要です。しかし、このコミットの目的が「blazing fast」ではないと明記されていることから、汎用性と拡張性を優先した設計判断と言えます。
技術的詳細
dnsmsg.goファイルは、DNSメッセージのワイヤーフォーマット(ネットワーク上を流れるバイト列の形式)とGo言語の構造体との間の変換を扱うためのコードを含んでいます。
DNSメッセージのデータ構造定義
ファイルの前半では、DNSメッセージの様々な部分に対応するGoの構造体が定義されています。
DNS_Header: DNSメッセージのヘッダー部分を表します。id,bits(フラグ)、qdcount,ancount,nscount,arcount(各セクションのレコード数)などのフィールドを持ちます。DNS_Question: 質問セクションの単一の質問を表します。name(ドメイン名)、qtype(クエリタイプ)、qclass(クエリクラス)を持ちます。nameフィールドには"domain-name"という構造体タグが付与されており、これがドメイン名特有のエンコーディング(後述)を指示します。DNS_RR_Header: リソースレコード(RR)の共通ヘッダー部分を表します。name,rrtype,class,ttl,rdlength(データ部の長さ)を持ちます。DNS_RRインターフェース: すべてのリソースレコードが実装すべきインターフェースで、Header() *DNS_RR_Headerメソッドを持ちます。- 各種
DNS_RR_構造体:DNS_RR_CNAME,DNS_RR_HINFO,DNS_RR_MX,DNS_RR_Aなど、様々なRRタイプに対応する具体的な構造体が定義されています。これらはDNS_RR_Headerを匿名フィールドとして埋め込むことで、共通ヘッダーを継承しています。DNS_RR_Aのaフィールドには"ipv4"タグが付与されており、これはIPアドレスとして表示するためのヒントとして使われます。
パッキングとアンパッキングの汎用メカニズム
このコミットの核心は、リフレクションを利用した汎用的なパッキング/アンパッキング関数です。
PackDomainName(s string, msg *[]byte, off int) (off1 int, ok bool): ドメイン名をDNSワイヤーフォーマット(長さバイトと文字列のシーケンス、末尾にゼロバイト)に変換し、バイトスライスmsgに書き込みます。ドメイン名の圧縮ポインタ(メッセージ内の他の場所への参照)はここでは扱わず、完全な形式で書き込みます。UnpackDomainName(msg *[]byte, off int) (s string, off1 int, ok bool): バイトスライスmsgからDNSワイヤーフォーマットのドメイン名を読み取り、Goの文字列に変換します。この関数は、DNSメッセージの圧縮ポインタ(0xC0で始まるバイト)を適切に処理し、ループを検出するためのポインタ追跡制限(10回まで)も実装しています。PackStructValue(val reflect.StructValue, msg *[]byte, off int) (off1 int, ok bool):reflect.StructValueを受け取り、そのフィールドを再帰的にバイトスライスmsgにパッキングします。uint16,uint32,string, およびネストされた構造体をサポートします。string型の場合、構造体タグ("domain-name"または空文字列)に基づいて、ドメイン名形式またはカウント付き文字列形式でパッキングします。UnpackStructValue(val reflect.StructValue, msg *[]byte, off int) (off1 int, ok bool):reflect.StructValueを受け取り、バイトスライスmsgからフィールドの値をアンパッキングします。PackStructValueと同様に、フィールドの型とタグに基づいて適切なアンパッキングロジックを適用します。PackStruct(any interface{}, msg *[]byte, off int) (off1 int, ok bool)/UnpackStruct(any interface{}, msg *[]byte, off int) (off1 int, ok bool):interface{}を受け取り、内部でreflect.NewValueを使ってreflect.StructValueに変換し、PackStructValue/UnpackStructValueを呼び出すラッパー関数です。PrintStructValue(val reflect.StructValue) string/PrintStruct(any interface{}) string: デバッグ目的で構造体の内容を文字列として整形して出力する汎用関数です。"ipv4"タグが付与されたuint32フィールドはIPアドレス形式で表示されます。
DNSメッセージの組み立てと解析 (DNS_Msg)
DNS_Msg構造体: DNSメッセージ全体をGoの構造体として表現します。DNS_Msg_Top(ヘッダーの解析済みフラグ)、question、answer、ns(ネームサーバー)、extra(追加情報)の各セクションをスライスとして持ちます。Pack() (msg *[]byte, ok bool)メソッド:DNS_Msg構造体の内容をDNSワイヤーフォーマットのバイトスライスに変換します。DNS_Headerのフラグを適切に設定し、各セクションのレコード数を計算した後、PackStructを呼び出してバイト列を生成します。メッセージサイズは固定で2000バイトを確保していますが、これは「非効率だが、高速である必要はない」という設計思想に基づいています。Unpack(msg *[]byte) boolメソッド: DNSワイヤーフォーマットのバイトスライスをDNS_Msg構造体に解析します。まずヘッダーをアンパッキングし、その情報に基づいて各セクションのレコード数を取得します。その後、各セクションのレコードをループでアンパッキングしていきます。リソースレコード(RR)のアンパッキングにはUnpackRR関数が使われます。UnpackRR(msg *[]byte, off int) (rr DNS_RR, off1 int, ok bool): リソースレコードをアンパッキングするための関数です。まずDNS_RR_HeaderをアンパッキングしてRRタイプとデータ長を取得し、そのタイプに対応する適切なDNS_RR構造体(rr_mkマップから取得)を生成して、再度アンパッキングを行います。String() stringメソッド:DNS_Msg構造体の内容を人間が読みやすい形式の文字列として出力します。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は以下の2点です。
-
src/lib/net/Makefileの変更:dnsmsg.goがビルド対象に追加されました。これにより、dnsmsg.goで定義された機能がnetパッケージの一部としてコンパイルされ、利用可能になります。- 具体的には、
gobuild -m ... dnsmsg.goの行と、O1変数およびa1ターゲットにdnsmsg.$Oが追加されています。
--- a/src/lib/net/Makefile +++ b/src/lib/net/Makefile @@ -3,7 +3,7 @@ # license that can be found in the LICENSE file. # DO NOT EDIT. Automatically generated by gobuild. -# gobuild -m fd_darwin.go fd.go net.go net_darwin.go ip.go >Makefile +# gobuild -m fd_darwin.go fd.go net.go net_darwin.go ip.go dnsmsg.go >Makefile O=6 GC=$(O)g CC=$(O)c -w @@ -34,6 +34,7 @@ coverage: packages O1=\ fd_$(GOOS).$O\ ip.$O\ + dnsmsg.$O\ O2=\ fd.$O\ @@ -45,7 +46,7 @@ O3=\ net.a: a1 a2 a3 a1: $(O1)\ - $(AR) grc net.a fd_$(GOOS).$O ip.$O + $(AR) grc net.a fd_$(GOOS).$O ip.$O dnsmsg.$O rm -f $(O1) a2: $(O2) -
src/lib/net/dnsmsg.goの新規追加:- このファイルが新規に作成され、DNSメッセージの構造体定義、パッキング/アンパッキングロジック、およびDNSメッセージ全体の組み立て/解析ロジックが約680行にわたって実装されています。
- このファイルが、DNSメッセージ処理のすべての機能を提供します。
--- /dev/null +++ b/src/lib/net/dnsmsg.go @@ -0,0 +1,683 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// DNS packet assembly. +// +// This is intended to support name resolution during net.Dial. +// It doesn't have to be blazing fast. +// +// Rather than write the usual handful of routines to pack and +// unpack every message that can appear on the wire, we use +// reflection to write a generic pack/unpack for structs and then +// use it. Thus, if in the future we need to define new message +// structs, no new pack/unpack/printing code needs to be written. +// +// The first half of this file defines the DNS message formats. +// The second half implements the conversion to and from wire format. +// A few of the structure elements have string tags to aid the +// generic pack/unpack routines. +// +// TODO(rsc) There are enough names defined in this file that they're all +// prefixed with DNS_. Perhaps put this in its own package later. + +package net // ... (以下、683行のコードが続く)
コアとなるコードの解説
src/lib/net/dnsmsg.goは、Go言語でDNSメッセージを扱うための基盤を提供します。その主要な機能は以下の通りです。
1. DNSメッセージ構造体の定義
DNSプロトコルの仕様に基づき、ヘッダー、質問、リソースレコードなどの各要素に対応するGoの構造体が定義されています。
DNS_Header: DNSメッセージの固定長ヘッダー(12バイト)を表現します。id: トランザクションID。bits: フラグフィールド。クエリ/応答、権威、切り詰め、再帰要求、再帰利用可能、応答コードなどの情報がビット単位で格納されます。qdcount,ancount,nscount,arcount: それぞれ質問、応答、権威、追加情報セクションに含まれるレコードの数。
DNS_Question: DNSクエリの質問部分を表現します。name: 問い合わせるドメイン名。"domain-name"タグにより、特殊なエンコーディングが適用されます。qtype: クエリタイプ(例: Aレコード、MXレコードなど)。qclass: クエリクラス(例: INET)。
DNS_RR_Header: すべてのリソースレコードに共通するヘッダー部分を表現します。name: リソースレコードのドメイン名。rrtype: リソースレコードのタイプ。class: リソースレコードのクラス。ttl: Time To Live(キャッシュ期間)。rdlength: RDATA(リソースデータ)の長さ。
DNS_RRインターフェース:Header() *DNS_RR_Headerメソッドを持つインターフェースで、様々なリソースレコード型を統一的に扱えるようにします。- 具体的な
DNS_RR_型:DNS_RR_A(IPv4アドレス),DNS_RR_CNAME(正規名),DNS_RR_MX(メール交換) など、各RRタイプに対応する構造体が定義されています。これらはDNS_RR_Headerを匿名フィールドとして埋め込むことで、共通ヘッダーを再利用しています。
2. ドメイン名のパッキングとアンパッキング
DNSメッセージにおいて、ドメイン名は特殊な形式でエンコードされます。
PackDomainName: ドメイン名を「長さバイト + 文字列」のシーケンスとしてバイト列に変換します。各セグメント(ドットで区切られた部分)の前にそのセグメントの長さが付き、最後にゼロバイトで終端されます。UnpackDomainName: バイト列からドメイン名を読み取ります。この関数は、DNSメッセージの圧縮ポインタ(メッセージ内の他の場所への参照)を処理する能力を持ちます。これにより、繰り返し現れるドメイン名の部分を効率的に表現できます。ポインタの無限ループを防ぐための対策も含まれています。
3. 汎用的な構造体のパッキングとアンパッキング(リフレクションの活用)
このコミットの最も特徴的な部分は、Goのリフレクション機能を使って、任意の構造体をバイト列に変換(パッキング)したり、バイト列から構造体に復元(アンパッキング)したりする汎用的なメカニズムを実装している点です。
PackStructValue/UnpackStructValue:- これらの関数は
reflect.StructValueを受け取り、構造体の各フィールドを走査します。 - フィールドの
Kind()(型)をチェックし、uint16,uint32,string,structなどの型に応じて適切なパッキング/アンパッキングロジックを適用します。 - 特に
string型の場合、構造体タグ(例:name string "domain-name")を読み取り、"domain-name"タグがあればPackDomainName/UnpackDomainNameを呼び出し、タグがなければ通常のカウント付き文字列として処理します。 uint16やuint32はネットワークバイトオーダー(ビッグエンディアン)でバイト列に変換されます。
- これらの関数は
PackStruct/UnpackStruct:interface{}を受け取り、内部でリフレクションを使ってPackStructValue/UnpackStructValueを呼び出すラッパー関数です。これにより、任意の構造体を引数として渡せるようになります。
この汎用的なアプローチにより、将来的に新しいDNSレコードタイプが追加された場合でも、新しい構造体を定義するだけで、既存のパッキング/アンパッキングコードを再利用できるという大きなメリットがあります。
4. DNSメッセージ全体の組み立てと解析 (DNS_Msgのメソッド)
DNS_Msg.Pack():DNS_Msg構造体の内容を元に、完全なDNSメッセージのバイト列を生成します。ヘッダーのフラグやカウントを設定し、質問、応答、権威、追加情報セクションの各レコードを順番にパッキングしていきます。DNS_Msg.Unpack(): 受信したDNSメッセージのバイト列を解析し、DNS_Msg構造体に格納します。まずヘッダーを解析し、その情報に基づいて各セクションのレコード数を把握し、それぞれのセクションをループでアンパッキングしていきます。リソースレコードのアンパッキングには、前述のUnpackRR関数が利用されます。
5. デバッグ用ユーティリティ
PrintStructValue/PrintStruct: 構造体の内容を人間が読みやすい形式で出力するための関数です。特に"ipv4"タグが付与されたuint32フィールドは、IPアドレス形式(例:192.168.1.1)で表示されるようにフォーマットされます。これはデバッグ時に非常に役立ちます。
これらの機能が組み合わさることで、Go言語のnetパッケージはDNSプロトコルを介した名前解決を内部的に実行できるようになり、より高レベルなネットワーク機能の実現に貢献しています。
関連リンク
- RFC 1034 - Domain Names - Concepts and Facilities
- RFC 1035 - Domain Names - Implementation and Specification
- Go言語のreflectパッケージ (現在のドキュメント)
参考にした情報源リンク
- Go言語の公式ドキュメント (特に
reflectパッケージに関する情報) - DNSプロトコルに関するRFCドキュメント (RFC 1034, RFC 1035)
- 一般的なDNSの仕組みに関する技術解説記事# [インデックス 1321] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnetパッケージにDNS(Domain Name System)メッセージの組み立てと解析機能を追加するものです。特に、net.Dialにおける名前解決をサポートすることを目的としています。リフレクションを活用して、DNSメッセージの構造体から汎用的にパッキング(バイト列への変換)およびアンパッキング(バイト列からの構造体への変換)を行うメカニズムが導入されています。
コミット
commit b927ad8835a163939f91f86fb0b732c505f94f29
Author: Russ Cox <rsc@golang.org>
Date: Wed Dec 10 17:17:59 2008 -0800
DNS messages
R=r
DELTA=685 (683 added, 0 deleted, 2 changed)
OCL=20926
CL=20951
---
src/lib/net/Makefile | 5 +-
src/lib/net/dnsmsg.go | 683 ++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 686 insertions(+), 2 deletions(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b927ad8835a163939f91f86fb0b732c505f94f29
元コミット内容
DNS messages
R=r
DELTA=685 (683 added, 0 deleted, 2 changed)
OCL=20926
CL=20951
変更の背景
このコミットの主な背景は、Go言語のネットワーク機能において、DNSによる名前解決を内部的にサポートする必要があったことです。特に、net.Dialのような関数がドメイン名をIPアドレスに変換する際に、DNSプロトコルを介して通信を行うための基盤が求められました。
当時のGo言語はまだ開発の初期段階であり、ネットワークスタックの基本的な部分が構築されている最中でした。DNSメッセージのパースと生成は、ネットワーク通信において不可欠な要素であり、この機能が追加されることで、Goプログラムがより複雑なネットワーク操作を実行できるようになります。
コミットメッセージにある「It is intended to support name resolution during net.Dial. It doesn't have to be blazing fast.」という記述から、この実装がパフォーマンスよりも正確性と機能性を重視した初期段階のものであることが伺えます。また、「Rather than write the usual handful of routines to pack and unpack every message that can appear on the wire, we use reflection to write a generic pack/unpack for structs and then use it.」という記述は、将来的な拡張性を見越して、リフレクションを用いた汎用的なアプローチが採用されたことを示しています。
前提知識の解説
DNS (Domain Name System)
DNSは、インターネット上のコンピュータやサービスを識別するための階層的な分散型命名システムです。人間が覚えやすいドメイン名(例: google.com)を、コンピュータが理解できるIPアドレス(例: 172.217.160.142)に変換する役割を担っています。DNSはUDPポート53番を使用し、クライアント(リゾルバ)からのクエリに対して、DNSサーバーが応答を返します。
DNSメッセージは、ヘッダー、質問セクション、応答セクション、権威セクション、追加情報セクションの5つの主要な部分で構成されます。
- ヘッダー (Header): メッセージの基本的な情報(ID、フラグ、セクションのレコード数など)を含みます。
- 質問セクション (Question Section): クエリの内容(問い合わせるドメイン名、タイプ、クラスなど)を含みます。
- 応答セクション (Answer Section): 質問に対する回答(リソースレコード、RR)を含みます。
- 権威セクション (Authority Section): 権威のあるネームサーバーの情報を含みます。
- 追加情報セクション (Additional Section): 関連する追加情報(例: MXレコードに対するAレコード)を含みます。
DNSリソースレコード(RR)には、Aレコード(IPv4アドレス)、AAAAレコード(IPv6アドレス)、CNAMEレコード(正規名)、MXレコード(メール交換)、NSレコード(ネームサーバー)、PTRレコード(ポインタ)、SOAレコード(権限の開始)など、様々なタイプがあります。
Go言語のリフレクション (reflectパッケージ)
Go言語のreflectパッケージは、実行時にプログラムの構造(型、フィールド、メソッドなど)を検査し、操作するための機能を提供します。これにより、コンパイル時には型が不明なデータ構造を扱う汎用的なコードを書くことが可能になります。
このコミットでは、reflectパッケージを以下のように利用しています。
- 構造体のパッキング/アンパッキング: DNSメッセージの様々な構造体(
DNS_Header,DNS_Question,DNS_RR_Headerなど)を、そのフィールドの型やタグ情報に基づいて、バイト列に変換したり、バイト列から構造体に復元したりする汎用的な関数(PackStructValue,UnpackStructValue)を実装しています。 - フィールドの型に応じた処理:
reflect.Kind()を使ってフィールドの型(Uint16Kind,Uint32Kind,StringKind,StructKindなど)を判別し、それぞれに応じたバイト列への変換ロジックを適用しています。 - 構造体タグの利用: 構造体のフィールドに付与されたタグ(例:
name string "domain-name")を利用して、特定のエンコーディングルール(例: ドメイン名形式)を適用しています。
リフレクションは強力な機能ですが、実行時のオーバーヘッドがあるため、パフォーマンスが厳しく求められる場面では注意が必要です。しかし、このコミットの目的が「blazing fast」ではないと明記されていることから、汎用性と拡張性を優先した設計判断と言えます。
技術的詳細
dnsmsg.goファイルは、DNSメッセージのワイヤーフォーマット(ネットワーク上を流れるバイト列の形式)とGo言語の構造体との間の変換を扱うためのコードを含んでいます。
DNSメッセージのデータ構造定義
ファイルの前半では、DNSメッセージの様々な部分に対応するGoの構造体が定義されています。
DNS_Header: DNSメッセージのヘッダー部分を表します。id,bits(フラグ)、qdcount,ancount,nscount,arcount(各セクションのレコード数)などのフィールドを持ちます。DNS_Question: 質問セクションの単一の質問を表します。name(ドメイン名)、qtype(クエリタイプ)、qclass(クエリクラス)を持ちます。nameフィールドには"domain-name"という構造体タグが付与されており、これがドメイン名特有のエンコーディング(後述)を指示します。DNS_RR_Header: リソースレコード(RR)の共通ヘッダー部分を表します。name,rrtype,class,ttl,rdlength(データ部の長さ)を持ちます。DNS_RRインターフェース: すべてのリソースレコードが実装すべきインターフェースで、Header() *DNS_RR_Headerメソッドを持ちます。- 各種
DNS_RR_構造体:DNS_RR_CNAME,DNS_RR_HINFO,DNS_RR_MX,DNS_RR_Aなど、様々なRRタイプに対応する具体的な構造体が定義されています。これらはDNS_RR_Headerを匿名フィールドとして埋め込むことで、共通ヘッダーを継承しています。DNS_RR_Aのaフィールドには"ipv4"タグが付与されており、これはIPアドレスとして表示するためのヒントとして使われます。
パッキングとアンパッキングの汎用メカニズム
このコミットの核心は、リフレクションを利用した汎用的なパッキング/アンパッキング関数です。
PackDomainName(s string, msg *[]byte, off int) (off1 int, ok bool): ドメイン名をDNSワイヤーフォーマット(長さバイトと文字列のシーケンス、末尾にゼロバイト)に変換し、バイトスライスmsgに書き込みます。ドメイン名の圧縮ポインタ(メッセージ内の他の場所への参照)はここでは扱わず、完全な形式で書き込みます。UnpackDomainName(msg *[]byte, off int) (s string, off1 int, ok bool): バイトスライスmsgからDNSワイヤーフォーマットのドメイン名を読み取り、Goの文字列に変換します。この関数は、DNSメッセージの圧縮ポインタ(0xC0で始まるバイト)を適切に処理し、ループを検出するためのポインタ追跡制限(10回まで)も実装しています。PackStructValue(val reflect.StructValue, msg *[]byte, off int) (off1 int, ok bool):reflect.StructValueを受け取り、そのフィールドを再帰的にバイトスライスmsgにパッキングします。uint16,uint32,string, およびネストされた構造体をサポートします。string型の場合、構造体タグ("domain-name"または空文字列)に基づいて、ドメイン名形式またはカウント付き文字列形式でパッキングします。UnpackStructValue(val reflect.StructValue, msg *[]byte, off int) (off1 int, ok bool):reflect.StructValueを受け取り、バイトスライスmsgからフィールドの値をアンパッキングします。PackStructValueと同様に、フィールドの型とタグに基づいて適切なアンパッキングロジックを適用します。PackStruct(any interface{}, msg *[]byte, off int) (off1 int, ok bool)/UnpackStruct(any interface{}, msg *[]byte, off int) (off1 int, ok bool):interface{}を受け取り、内部でreflect.NewValueを使ってreflect.StructValueに変換し、PackStructValue/UnpackStructValueを呼び出すラッパー関数です。PrintStructValue(val reflect.StructValue) string/PrintStruct(any interface{}) string: デバッグ目的で構造体の内容を文字列として整形して出力する汎用関数です。"ipv4"タグが付与されたuint32フィールドはIPアドレス形式で表示するためのヒントとして使われます。
DNSメッセージの組み立てと解析 (DNS_Msg)
DNS_Msg構造体: DNSメッセージ全体をGoの構造体として表現します。DNS_Msg_Top(ヘッダーの解析済みフラグ)、question、answer、ns(ネームサーバー)、extra(追加情報)の各セクションをスライスとして持ちます。Pack() (msg *[]byte, ok bool)メソッド:DNS_Msg構造体の内容をDNSワイヤーフォーマットのバイトスライスに変換します。DNS_Headerのフラグを適切に設定し、各セクションのレコード数を計算した後、PackStructを呼び出してバイト列を生成します。メッセージサイズは固定で2000バイトを確保していますが、これは「非効率だが、高速である必要はない」という設計思想に基づいています。Unpack(msg *[]byte) boolメソッド: DNSワイヤーフォーマットのバイトスライスをDNS_Msg構造体に解析します。まずヘッダーをアンパッキングし、その情報に基づいて各セクションのレコード数を取得します。その後、各セクションのレコードをループでアンパッキングしていきます。リソースレコード(RR)のアンパッキングにはUnpackRR関数が使われます。UnpackRR(msg *[]byte, off int) (rr DNS_RR, off1 int, ok bool): リソースレコードをアンパッキングするための関数です。まずDNS_RR_HeaderをアンパッキングしてRRタイプとデータ長を取得し、そのタイプに対応する適切なDNS_RR構造体(rr_mkマップから取得)を生成して、再度アンパッキングを行います。String() stringメソッド:DNS_Msg構造体の内容を人間が読みやすい形式の文字列として出力します。
これらの機能が組み合わさることで、Go言語のnetパッケージはDNSプロトコルを介した名前解決を内部的に実行できるようになり、より高レベルなネットワーク機能の実現に貢献しています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は以下の2点です。
-
src/lib/net/Makefileの変更:dnsmsg.goがビルド対象に追加されました。これにより、dnsmsg.goで定義された機能がnetパッケージの一部としてコンパイルされ、利用可能になります。- 具体的には、
gobuild -m ... dnsmsg.goの行と、O1変数およびa1ターゲットにdnsmsg.$Oが追加されています。
--- a/src/lib/net/Makefile +++ b/src/lib/net/Makefile @@ -3,7 +3,7 @@ # license that can be found in the LICENSE file. # DO NOT EDIT. Automatically generated by gobuild. -# gobuild -m fd_darwin.go fd.go net.go net_darwin.go ip.go >Makefile +# gobuild -m fd_darwin.go fd.go net.go net_darwin.go ip.go dnsmsg.go >Makefile O=6 GC=$(O)g CC=$(O)c -w @@ -34,6 +34,7 @@ coverage: packages O1=\ fd_$(GOOS).$O\ ip.$O\ + dnsmsg.$O\ O2=\ fd.$O\ @@ -45,7 +46,7 @@ O3=\ net.a: a1 a2 a3 a1: $(O1)\ - $(AR) grc net.a fd_$(GOOS).$O ip.$O + $(AR) grc net.a fd_$(GOOS).$O ip.$O dnsmsg.$O rm -f $(O1) a2: $(O2) -
src/lib/net/dnsmsg.goの新規追加:- このファイルが新規に作成され、DNSメッセージの構造体定義、パッキング/アンパッキングロジック、およびDNSメッセージ全体の組み立て/解析ロジックが約680行にわたって実装されています。
- このファイルが、DNSメッセージ処理のすべての機能を提供します。
--- /dev/null +++ b/src/lib/net/dnsmsg.go @@ -0,0 +1,683 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// DNS packet assembly. +// +// This is intended to support name resolution during net.Dial. +// It doesn't have to be blazing fast. +// +// Rather than write the usual handful of routines to pack and +// unpack every message that can appear on the wire, we use +// reflection to write a generic pack/unpack for structs and then +// use it. Thus, if in the future we need to define new message +// structs, no new pack/unpack/printing code needs to be written. +// +// The first half of this file defines the DNS message formats. +// The second half implements the conversion to and from wire format. +// A few of the structure elements have string tags to aid the +// generic pack/unpack routines. +// +// TODO(rsc) There are enough names defined in this file that they're all +// prefixed with DNS_. Perhaps put this in its own package later. + +package net // ... (以下、683行のコードが続く)
コアとなるコードの解説
src/lib/net/dnsmsg.goは、Go言語でDNSメッセージを扱うための基盤を提供します。その主要な機能は以下の通りです。
1. DNSメッセージ構造体の定義
DNSプロトコルの仕様に基づき、ヘッダー、質問、リソースレコードなどの各要素に対応するGoの構造体が定義されています。
DNS_Header: DNSメッセージの固定長ヘッダー(12バイト)を表現します。id: トランザクションID。bits: フラグフィールド。クエリ/応答、権威、切り詰め、再帰要求、再帰利用可能、応答コードなどの情報がビット単位で格納されます。qdcount,ancount,nscount,arcount: それぞれ質問、応答、権威、追加情報セクションに含まれるレコードの数。
DNS_Question: DNSクエリの質問部分を表現します。name: 問い合わせるドメイン名。"domain-name"タグにより、特殊なエンコーディングが適用されます。qtype: クエリタイプ(例: Aレコード、MXレコードなど)。qclass: クエリクラス(例: INET)。
DNS_RR_Header: すべてのリソースレコードに共通するヘッダー部分を表現します。name: リソースレコードのドメイン名。rrtype: リソースレコードのタイプ。class: リソースレコードのクラス。ttl: Time To Live(キャッシュ期間)。rdlength: RDATA(リソースデータ)の長さ。
DNS_RRインターフェース:Header() *DNS_RR_Headerメソッドを持つインターフェースで、様々なリソースレコード型を統一的に扱えるようにします。- 具体的な
DNS_RR_型:DNS_RR_A(IPv4アドレス),DNS_RR_CNAME(正規名),DNS_RR_MX(メール交換) など、各RRタイプに対応する構造体が定義されています。これらはDNS_RR_Headerを匿名フィールドとして埋め込むことで、共通ヘッダーを再利用しています。
2. ドメイン名のパッキングとアンパッキング
DNSメッセージにおいて、ドメイン名は特殊な形式でエンコードされます。
PackDomainName: ドメイン名を「長さバイト + 文字列」のシーケンスとしてバイト列に変換します。各セグメント(ドットで区切られた部分)の前にそのセグメントの長さが付き、最後にゼロバイトで終端されます。UnpackDomainName: バイト列からドメイン名を読み取ります。この関数は、DNSメッセージの圧縮ポインタ(メッセージ内の他の場所への参照)を処理する能力を持ちます。これにより、繰り返し現れるドメイン名の部分を効率的に表現できます。ポインタの無限ループを防ぐための対策も含まれています。
3. 汎用的な構造体のパッキングとアンパッキング(リフレクションの活用)
このコミットの最も特徴的な部分は、Goのリフレクション機能を使って、任意の構造体をバイト列に変換(パッキング)したり、バイト列から構造体に復元(アンパッキング)したりする汎用的なメカニズムを実装している点です。
PackStructValue/UnpackStructValue:- これらの関数は
reflect.StructValueを受け取り、構造体の各フィールドを走査します。 - フィールドの
Kind()(型)をチェックし、uint16,uint32,string,structなどの型に応じて適切なパッキング/アンパッキングロジックを適用します。 - 特に
string型の場合、構造体タグ(例:name string "domain-name")を読み取り、"domain-name"タグがあればPackDomainName/UnpackDomainNameを呼び出し、タグがなければ通常のカウント付き文字列として処理します。 uint16やuint32はネットワークバイトオーダー(ビッグエンディアン)でバイト列に変換されます。
- これらの関数は
PackStruct/UnpackStruct:interface{}を受け取り、内部でリフレクションを使ってPackStructValue/UnpackStructValueを呼び出すラッパー関数です。これにより、任意の構造体を引数として渡せるようになります。
この汎用的なアプローチにより、将来的に新しいDNSレコードタイプが追加された場合でも、新しい構造体を定義するだけで、既存のパッキング/アンパッキングコードを再利用できるという大きなメリットがあります。
4. DNSメッセージ全体の組み立てと解析 (DNS_Msgのメソッド)
DNS_Msg.Pack():DNS_Msg構造体の内容を元に、完全なDNSメッセージのバイト列を生成します。ヘッダーのフラグやカウントを設定し、質問、応答、権威、追加情報セクションの各レコードを順番にパッキングしていきます。DNS_Msg.Unpack(): 受信したDNSメッセージのバイト列を解析し、DNS_Msg構造体に格納します。まずヘッダーを解析し、その情報に基づいて各セクションのレコード数を把握し、それぞれのセクションをループでアンパッキングしていきます。リソースレコードのアンパッキングには、前述のUnpackRR関数が利用されます。
5. デバッグ用ユーティリティ
PrintStructValue/PrintStruct: 構造体の内容を人間が読みやすい形式で出力するための関数です。特に"ipv4"タグが付与されたuint32フィールドは、IPアドレス形式(例:192.168.1.1)で表示されるようにフォーマットされます。これはデバッグ時に非常に役立ちます。
これらの機能が組み合わさることで、Go言語のnetパッケージはDNSプロトコルを介した名前解決を内部的に実行できるようになり、より高レベルなネットワーク機能の実現に貢献しています。
関連リンク
- RFC 1034 - Domain Names - Concepts and Facilities
- RFC 1035 - Domain Names - Implementation and Specification
- Go言語のreflectパッケージ (現在のドキュメント)
参考にした情報源リンク
- Go言語の公式ドキュメント (特に
reflectパッケージに関する情報) - DNSプロトコルに関するRFCドキュメント (RFC 1034, RFC 1035)
- 一般的なDNSの仕組みに関する技術解説記事