AsyncDNS-cr  Check-in [b6cfd2fe24]

Overview
Comment:Initial support for sending the query
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: b6cfd2fe242cd0cd48950c53d6ef2f4810e8f76d162412f6d4e5226a341b4ca8
User & Date: js on 2021-03-05 00:59:15
Other Links: manifest | tags
Context
2021-03-05
01:34
Use IO::Memory to build the raw data check-in: af207d1100 user: js tags: trunk
00:59
Initial support for sending the query check-in: b6cfd2fe24 user: js tags: trunk
2021-03-04
02:09
Some cleanups check-in: c210ddc089 user: js tags: trunk
Changes

Modified src/query.cr from [67a7ba4afc] to [8a39ec595d].

1
2
3
4
5
6
7
8
9
10
11
12
13
require "./dns_class"
require "./rr_type"

module AsyncDNS
  class Query
    getter name : String
    getter class : DNSClass
    getter type : RRType

    def initialize(@name : String, @class : DNSClass, @type : RRType)
    end
  end
end





|
|
|

|



1
2
3
4
5
6
7
8
9
10
11
12
13
require "./dns_class"
require "./rr_type"

module AsyncDNS
  class Query
    getter domain : String
    getter dns_class : DNSClass
    getter rr_type : RRType

    def initialize(@domain : String, @dns_class : DNSClass, @rr_type : RRType)
    end
  end
end

Modified src/resolver.cr from [389b0620fd] to [d8a14eee35].

1

2
3


4
5
6



7
8
9
10
11
12



13







14




15








































16
17
18
19
20
21
22
23
24
25
26
27
28




29
30
31
32
33
34
35
36
37
38
39


40






41












42
43
44
45
46
47
require "./rr"

require "./settings"
require "./response"



module AsyncDNS
  class Resolver



    getter settings : Settings

    enum Error
      NO_NAME_SERVER
    end




    private record Context, id : UInt16, settings : Settings,







      block : Response | Error ->













































    def initialize
      @settings = Settings.new
      @queries = Hash(UInt16, Context).new
      @tcp_queries = Hash(Socket, Context).new
    end

    def resolve(query : Query, &block : Response | Error ->)
      id : UInt16
      while true
        id = Random::Secure.rand(UInt16::MIN..UInt16::MAX)
        break unless @queries.has_key?(id)
      end





      if @settings.nameservers.empty?
        yield Error::NO_NAME_SERVER
        return
      end

      send(Context.new(id, settings.dup, block))
    end

    private def send(context : Context)
      @queries[context.@id] = context



      # TODO






    end













    def stop
      # TODO
    end
  end
end
|
>
|

>
>



>
>
>






>
>
>
|
>
>
>
>
>
>
>
|
>
>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>






|






>
>
>
>

|



|


|
|

>
>
|
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>

|




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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
        @raw_data = Bytes.new(512)

        # Header

        i = 0
        @raw_data[i] = (@id >> 8).to_u8; i += 1
        @raw_data[i] = (@id & 0xFF).to_u8; i += 1
        # RD
        @raw_data[i] = 1; i += 1
        i += 1
        # QDCOUNT
        i += 1
        @raw_data[i] = 1; i += 1
        # ANCOUNT, NSCOUNT and ARCOUNT
        i += 6

        # Question

        # QNAME
        @query.domain.split('.').each do |component|
          if component.bytesize > 63 || i + component.bytesize > 512
            raise ArgumentError.new("Domain component too long")
          end

          raw_component = component.to_slice
          @raw_data[i] = raw_component.bytesize.to_u8; i += 1
          @raw_data[i, raw_component.bytesize].copy_from(raw_component)
          i += raw_component.bytesize
        end

        # QTYPE
        qtype = @query.rr_type.to_i
        @raw_data[i] = (qtype >> 8).to_u8; i += 1
        @raw_data[i] = (qtype & 0xFF).to_u8; i+= 1

        # QCLASS
        qclass = @query.dns_class.to_i
        @raw_data[i] = (qclass >> 8).to_u8; i += 1
        @raw_data[i] = (qclass & 0xFF).to_u8; i += 1
      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

Modified src/response.cr from [05324bedeb] to [d2b49f7ae9].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
require "./rr"

module AsyncDNS
  class Response
    property name : String
    property answer : Array(RR)
    property authority : Array(RR)
    property additional : Array(RR)

    def initialize(@name : String, @answer : Array(RR), @authority : Array(RR),
                   @additional : Array(RR))
    end
  end
end




|




|
|



1
2
3
4
5
6
7
8
9
10
11
12
13
14
require "./rr"

module AsyncDNS
  class Response
    property domain : String
    property answer : Array(RR)
    property authority : Array(RR)
    property additional : Array(RR)

    def initialize(@domain : String, @answer : Array(RR),
                   @authority : Array(RR), @additional : Array(RR))
    end
  end
end

Modified src/rr.cr from [2fc5a6484f] to [6005816ddc].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
require "socket"

module AsyncDNS
  abstract struct RR
    property name : String
    property class : DNSClass
    property type : RRType
    property ttl : UInt32

    def initialize(@name : String, @class : DNSClass, @type : RRType,
                   @ttl : UInt32)
    end

    struct A < RR
      property address : Socket::IPAddress

      def initialize(name : String, @address : Socket::IPAddress, ttl : UInt32)





|



|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
require "socket"

module AsyncDNS
  abstract struct RR
    property name : String
    property dns_class : DNSClass
    property type : RRType
    property ttl : UInt32

    def initialize(@name : String, @dns_class : DNSClass, @type : RRType,
                   @ttl : UInt32)
    end

    struct A < RR
      property address : Socket::IPAddress

      def initialize(name : String, @address : Socket::IPAddress, ttl : UInt32)