Half-Penny For Your Thoughts

rounded down to the nearest cent



Categories


Recent Articles




Computing

Simple Ruby DNS Server

Update 2009-09-10: If you’re interested in a DNS server written in Ruby, check out RubyDNS. Thanks to Samuel for his comment below announcing its release.

A few weeks ago, I started playing with Ruby’s Resolv library, which abstracts the DNS protocol. I’m playing with creating a DNS server in Ruby. It turns out to be pretty easy to get a very basic DNS server going in Ruby, And, for the curious, here it is.

require 'socket'
require 'resolv'

hosts = [
  {:name => "example.com", :type => "A", :data => "192.168.0.1"}
]

# Bind to UDP port 53 to receive requests
$port = 53
server = UDPSocket.open
server.bind(nil, $port)

while true
  # Receive and parse query
  data = server.recvfrom(10000)
  query = Resolv::DNS::Message::decode(data[0])

  # Setup answer
  answer = Resolv::DNS::Message::new(query.id)
  answer.qr = 1                 # 0 = Query, 1 = Response
  answer.opcode = query.opcode  # Type of Query; copy from query
  answer.aa = 1                 # Is this an authoritative response: 0 = No, 1 = Yes
  answer.rd = query.rd          # Is Recursion Desired, copied from query
  answer.ra = 0                 # Does name server support recursion: 0 = No, 1 = Yes
  answer.rcode = 0              # Response code: 0 = No errors

  query.each_question do |question, typeclass|    # There may be multiple questions per query
    name = question.to_s                          # The domain name looked for in the query.
    record_type = typeclass.name.split("::").last # For example "A", "MX"
    ttl = 16000
    record = hosts.find{|host| host[:name] == name && host[:type] == record_type }
    unless record.nil?
      # Setup answer to this question
      answer.add_answer(name + ".",ttl,typeclass.new(record[:data]))
      answer.encode
    end
  end

  # Send the response
  server.send(answer.encode, 0, data[1][2], data[1][1])
end

To try this out, run the above script (may require sudo, since it binds to port 53; I haven’t figured out a way to test this at a different port) and then, in irb:

require 'resolv'
d = Resolv::DNS::new(:nameserver => "localhost")
puts d.getaddress("example.com")

This would only respond to A record requests for example.com, and the array of hashes is not particularly scalable. One could, for example, use ActiveRecord to access the records from a database, which is something I’m currently working on.