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