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
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
    getter domain : String
    getter dns_class : DNSClass
    getter rr_type : RRType

    def initialize(@name : String, @class : DNSClass, @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

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 "./rr"
require "./settings"
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
    private record Context, id : UInt16, settings : Settings,
      block : Response | Error ->
      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 ->)
    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?
        yield Error::NO_NAME_SERVER
        block.call(Error::NO_NAME_SERVER)
        return
      end

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

    private def send(context : Context)
      @queries[context.@id] = context
    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)
      # TODO
    end

      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
    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
1
2
3
4

5
6
7
8
9


10
11
12
13
14



-
+




-
-
+
+



require "./rr"

module AsyncDNS
  class Response
    property name : String
    property domain : 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))
    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
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 dns_class : DNSClass
    property type : RRType
    property ttl : UInt32

    def initialize(@name : String, @class : DNSClass, @type : RRType,
    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)