DNS for Crystal

crystal-dns implements the DNS protocol and provides a resolver. It can also be used to implement a name server, but there might be some missing pieces.

This is beta quality software. The API is not stable and some features are missing. Further testing is needed. No support for DNSSEC and DNS-over-TLS at this time.

crystal-dns was developed for Everbase, our Crystal-powered API that makes you a more productive developer. Check it out!

Installation

Add this to your project's shard.yml:

dependencies:
  dns:
    gitlab: jgillich/crystal-dns

Resolver Usage

resolver = DNS::Resolver.new
response = resolver.query("example.com", DNS::RecordType::AAAA)
response.answers.each do |answer|
  puts "got ipv6 address #{answer.data}"
end
resolver.close

Advanced Usage

If you are already familiar with DNS, most of this library should be self-explanatory. The main information carrier in DNS is a Message. The format is identical for both queries and responses, but some fields vary. In a nutshell, a message contains:

First we need a Socket to communicate over. This can be either a client or a server, but for this example we'll talk to a server.

require "socket"
require "dns"

socket = UDPSocket.new
socket.connect("8.8.8.8", 53)

Now we build a message with the format from above:

header = DNS::Header.new(op_code: DNS::OpCode::Query, recursion_desired: true)
message = DNS::Message.new(header: header)
message.questions << DNS::Question.new(name: DNS::Name.new("www.example.com"), query_type: DNS::ResourceType::A)

This is a valid DNS query that will return the IPv4 address for the domain www.example.com.

Message implements #to_io(IO, IO::ByteFormat) and .from_io(IO, IO::ByteFormat), but they do not work well with a Socket. We need IO#seek to resolve pointers, but sockets do not implement this method. There are also some slight variations in the data format between UDP and TCP. So instead, we provide #to_socket(Socket, IO::ByteFormat) and Message.from_socket(Socket, IO::ByteFormat).

Let's use them to send our message:

message.to_socket socket

Done! Now we can read the response:

response = Message.from_socket socket
response.answers.each do |answer|
  puts "got ipv4 address #{answer.data}"
end

Links