Artifact 7201657da1b31cf788e683f8c8d596bb2d08b4f925e4f5f2f078d03b0852c1c6:
- File
src/resolver.cr
— part of check-in
[af207d1100]
at
2021-03-05 01:34:03
on branch trunk
— Use IO::Memory to build the raw data
This is much nicer than using Bytes and fixes always sending 512 bytes. (user: js, size: 3030) [annotate] [blame] [check-ins using]
require "socket" require "./query" require "./response" require "./rr" require "./settings" module AsyncDNS class Resolver @v6_sock : UDPSocket? @v4_sock : UDPSocket? getter settings : Settings enum Error NO_NAME_SERVER end private class Context getter query : Query getter id : UInt16 getter settings : Settings getter block : Response | Error -> property ns_index : Int32 property attempt : Int32 property used_ns : Socket::IPAddress | Nil getter raw_data : Bytes def initialize(@query : Query, @id : UInt16, @settings : Settings, @block : Response | Error ->) @ns_index = 0 @attempt = 0 @used_ns = nil # Header io = IO::Memory.new(512) io.write_bytes(@id, IO::ByteFormat::BigEndian) # RD io.write_bytes(0x100_u16, IO::ByteFormat::BigEndian) # QDCOUNT io.write_bytes(1_u16, IO::ByteFormat::BigEndian) # ANCOUNT, NSCOUNT and ARCOUNT 3.times { io.write_bytes(0_u16) } # Question # QNAME @query.domain.split('.').each do |component| if component.bytesize > 63 || io.size + component.bytesize > 512 raise ArgumentError.new("Domain component too long") end io << component end # QTYPE io.write_bytes(@query.rr_type.to_u16, IO::ByteFormat::BigEndian) # QCLASS io.write_bytes(@query.dns_class.to_u16, IO::ByteFormat::BigEndian) @raw_data = io.to_slice end end def initialize @settings = Settings.new @queries = Hash(UInt16, Context).new @tcp_queries = Hash(Socket, Context).new end def resolve(query : Query, &block : Response | Error ->) : Nil id : UInt16 while true id = Random::Secure.rand(UInt16::MIN..UInt16::MAX) break unless @queries.has_key?(id) end if query.domain.bytesize > 253 raise ArgumentError.new("Queried domain is too long") end if @settings.nameservers.empty? block.call(Error::NO_NAME_SERVER) return end send(Context.new(query.dup, id, settings.dup, block)) end private def send(context : Context) : Nil @queries[context.id] = context ns = context.settings.nameservers[context.ns_index] context.used_ns = used_ns = Socket::IPAddress.new(ns, 53) sock : UDPSocket case used_ns.family when Socket::Family::INET6 if @v6_sock.nil? @v6_sock = s = UDPSocket.new s.bind "::", 0 end sock = @v6_sock.not_nil! when Socket::Family::INET if @v4_sock.nil? @v4_sock = s = UDPSocket.new s.bind "0.0.0.0", 0 end sock = @v4_sock.not_nil! else raise ArgumentError.new("Nameserver must be INET or INET6") end sock.send(context.raw_data, used_ns) end def stop : Nil # TODO end end end