søndag 8. februar 2015

Ruby and DNS.

While i reviewed the source-code of a FakeDNS-utility i wrote a while ago, i saw the need to make this tool a bit more ruby liek and a lot of improvement could be done, especially regarding the handling of dns-messages.There are a few different gems for handling these messages, including a standard library for Ruby. I decided to check out the Resolv-library to handle the DNS-packet it self, but quickly discovered that this library wasn't very well documented at all.

When i browsed through the source-code in 'ruby-src/lib/resolv.rb' i realized that the code had some nice objects which was flagged with # :nodoc. While reading source-code is great fun during the weekends and all that, one could of course uncomment all the "# :nodoc" tags, and do a create an rdoc on that particular file to get the docs containing all the goodies....

This is a short post on the very basics of Resolv and how to build a DNS request / response ready to be used as a server/client scenario.


Message

While going through the source-code i discovered Message, a class to handle a DNS-message, which even comes with a class-method called #decode(data). This particular method takes UDP-data as argument and populates a DNS Message-object based on the data given. This makes it pretty easy to create DNS-messages indeed.


Instance objects.

There are several ways to manipulate and work with an message-object it self(eg after decoding some data). Message-object it self have a few methods which makes it pretty easy to extract and add information into each of the sections of the DNS-message. A few objects are needed before any of these methods are used. For instance in a query a name and a typeclass is required to add a question into its section.

These classes can be created with Resolv::DNS::Name.create(string), and Resolv::DNS::Resource::IN::A would be an example of typeclasses. To add questions and answers into the message-object the following methods makes it quite easy to work with message-objects.

add_question(name, typeclass)
add_answer(name, ttl, data)
add_additional(name, ttl, data)
add_authority(name, ttl, data)


Typeclass can be one of the following as shown in the picture below.

Typeclasses defined in resolv.rb.

While typeclasses are constants they can also be used to create new Resource object. By using Resolv::DNS::Resource::IN::A.new we can pass this on to a message question / answer section. For my purpose i only need two of the methods mentioned above to create a DNS-response, but one could also add additionals and authority the same way as add_question-method.


Query

To create a dns-query for www.google.com following method is used:
# Create a new DNS message.
request = Resolv::DNS::Message.new
# Add a random 16bit unsigned integer to message' ID.
request.id = rand(0xffff)
# Add a new Name-object into message' question section.
request.add_question(Resolv::DNS::Name.create("www.google.com"), Resolv::DNS::Resource::IN::A)


Response

Based on the request-object i can easily create an response as shown below.

response = request
# Question / Response (1) flag and set recursion available flag if recursion desired flag is set
response.qr = 1
response.ra = 1 if request.rd.eql? 1
# Grab domain name. 
domain_name = response.question.first.first
# Grab typeclass
resource = response.question.first[1]
# Create a new typeclass object based on the resolved IP-address.  
resource = resource.new(Resolv.getaddress(domain_name.to_s))
# Add domain_name-object, a random 16-bits unsigned integer as ttl and resource-object.
response.add_answer(domain_name, rand(0xffff), resource)

Use Message.decode to parse incoming data instead of creating a new Query-message, put this together with your UDPServer and suddenly you have created a simple DNS server using Resolv.getaddress.... Surely this is a lot easier than first represented in FakeDNS.