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:
- A header. This includes information about the message (is it a query or a response, number of results, etc)
- A question. It consists of a domain name, a record type (A, CNAME, MX etc) and a record class (in most cases IN for internet). Technically DNS allows for more than one question, but very few name servers support this.
- A number of records with a data payload. If your question's record type is A, the payload is an IPv4 address.
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