Setting up a Ruby based DNS Server

While you are in development or staging, you might want to setup servers that are only accessible internally like yourproduct.dev, yourproduct.test, yourproduct.demo and so on. We wanted this for KeepRecruiting as we isolate our development, staging and demo environments. But having servers running on a physical box inside the office network is super inefficient. So we usually set these up on Linodes and have entries in our /etc/hosts file. As the number of subdomains grow, maintaining these hosts files across the entire team becomes really hard.

That is when we started look at setting up my own DNS server with BIND. But we quickly got buried under tons of boring documentation. We just wanted something really simple and preferable in Ruby. The alternative we found was RubyDNS. RubyDNS provides a very simple syntax to setup a fully functional DNS server and also allows forwarding to a standard DNS server like Google. Now you can have your internal rubygems server or any number of your fake servers sitting publicly on the internet but with non existent domains like myproduct.gems, sub.myproduct.dev, sub.myproduct.test and sub.myproduct.demo.

The following piece of code is shamelessly stolen from RubyDNS’ documentation and slightly altered for readability. Thanks to Sam for producing a brilliant piece of work.

#!/usr/bin/env ruby

# rubydns_server.rb - The Ruby DNS server

# Copyright (c) 2009 Samuel Williams. Released under the GNU GPLv3.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

require 'rubygems'

require 'rexec'
require 'rexec/daemon'

require 'rubygems'
require 'rubydns'

require 'timeout'

# Run as user "daemon"
RUN_AS="daemon"

# Cache DNS entries for 5 minutes
CACHE_TIME=60*5

# We need to be root in order to bind to privileged port
if RExec.current_user != "root"
  $stderr.puts "Sorry, this command needs to be run as root!"
  exit 1
end

# Helper
Name = Resolv::DNS::Name

YOURIP = "LINODEIP"
GOOGLE = "8.8.8.8"

# The Daemon itself
class Server < RExec::Daemon::Base
  @@var_directory = File.dirname(__FILE__)

  def self.run
    # Don't buffer output (for debug purposes)
    $stderr.sync = true

    # Use upstream DNS for name resolution
    # Scooter DNS
    $R = Resolv::DNS.new(:nameserver => GOOGLE)

    $CACHE = {}

    # Start the RubyDNS server
    RubyDNS::run_server do
      on(:start) do
        RExec.change_user(RUN_AS)
      end

      # setup A records for custom.dev, custom.test, *.custom.dev and *.custom.test
      # brilliant ruby goodness
      match(/(.*\.)?custom.(dev|test)$/, :A) do |match, transaction|
        transaction.respond!(YOURIP)
      end

      # Default DNS handler
      otherwise do |transaction|
        key = [transaction.name, transaction.resource_class]
        cache = $CACHE[key]

        if cache and (Time.now - cache[1]) < CACHE_TIME
          logger.info "Cached: #{transaction.name}..."
          transaction.answer.merge!(cache[0])
        else
          logger.info "Lookup: #{transaction.question.to_s}"
          transaction.passthrough!($R) do |reply, reply_name|
            $CACHE[key] = [reply, Time.now]
          end
        end
      end
    end
  end
end

# RExec daemon runner
Server.daemonize

Now if you are using RVM and installed rubydns into non-system ruby, just remember to use rvmsudo instead of plain sudo. Alternatively, you can generate a wrapper. That is a topic for another blog post.

$ rvmsudo ruby rubydns_server start

 

$ rvmsudo ruby rubydns_server stop
About these ads


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.