ネームサーバーの動作確認をRubyでしたい件の続き。以前の記事で、Rubyのresolvライブラリを用いて名前解決できるようになった。しかし、取得した結果が期待値と一致するか調べるには、手元でも文字列からインスタンスを作成しなければならない。
初めは取得した結果を文字列化して比較しようとしたが、AAAAレコード(IPv6)の比較で早速つまづいた。他のレコードも文字列化すると、空白の数など表記揺れで失敗しかねない。
コンストラクタに引数を与えればいいだけではあるが、文字列のまま与えると適切なクラスに変換されず比較失敗することがあるので、適宜対処しなければならない。
(ちなみにTTLはインスタンス同士の比較に使われない)
コード
扱ったことのあるレコード種別のみ。文字列も規定されたパターンを全て解釈できるわけではなく、よく使うパターンだけ対応させている。
require 'resolv'
def create_resource(type, value)
type = type.upcase.to_sym
klass = Resolv::DNS::Resource::IN.const_get(type)
case type
when :A, :AAAA
klass.new(value)
when :CNAME, :NS, :PTR
klass.new(Resolv::DNS::Name.create(value))
when :MX, :SRV
*num_list, name = value.split
klass.new(*num_list.map!(&:to_i),
Resolv::DNS::Name.create(name))
when :SOA
mname, rname, *num_list = value.tr("()", " ").split
klass.new(Resolv::DNS::Name.create(mname),
Resolv::DNS::Name.create(rname),
*num_list.map!(&:to_i))
when :TXT, :SPF
klass.new(value.gsub(/^"|"$/, ""))
else
raise ArgumentError, "not implemented yet"
end
end
create_resource(:A, '192.0.2.1')
# => #<Resolv::DNS::Resource::IN::A:0x... @address=#<Resolv::IPv4 192.0.2.1>>
create_resource(:AAAA, '2001:0db8:0000:0000:0000:0000:0000:0001')
# => #<Resolv::DNS::Resource::IN::AAAA:0x... @address=#<Resolv::IPv6 2001:DB8::1>>
create_resource(:CNAME, 'example.com.')
# => #<Resolv::DNS::Resource::IN::CNAME:0x... @name=#<Resolv::DNS::Name: example.com.>>
create_resource(:NS, 'example.com.')
# => #<Resolv::DNS::Resource::IN::NS:0x... @name=#<Resolv::DNS::Name: example.com.>>
create_resource(:PTR, 'example.com.')
# => #<Resolv::DNS::Resource::IN::PTR:0x... @name=#<Resolv::DNS::Name: example.com.>>
create_resource(:MX, '10 example.com.')
# => #<Resolv::DNS::Resource::IN::MX:0x... @preference=10, @exchange=#<Resolv::DNS::Name: example.com.>>
create_resource(:SRV, '1 0 21 example.com.')
# => #<Resolv::DNS::Resource::IN::SRV:0x... @priority=1, @weight=0, @port=21, @target=#<Resolv::DNS::Name: example.com.>>
create_resource(:SOA, <<EOS)
ns1.example.com. root.example.com. (
1
7200
900
1209600
86400 )
EOS
# => #<Resolv::DNS::Resource::IN::SOA:0x... @mname=#<Resolv::DNS::Name: ns1.example.com.>, @rname=#<Resolv::DNS::Name: root.example.com.>, @serial=1, @refresh=7200, @retry=900, @expire=1209600, @minimum=86400>
create_resource(:TXT, '"v=spf1 +ip4:192.168.100.0/24 ~all"')
# => #<Resolv::DNS::Resource::IN::TXT:0x... @strings=["v=spf1 +ip4:192.168.100.0/24 ~all"]>
create_resource(:SPF, '"v=spf1 +ip4:192.168.100.0/24 ~all"')
# => NameError: uninitialized constant Resolv::DNS::Resource::IN::SPF
最後のSPFレコードについては次節を参照。
詳細
case
でレコード毎に処理を変えているが、それぞれ何のための処理なのかを簡単に記す。
A, AAAA レコード
IPアドレス(IPv4, IPv6)を表すレコード。コンストラクタに文字列のまま与えても、内部で Resolv::IPv[46]
に変換してくれる。親切。
IPアドレスくらいなら文字列比較でもいけそうに思えるが、IPv6には様々な省略ルールがあるため文字列比較は非常に難しい。上記の実験では結果が最大限に短縮されている上、アルファベットも大文字に変わっている。
CNAME, NS, PTR レコード
ドメイン名を表すレコード。これらのクラスは Resolv::DNS::Resource::DomainName
を親に持つ。コンストラクタに Resolv::DNS::Name
を与えないといけない(文字列から自動変換されない)。
MX, SRV レコード
ドメイン名だけでなく数値も持つレコード。これも、ドメイン名は Resolv::DNS::Name
で、数値は Integer
で与える必要がある。引数の順序はDNSに設定する文字列と同じなので、コードではsplatを利用して簡略化している。
SOA レコード
括弧を用いて複数行で書くことがあるので、事前にスペースに変換してから区切っている。引数の数は多いけれども、これも文字列の順番通りなのでSRVレコードと似た対応でいい。
TXT, SPF レコード
任意の文字列を持てるレコード。DNSに設定するときはダブルクォートで囲むが、 Resolv::DNS
の結果ではダブルクォートが含まれないので、事前に取っておく。
なお、resolvライブラリはまだSPFレコードのクラスを持たないので、上の実験ではエラーになった。issueにパッチが書かれているので、それを適用すればひとまず使えるようになる。
https://bugs.ruby-lang.org/issues/11312