まとめ
-
DNS サーバを監視する必要があり、スクリプトを作成することにしました
-
dig など、既存のコマンドを使うこともできましたが、DNS の勉強を兼ねて自分で実装することにしました
-
Ruby の勉強も兼ねて、プログラムは Ruby で作成することにしました
-
RFC 1035 によると、DNS リクエスト・ヘッダの OPCODE フィールドに 2 を指定すると DNS サーバのステータスが確認できるようです
-
これを利用して DNS サーバのステータス・モニタリングができるか試してみました
-
スクリプトは問題なく完成しましたが、狙っていた結果(DNS サーバのステータスの確認)は得られませんでした
-
DNS サーバ側で OPCODE=2 のリクエストへの応答が実装されていない感じでした(応答ヘッダの RCODE が 4 (= "Not Implemented") でした)
-
DNS サーバの監視は、ダミーのリクエストを投げて、その応答の中の RCODE を確認するのが良いのかもしれません(いずれ試してみたいと思います)
-
以下は今回作成したスクリプトと実装メモです
-
内容に間違いがあったらごめんなさい
参考情報
- https://tools.ietf.org/html/rfc1035
- http://www.eric-a-hall.com/specs/draft-hall-status-opcode-00-1.txt
ソースコード
- 以下が作成したスクリプトです
- 手続的なコードですがご容赦ください
# !/usr/bin/ruby
require 'socket'
if ARGV.size == 0
puts "Usage: ./dnsping.rb <DNS Server IP Address>"
exit 1
end
SERV = ARGV[0] # DNS Server Address
PORT = 53 # DNS Port
LEN = 512 # maxlen bytes
# Generate DNS Request
# for a server status request, we only need a header data
# header format is described in RFC 1035
def dns_request()
id = 1
qr = 0 << 15 # qr = 0 => query (not response)
opcode = 2 << 11 # opcode = 2 => server status request
aa = 0 << 10
tc = 0 << 9
rd = 0 << 8
ra = 0 << 7
z = 0 << 4
rcode = 0 << 0
qdcount = 0
ancount = 0
nscount = 0
arcount = 0
f1 = id
f2 = (qr + opcode + aa + tc + rd + ra + z + rcode)
f3 = qdcount
f4 = ancount
f5 = nscount
f6 = arcount
# 'n*' converts 16bit unsigned array to byte array in network byte order
header = [f1, f2, f3, f5, f5, f6].pack('n*')
return header
end
def main()
# create DNS request message
req = dns_request()
# display DNS request message
print " Request: "
p req
# create socket
sock = UDPSocket.new
sock.connect(SERV, PORT)
# send the query
sock.send req, 0
# receive response
data = sock.recvfrom(LEN)
# display received data
# data[0] => received data
print " Response: "
p data[0]
# display response code
# lower 4 bytes of the 4th octet in received data is the response code
print " RCODE: "
p (data[0].unpack('C*')[3] & 0b1111)
end
main()
ソースコードの説明
# !/usr/bin/ruby
require 'socket'
if ARGV.size == 0
puts "Usage: ./dnsping.rb <DNS Server IP Address>"
exit 1
end
-
サーバのアドレスはスクリプトの第一引数で指定します
-
DNS サーバのポートは 53 番です
-
LEN は DNS サーバからの応答パケットの最大サイズです
-
実際の応答パケットのサイズは 96byte でした(DNS のヘッダサイズが 96byte)
SERV = ARGV[0] # DNS Server Address
PORT = 53 # DNS Port
LEN = 512 # maxlen bytes
-
DNS のリクエスト・メッセージを作成します
-
リクエストのフォーマットは RFC 1035 に定義されています
-
図解すると、こんな感じのバイト列を作成します
フィールド名 | サイズ(バイト数) | 設定値 | 説明 |
---|---|---|---|
ID | 16 | 1 | 任意の値 |
QR | 1 | 0 | 問い合わせ |
OPCODE | 4 | 2 | ステータス確認 |
AA | 1 | 0 | 応答専用フィールド |
TC | 1 | 0 | トランケートされていない |
RD | 1 | 0 | 再帰希望しない |
RA | 1 | 0 | 再帰不可能 |
Z | 3 | 0 | 予約フィールド |
RCODE | 4 | 0 | 応答専用フィールド |
QDCOUNT | 16 | 0 | 0 => 全ての Zone, 1 => Question Section で指定した Zone |
ANCOUNT | 16 | 0 | - |
NSCOUNT | 16 | 0 | - |
ARCOUNT | 16 | 0 | - |
# Generate DNS Request
# for a server status request, we only need a header data
# header format is described in RFC 1035
def dns_request()
- ID は任意の値を設定します
id = 1
- DNS の問い合わせの際は QR = 0 に設定します
- OPCODE = 2 が DNS サーバのステータス確認です
- その他のフィールドは 0 に設定します
- 各フィールドの意味と設定すべき値は RFC 1035 で解説されています
qr = 0 << 15 # qr = 0 => query (not response)
opcode = 2 << 11 # opcode = 2 => server status request
aa = 0 << 10
tc = 0 << 9
rd = 0 << 8
ra = 0 << 7
z = 0 << 4
rcode = 0 << 0
qdcount = 0
ancount = 0
nscount = 0
arcount = 0
f1 = id
f2 = (qr + opcode + aa + tc + rd + ra + z + rcode)
f3 = qdcount
f4 = ancount
f5 = nscount
f6 = arcount
- Array#pack の書式指定に 'n*' を指定すると、16bit unsigned の配列をバイト配列に変換することができます
- バイト・オーダーはネットワーク・バイト・オーダーになります
# 'n*' converts 16bit unsigned array to byte array in network byte order
header = [f1, f2, f3, f5, f5, f6].pack('n*')
return header
end
def main()
# create DNS request message
req = dns_request()
# display DNS request message
print " Request: "
p req
- 監視スクリプトなので DNS パケットは UDP で送信することにします
# create socket
sock = UDPSocket.new
sock.connect(SERV, PORT)
# send the query
sock.send req, 0
# receive response
data = sock.recvfrom(LEN)
# display received data
# data[0] => received data
print " Response: "
p data[0]
- DNS サーバから返ってきたパケットの 4 オクテット目の下位 4byte が応答コードです
- この値が 0 ならサーバは正常に稼働しています
- ここで返される応答コードの意味は以下の通りです
応答コード | 意味 |
---|---|
0 | No Error |
1 | Format Error |
2 | Server Failure |
3 | Name Error |
4 | Not Implemented |
5 | Refused |
# display response code
# lower 4 bytes of the 4th octet in received data is the response code
print " RCODE: "
p (data[0].unpack('C*')[3] & 0b1111)
end
main()
実行結果
- 手元の DNS サーバで試してみたところ、RCODE = 4 (= "Not Implemented") が返って来ました(残念!)
$ ./dnsping.rb x.x.x.x
Request: "\x00\x01\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00"
Response: "\x00\x01\x90\x04\x00\x00\x00\x00\x00\x00\x00\x00"
RCODE: 4
おしまい