-
-
Save relistan/4345409 to your computer and use it in GitHub Desktop.
A Ruby 1.8.7 (for MagLev) port of Peter Cooper's DNS server example.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Simple, scrappy UDP DNS server in Ruby (with protocol annotations) | |
# By Peter Cooper | |
# Ruby 1.8.7/MagLev version by Karl Matthias | |
# | |
# MIT license | |
# | |
# * Not advised to use in your production environment! ;-) | |
# * Supports A and CNAME records | |
# * See http://www.ietf.org/rfc/rfc1035.txt for protocol guidance | |
# * All records get the same TTL | |
require 'socket' | |
class DNSRequest | |
attr_reader :server, :data, :domain | |
def initialize(server, data) | |
@server = server | |
@data = data | |
extract_domain | |
end | |
def extract_domain | |
@domain = '' | |
# Check "Opcode" of question header for valid question | |
if @data[2].ord & 120 == 0 | |
# Read QNAME section of question section | |
# DNS header section is 12 bytes long, so data starts at offset 12 | |
idx = 12 | |
len = @data[idx].ord | |
# Strings are rendered as a byte containing length, then text.. repeat until length of 0 | |
until len == 0 do | |
@domain += @data[idx + 1, len] + '.' | |
idx += len + 1 | |
len = @data[idx].ord | |
end | |
end | |
end | |
def response(val) | |
return empty_response if domain.empty? || !val | |
cname = val =~ /[a-z]/ | |
# Valid response header | |
response = "#{data[0,2]}\x81\x00#{data[4,2] * 2}\x00\x00\x00\x00" | |
# Append original question section | |
response += data[12..-1] | |
# Use pointer to refer to domain name in question section | |
response += "\xc0\x0c" | |
# Set response type accordingly | |
response += cname ? "\x00\x05" : "\x00\x01" | |
# Set response class (IN) | |
response += "\x00\x01" | |
# TTL in seconds | |
response += [server.ttl].pack("N") | |
# Calculate RDATA - we need its length in advance | |
if cname | |
rdata = val.split('.').collect { |a| a.length.chr + a }.join + "\x00" | |
else | |
# Append IP address as four 8 bit unsigned bytes | |
rdata = val.split('.').collect(&:to_i).pack("C*") | |
end | |
# RDATA is 4 bytes | |
response += [rdata.length].pack("n") | |
response += rdata | |
end | |
def empty_response | |
# Empty response header | |
# [id * 2, flags, NXDOMAIN, qd count * 2, an count * 2, ns count * 2, ar count * 2] | |
response = "#{data[0,2]}\x81\x03#{data[4,2]}\x00\x00\x00\x00\x00\x00" | |
# Append original question section | |
response += data[12..-1] | |
end | |
end | |
class DNSServer | |
attr_reader :port, :ttl | |
attr_accessor :records | |
def initialize(options = {}) | |
options = { | |
:address => '127.0.0.1', | |
:port => 53, | |
:ttl => 60, | |
:records => {} | |
}.merge(options) | |
@address, @port, @records, @ttl = options[:address], options[:port], options[:records], options[:ttl] | |
end | |
def run | |
BasicSocket.do_not_reverse_lookup = true | |
socket = UDPSocket.new | |
socket.bind(@address, @port) | |
loop do | |
ready = IO.select([socket], nil, nil, 1) | |
if ready | |
message = ready[0][0].recvfrom(512) # Pull down up to 512 bytes (DNS max packet size) | |
data = message[0] # The data we received | |
_, port, _, host = message[1] # The addrinfo record for the remote host | |
puts "Request from: #{host}:#{port}" | |
r = DNSRequest.new(self, data) | |
puts "Responding to: #{r.domain} with: #{@records[r.domain]}" | |
socket.send(r.response(@records[r.domain]), 0, host, port) | |
end | |
end | |
end | |
end | |
records = { | |
'example.com.' => '1.2.3.4', | |
'test.host.' => '127.0.0.2', | |
'test.cnames.com.' => 'example.com' | |
} | |
DNSServer.new(:records => records, :ttl => 120, :port => 1053).run |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
UDP DNS Server for 1.8.7/Maglev
I wanted to try some experiments with this with the Maglev storage backend, so this has been ported to Ruby 1.8.7 semantics, with my own non-blocking server loop instead of 1.9's
udp_server_loop
.This is capable of serving about 250 requests per second or ~15000 RPM on a MagLev 1.0.0 VM running on a 2.53Ghz Intel Core 2 Duo (2009) MacBook Pro under OS X 10.6.8.