DNS サーバのステータスを確認する試み

# まとめ

- 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


# ソースコード
- 以下が作成したスクリプトです
- 手続的なコードですがご容赦ください

````dnsping.rb
#!/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
````

__おしまい__