Ruby で DNS サーバを自作する

プログラマブルDNS サーバが欲しくなったので、スクリプト系言語で DNS が実装できるかどうか調べてみた。
Perl であれば、CPANNet::DNS::Server というモジュールがあるので、これを使えば簡単に DNS サーバが実装できるようだ。
また、既存の実装では DNS Balance が「Ruby で実装された DNS サーバ」だということが分かったが、コードを見たところあまり流用したくなるような内容ではなかった。
そこで、RFC 1034, RFC 1035 を読みつつ*1Ruby で自作してみることにした。
で、初版として作ったのが以下のプログラム。

require 'rubygems'
require 'Net/DNS'
require 'socket'

sock = UDPSocket.new
sock.bind('localhost', 10053)

while true
  packet, address = sock.recvfrom(1024)
  address_family, port, host, address = address

  request = Net::DNS::Packet.new_from_binary(packet)

  if request.header.opcode == 'QUERY' && request.question.size == 1
    q = request.question.first
    response = Net::DNS::Packet.new_from_values(q.qname, q.qtype, q.qclass)
    response.header.id = request.header.id
    response.header.qr = 1

    if %w(A ANY).include?(q.qtype) && %w(IN ANY).include?(q.qclass)
      response.answer << Net::DNS::RR.new_from_hash(
        :name => q.qname, :type => 'A', :class => 'IN', :ttl => 60, :address => '127.0.0.1'
      )
    end
  else
    response = Net::DNS::Packet.new
    response.header.id = request.header.id
    response.header.qr = 1
    response.header.rcode = 'NOTIMPL'
  end

  sock.send(response.data, 0, host, port)
end

A レコードの問い合わせに対して毎回 127.0.0.1 を返すようになっている。また、パケットのデコードとエンコードは、以前にも使った pnet-dns を使っている。
上のプログラムを実行して dig コマンドで問い合わせてみたら、意図どおりに 127.0.0.1 が返ってきた。どうやら最低限の実装としてはこれで良いようだ。*2

 $ dig @localhost -p 10053 www.yahoo.co.jp

; <<>> DiG 9.3.4 <<>> @localhost -p 10053 www.yahoo.co.jp
; (1 server found)
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 39190
;; flags: qr rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;www.yahoo.co.jp.               IN      A

;; ANSWER SECTION:
www.yahoo.co.jp.        60      IN      A       127.0.0.1

;; Query time: 94 msec
;; SERVER: 127.0.0.1#10053(127.0.0.1)
;; WHEN: Fri Aug 24 00:23:58 2007
;; MSG SIZE  rcvd: 49

これを元にエラー処理とかを作り込んでいけば、何とかなりそうな気がしてきた。
まずは RFC を読みなおそう。

*1:きちんと実装するなら RFC 1123 の Section 6.1、RFC 2181 あたりも読む必要がありそうだ。

*2:エラー処理やヘッダのフラグ処理をまともにしていないので、このままインターネットで運用するのは無理だけど。