dnsruby 1.55 → 1.56.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +96 -0
  3. data/Rakefile +30 -29
  4. data/demo/axfr.rb +93 -93
  5. data/demo/check_soa.rb +99 -99
  6. data/demo/check_zone.rb +59 -59
  7. data/demo/digdlv.rb +43 -43
  8. data/demo/digroot.rb +34 -34
  9. data/demo/example_recurse.rb +14 -14
  10. data/demo/mresolv.rb +30 -30
  11. data/demo/mx.rb +31 -31
  12. data/demo/rubydig.rb +37 -37
  13. data/demo/to_resolve.txt +3088 -3088
  14. data/demo/trace_dns.rb +46 -46
  15. data/lib/dnsruby.rb +161 -526
  16. data/lib/dnsruby/DNS.rb +305 -0
  17. data/lib/{Dnsruby/Cache.rb → dnsruby/cache.rb} +152 -152
  18. data/lib/{Dnsruby → dnsruby}/code_mapper.rb +48 -52
  19. data/lib/dnsruby/code_mappers.rb +295 -0
  20. data/lib/{Dnsruby/Config.rb → dnsruby/config.rb} +454 -454
  21. data/lib/{Dnsruby → dnsruby}/dnssec.rb +91 -91
  22. data/lib/{Dnsruby/Hosts.rb → dnsruby/hosts.rb} +125 -125
  23. data/lib/{Dnsruby → dnsruby}/ipv4.rb +26 -26
  24. data/lib/{Dnsruby → dnsruby}/ipv6.rb +42 -42
  25. data/lib/{Dnsruby → dnsruby}/key_cache.rb +29 -29
  26. data/lib/dnsruby/message/decoder.rb +164 -0
  27. data/lib/dnsruby/message/encoder.rb +75 -0
  28. data/lib/dnsruby/message/header.rb +249 -0
  29. data/lib/dnsruby/message/message.rb +629 -0
  30. data/lib/dnsruby/message/question.rb +86 -0
  31. data/lib/dnsruby/message/section.rb +96 -0
  32. data/lib/{Dnsruby → dnsruby}/name.rb +141 -141
  33. data/lib/dnsruby/packet_sender.rb +661 -0
  34. data/lib/{Dnsruby/Recursor.rb → dnsruby/recursor.rb} +235 -233
  35. data/lib/dnsruby/resolv.rb +113 -0
  36. data/lib/dnsruby/resolver.rb +1192 -0
  37. data/lib/dnsruby/resource/A.rb +56 -0
  38. data/lib/dnsruby/resource/AAAA.rb +54 -0
  39. data/lib/{Dnsruby → dnsruby}/resource/AFSDB.rb +68 -68
  40. data/lib/{Dnsruby → dnsruby}/resource/CERT.rb +105 -105
  41. data/lib/{Dnsruby → dnsruby}/resource/DHCID.rb +54 -54
  42. data/lib/dnsruby/resource/DLV.rb +27 -0
  43. data/lib/{Dnsruby → dnsruby}/resource/DNSKEY.rb +372 -372
  44. data/lib/{Dnsruby → dnsruby}/resource/DS.rb +255 -255
  45. data/lib/{Dnsruby → dnsruby}/resource/HINFO.rb +71 -71
  46. data/lib/{Dnsruby → dnsruby}/resource/HIP.rb +29 -29
  47. data/lib/{Dnsruby → dnsruby}/resource/IN.rb +30 -30
  48. data/lib/{Dnsruby → dnsruby}/resource/IPSECKEY.rb +31 -31
  49. data/lib/{Dnsruby → dnsruby}/resource/ISDN.rb +62 -62
  50. data/lib/{Dnsruby → dnsruby}/resource/KX.rb +65 -65
  51. data/lib/{Dnsruby → dnsruby}/resource/LOC.rb +263 -263
  52. data/lib/{Dnsruby → dnsruby}/resource/MINFO.rb +69 -69
  53. data/lib/{Dnsruby → dnsruby}/resource/MX.rb +65 -65
  54. data/lib/{Dnsruby → dnsruby}/resource/NAPTR.rb +98 -98
  55. data/lib/{Dnsruby → dnsruby}/resource/NSAP.rb +171 -171
  56. data/lib/dnsruby/resource/NSEC.rb +275 -0
  57. data/lib/dnsruby/resource/NSEC3.rb +332 -0
  58. data/lib/dnsruby/resource/NSEC3PARAM.rb +135 -0
  59. data/lib/dnsruby/resource/OPT.rb +272 -0
  60. data/lib/{Dnsruby → dnsruby}/resource/PX.rb +70 -70
  61. data/lib/{Dnsruby → dnsruby}/resource/RP.rb +75 -75
  62. data/lib/dnsruby/resource/RR.rb +421 -0
  63. data/lib/dnsruby/resource/RRSIG.rb +275 -0
  64. data/lib/dnsruby/resource/RRSet.rb +190 -0
  65. data/lib/{Dnsruby → dnsruby}/resource/RT.rb +67 -67
  66. data/lib/{Dnsruby → dnsruby}/resource/SOA.rb +94 -94
  67. data/lib/dnsruby/resource/SPF.rb +29 -0
  68. data/lib/dnsruby/resource/SRV.rb +112 -0
  69. data/lib/{Dnsruby → dnsruby}/resource/SSHFP.rb +14 -14
  70. data/lib/dnsruby/resource/TKEY.rb +163 -0
  71. data/lib/dnsruby/resource/TSIG.rb +593 -0
  72. data/lib/{Dnsruby → dnsruby}/resource/TXT.rb +191 -191
  73. data/lib/dnsruby/resource/X25.rb +55 -0
  74. data/lib/{Dnsruby → dnsruby}/resource/domain_name.rb +25 -25
  75. data/lib/{Dnsruby → dnsruby}/resource/generic.rb +80 -80
  76. data/lib/dnsruby/resource/resource.rb +25 -0
  77. data/lib/{Dnsruby → dnsruby}/select_thread.rb +148 -148
  78. data/lib/{Dnsruby/SingleResolver.rb → dnsruby/single_resolver.rb} +60 -60
  79. data/lib/{Dnsruby → dnsruby}/single_verifier.rb +344 -344
  80. data/lib/dnsruby/the_log.rb +44 -0
  81. data/lib/dnsruby/update.rb +278 -0
  82. data/lib/dnsruby/validator_thread.rb +124 -0
  83. data/lib/dnsruby/version.rb +3 -0
  84. data/lib/{Dnsruby → dnsruby}/zone_reader.rb +93 -93
  85. data/lib/{Dnsruby → dnsruby}/zone_transfer.rb +377 -377
  86. data/test/spec_helper.rb +16 -0
  87. data/test/tc_axfr.rb +31 -34
  88. data/test/tc_cache.rb +32 -32
  89. data/test/tc_dlv.rb +28 -28
  90. data/test/tc_dns.rb +73 -76
  91. data/test/tc_dnskey.rb +31 -32
  92. data/test/tc_dnsruby.rb +50 -44
  93. data/test/tc_ds.rb +36 -36
  94. data/test/tc_escapedchars.rb +252 -255
  95. data/test/tc_hash.rb +17 -21
  96. data/test/tc_header.rb +48 -57
  97. data/test/tc_hip.rb +19 -22
  98. data/test/tc_ipseckey.rb +18 -21
  99. data/test/tc_keith.rb +300 -0
  100. data/test/tc_message.rb +87 -0
  101. data/test/tc_misc.rb +83 -87
  102. data/test/tc_name.rb +81 -84
  103. data/test/tc_naptr.rb +18 -21
  104. data/test/tc_nsec.rb +55 -55
  105. data/test/tc_nsec3.rb +23 -24
  106. data/test/tc_nsec3param.rb +20 -21
  107. data/test/tc_packet.rb +90 -93
  108. data/test/tc_packet_unique_push.rb +48 -51
  109. data/test/tc_question.rb +30 -33
  110. data/test/tc_queue.rb +16 -17
  111. data/test/tc_recur.rb +16 -17
  112. data/test/tc_res_config.rb +38 -41
  113. data/test/tc_res_env.rb +29 -32
  114. data/test/tc_res_file.rb +26 -29
  115. data/test/tc_res_opt.rb +62 -65
  116. data/test/tc_resolver.rb +287 -242
  117. data/test/tc_rr-opt.rb +70 -63
  118. data/test/tc_rr-txt.rb +68 -71
  119. data/test/tc_rr-unknown.rb +45 -48
  120. data/test/tc_rr.rb +76 -70
  121. data/test/tc_rrset.rb +21 -22
  122. data/test/tc_rrsig.rb +19 -20
  123. data/test/tc_single_resolver.rb +294 -297
  124. data/test/tc_soak.rb +199 -202
  125. data/test/tc_soak_base.rb +29 -34
  126. data/test/tc_sshfp.rb +20 -23
  127. data/test/tc_tcp.rb +32 -35
  128. data/test/tc_tkey.rb +41 -44
  129. data/test/tc_tsig.rb +81 -84
  130. data/test/tc_update.rb +108 -111
  131. data/test/tc_validator.rb +29 -29
  132. data/test/tc_verifier.rb +81 -82
  133. data/test/ts_dnsruby.rb +16 -15
  134. data/test/ts_offline.rb +62 -63
  135. data/test/ts_online.rb +115 -115
  136. metadata +155 -90
  137. data/README +0 -59
  138. data/lib/Dnsruby/DNS.rb +0 -305
  139. data/lib/Dnsruby/PacketSender.rb +0 -656
  140. data/lib/Dnsruby/Resolver.rb +0 -1189
  141. data/lib/Dnsruby/TheLog.rb +0 -44
  142. data/lib/Dnsruby/message.rb +0 -1230
  143. data/lib/Dnsruby/resource/A.rb +0 -56
  144. data/lib/Dnsruby/resource/AAAA.rb +0 -54
  145. data/lib/Dnsruby/resource/DLV.rb +0 -27
  146. data/lib/Dnsruby/resource/NSEC.rb +0 -298
  147. data/lib/Dnsruby/resource/NSEC3.rb +0 -340
  148. data/lib/Dnsruby/resource/NSEC3PARAM.rb +0 -135
  149. data/lib/Dnsruby/resource/OPT.rb +0 -213
  150. data/lib/Dnsruby/resource/RRSIG.rb +0 -275
  151. data/lib/Dnsruby/resource/SPF.rb +0 -29
  152. data/lib/Dnsruby/resource/SRV.rb +0 -112
  153. data/lib/Dnsruby/resource/TKEY.rb +0 -163
  154. data/lib/Dnsruby/resource/TSIG.rb +0 -593
  155. data/lib/Dnsruby/resource/X25.rb +0 -55
  156. data/lib/Dnsruby/resource/resource.rb +0 -678
  157. data/lib/Dnsruby/update.rb +0 -278
  158. data/lib/Dnsruby/validator_thread.rb +0 -124
@@ -0,0 +1,113 @@
1
+ # The Resolv class can be used to resolve addresses using /etc/hosts and /etc/resolv.conf,
2
+ #
3
+ # The DNS class may be used to perform more queries. If greater control over the sending
4
+ # of packets is required, then the Resolver or SingleResolver classes may be used.
5
+ module Dnsruby
6
+ class Resolv
7
+
8
+ # Looks up the first IP address for +name+
9
+ def self.getaddress(name)
10
+ DefaultResolver.getaddress(name)
11
+ end
12
+
13
+ # Looks up all IP addresses for +name+
14
+ def self.getaddresses(name)
15
+ DefaultResolver.getaddresses(name)
16
+ end
17
+
18
+ # Iterates over all IP addresses for +name+
19
+ def self.each_address(name, &block)
20
+ DefaultResolver.each_address(name, &block)
21
+ end
22
+
23
+ # Looks up the first hostname of +address+
24
+ def self.getname(address)
25
+ DefaultResolver.getname(address)
26
+ end
27
+
28
+ # Looks up all hostnames of +address+
29
+ def self.getnames(address)
30
+ DefaultResolver.getnames(address)
31
+ end
32
+
33
+ # Iterates over all hostnames of +address+
34
+ def self.each_name(address, &proc)
35
+ DefaultResolver.each_name(address, &proc)
36
+ end
37
+
38
+ # Creates a new Resolv using +resolvers+
39
+ def initialize(resolvers=[Hosts.new, DNS.new])
40
+ @resolvers = resolvers
41
+ end
42
+
43
+ # Looks up the first IP address for +name+
44
+ def getaddress(name)
45
+ each_address(name) {|address| return address}
46
+ raise ResolvError.new("no address for #{name}")
47
+ end
48
+
49
+ # Looks up all IP addresses for +name+
50
+ def getaddresses(name)
51
+ ret = []
52
+ each_address(name) {|address| ret << address}
53
+ return ret
54
+ end
55
+
56
+ # Iterates over all IP addresses for +name+
57
+ def each_address(name)
58
+ if AddressRegex =~ name
59
+ yield name
60
+ return
61
+ end
62
+ yielded = false
63
+ @resolvers.each {|r|
64
+ r.each_address(name) {|address|
65
+ yield address.to_s
66
+ yielded = true
67
+ }
68
+ return if yielded
69
+ }
70
+ end
71
+
72
+ # Looks up the first hostname of +address+
73
+ def getname(address)
74
+ each_name(address) {|name| return name}
75
+ raise ResolvError.new("no name for #{address}")
76
+ end
77
+
78
+ # Looks up all hostnames of +address+
79
+ def getnames(address)
80
+ ret = []
81
+ each_name(address) {|name| ret << name}
82
+ return ret
83
+ end
84
+
85
+ # Iterates over all hostnames of +address+
86
+ def each_name(address)
87
+ yielded = false
88
+ @resolvers.each {|r|
89
+ r.each_name(address) {|name|
90
+ yield name.to_s
91
+ yielded = true
92
+ }
93
+ return if yielded
94
+ }
95
+ end
96
+
97
+
98
+ require 'dnsruby/cache'
99
+ require 'dnsruby/DNS'
100
+ require 'dnsruby/hosts'
101
+ require 'dnsruby/message'
102
+ require 'dnsruby/update'
103
+ require 'dnsruby/zone_transfer'
104
+ require 'dnsruby/dnssec'
105
+ require 'dnsruby/zone_reader'
106
+
107
+ # Default Resolver to use for Dnsruby class methods
108
+ DefaultResolver = self.new
109
+
110
+ # Address RegExp to use for matching IP addresses
111
+ AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/
112
+ end
113
+ end
@@ -0,0 +1,1192 @@
1
+ # --
2
+ # Copyright 2007 Nominet UK
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+ # require 'Dnsruby/resolver_register.rb'
17
+
18
+ require 'dnsruby/packet_sender'
19
+ require 'dnsruby/recursor'
20
+
21
+ module Dnsruby
22
+ # == Description
23
+ # Dnsruby::Resolver is a DNS stub resolver.
24
+ # This class performs queries with retries across multiple nameservers.
25
+ # The system configured resolvers are used by default.
26
+ #
27
+ # The retry policy is a combination of the Net::DNS and dnsjava approach, and has the option of :
28
+ # * A total timeout for the query (defaults to 0, meaning "no total timeout")
29
+ # * A retransmission system that targets the namervers concurrently once the first query round is
30
+ # complete, but in which the total time per query round is split between the number of nameservers
31
+ # targetted for the first round. and total time for query round is doubled for each query round
32
+ #
33
+ # Note that, if a total timeout is specified, then that will apply regardless of the retry policy
34
+ # (i.e. it may cut retries short).
35
+ #
36
+ # Note also that these timeouts are distinct from the SingleResolver's packet_timeout
37
+ #
38
+ # Timeouts apply to the initial query and response. If DNSSEC validation is to
39
+ # be performed, then additional queries may be required (these are performed automatically
40
+ # by Dnsruby). Each additional query will be performed with its own timeouts.
41
+ # So, even with a query_timeout of 5 seconds, a response which required extensive
42
+ # validation may take several times that long.
43
+ # (Future versions of Dnsruby may expose finer-grained events for client tracking of
44
+ # responses and validation)
45
+ #
46
+ # == Methods
47
+ #
48
+ # === Synchronous
49
+ # These methods raise an exception or return a response message with rcode==NOERROR
50
+ #
51
+ # * Dnsruby::Resolver#send_message(msg)
52
+ # * Dnsruby::Resolver#query(name [, type [, klass]])
53
+ #
54
+ # There are "!" versions of these two methods that return an array [response, error]
55
+ # instead of raising an error on failure. They can be called as follows:
56
+ #
57
+ # response, error = resolver.send_message!(...)
58
+ # response, error = resolver.query!(...)
59
+ #
60
+ # If the request succeeds, response will contain the Dnsruby::Message response
61
+ # and error will be nil.
62
+ #
63
+ # If the request fails, response will be nil and error will contain the error raised.
64
+ #
65
+ # === Asynchronous
66
+ # These methods use a response queue to return the response and the error
67
+ #
68
+ # * Dnsruby::Resolver#send_async(msg, response_queue, query_id)
69
+ #
70
+ # == Event Loop
71
+ # Dnsruby runs a pure Ruby event loop to handle I/O in a single thread.
72
+ # Support for EventMachine has been deprecated.
73
+ class Resolver
74
+ DefaultQueryTimeout = 0
75
+ DefaultPacketTimeout = 5
76
+ DefaultRetryTimes = 1
77
+ DefaultRetryDelay = 5
78
+ DefaultPort = 53
79
+ DefaultDnssec = true
80
+ AbsoluteMinDnssecUdpSize = 1220
81
+ MinDnssecUdpSize = 4096
82
+ DefaultUDPSize = MinDnssecUdpSize
83
+
84
+ class EventType
85
+ RECEIVED = 0
86
+ VALIDATED = 1 # @TODO@ Should be COMPLETE?
87
+ ERROR = 2
88
+ end
89
+
90
+ # The port to send queries to on the resolver
91
+ attr_reader :port
92
+
93
+ # Should TCP be used as a transport rather than UDP?
94
+ # If use_tcp==true, then ONLY TCP will be used as a transport.
95
+ attr_reader :use_tcp
96
+
97
+ # If no_tcp==true, then ONLY UDP will be used as a transport.
98
+ # This should not generally be used, but is provided as a debugging aid.
99
+ attr_reader :no_tcp
100
+
101
+
102
+ attr_reader :tsig
103
+
104
+ # Should truncation be ignored?
105
+ # i.e. the TC bit is ignored and thus the resolver will not requery over TCP if TC is set
106
+ attr_reader :ignore_truncation
107
+
108
+ # The source address to send queries from for IPv4
109
+ attr_reader :src_address
110
+
111
+ # The source address to send queries from for IPv6
112
+ attr_reader :src_address6
113
+
114
+ # Should the Recursion Desired bit be set?
115
+ attr_reader :recurse
116
+
117
+ # The maximum UDP size to be used
118
+ attr_reader :udp_size
119
+
120
+ # The current Config
121
+ attr_reader :config
122
+
123
+ # Does this Resolver cache answers, and attempt to retrieve answer from the cache?
124
+ attr_reader :do_caching
125
+
126
+ # The array of SingleResolvers used for sending query messages
127
+ # attr_accessor :single_resolvers # :nodoc:
128
+ def single_resolvers=(s) # :nodoc:
129
+ @configured = true
130
+ # @single_res_mutex.synchronize {
131
+ @single_resolvers = s
132
+ # }
133
+ end
134
+ def single_resolvers # :nodoc:
135
+ unless @configured
136
+ add_config_nameservers
137
+ end
138
+ @single_resolvers
139
+ end
140
+
141
+ # The timeout for any individual packet. This is the timeout used by SingleResolver
142
+ attr_reader :packet_timeout
143
+
144
+ # Note that this timeout represents the total time a query may run for - multiple packets
145
+ # can be sent to multiple nameservers in this time.
146
+ # This is distinct from the SingleResolver per-packet timeout
147
+ # The query_timeout is not required - it will default to 0, which means "do not use query_timeout".
148
+ # If this is the case then the timeout will be dictated by the retry_times and retry_delay attributes
149
+ attr_accessor :query_timeout
150
+
151
+ # The query will be tried across nameservers retry_times times, with a delay of retry_delay seconds
152
+ # between each retry. The first time round, retry_delay will be divided by the number of nameservers
153
+ # being targetted, and a new nameserver will be queried with the resultant delay.
154
+ attr_accessor :retry_times, :retry_delay
155
+
156
+ # Use DNSSEC for this Resolver
157
+ attr_reader :dnssec
158
+
159
+ # Defines whether validation is performed by default on this Resolver when the
160
+ # query method is called.
161
+ # Note that send_message and send_async expect a
162
+ # Message object to be passed in, which is already configured to the callers
163
+ # requirements.
164
+ attr_accessor :do_validation
165
+
166
+ # Defines whether we will cache responses, or pass every request to the
167
+ # upstream resolver. This is only really useful when querying authoritative
168
+ # servers (as the upstream recursive resolver is likely to cache)
169
+ attr_accessor :do_caching
170
+
171
+ # --
172
+ # @TODO@ add load_balance? i.e. Target nameservers in a random, rather than pre-determined, order?
173
+ # This is best done when configuring the Resolver, as it will re-order servers based on their response times.
174
+ #
175
+ # ++
176
+
177
+ # Query for a name. If a valid Message is received, then it is returned
178
+ # to the caller. Otherwise an exception (a Dnsruby::ResolvError or Dnsruby::ResolvTimeout) is raised.
179
+ #
180
+ # require 'dnsruby'
181
+ # res = Dnsruby::Resolver.new
182
+ # response = res.query('example.com') # defaults to Types.A, Classes.IN
183
+ # response = res.query('example.com', Types.MX)
184
+ # response = res.query('208.77.188.166') # IPv4 address so PTR query will be made
185
+ # response = res.query('208.77.188.166', Types.PTR)
186
+ def query(name, type=Types.A, klass=Classes.IN, set_cd=@dnssec)
187
+ msg = Message.new
188
+ msg.do_caching = @do_caching
189
+ msg.header.rd = 1
190
+ msg.add_question(name, type, klass)
191
+ msg.do_validation = @do_validation
192
+ if @dnssec
193
+ msg.header.cd = set_cd # We do our own validation by default
194
+ end
195
+ send_message(msg)
196
+ end
197
+
198
+ # Like query, but does not raise an error when an error occurs.
199
+ # Instead, it returns it.
200
+ # @return a 2 element array: [response, error]
201
+ def query!(name, type=Types.A, klass=Classes.IN, set_cd=@dnssec)
202
+ response = nil; error = nil
203
+ begin
204
+ response = query(name, type, klass, set_cd)
205
+ rescue => e
206
+ error = e
207
+ end
208
+ [response, error]
209
+ end
210
+
211
+ def query_no_validation_or_recursion(name, type=Types.A, klass=Classes.IN) # :nodoc: all
212
+ msg = Message.new
213
+ msg.do_caching = @do_caching
214
+ msg.header.rd = false
215
+ msg.do_validation = false
216
+ msg.add_question(name, type, klass)
217
+ if @dnssec
218
+ msg.header.cd = true # We do our own validation by default
219
+ end
220
+ send_message(msg)
221
+ end
222
+
223
+ # Send a message, and wait for the response. If a valid Message is received, then it is returned
224
+ # to the caller. Otherwise an exception (a Dnsruby::ResolvError or Dnsruby::ResolvTimeout) is raised.
225
+ #
226
+ # send_async is called internally.
227
+ #
228
+ # example :
229
+ #
230
+ # require 'dnsruby'
231
+ # include Dnsruby
232
+ # res = Dnsruby::Resolver.new
233
+ # begin
234
+ # response = res.send_message(Message.new('example.com', Types.MX))
235
+ # rescue ResolvError
236
+ # # ...
237
+ # rescue ResolvTimeout
238
+ # # ...
239
+ # end
240
+ def send_message(message)
241
+ Dnsruby.log.debug{'Resolver : sending message'}
242
+ q = Queue.new
243
+ send_async(message, q)
244
+
245
+ _id, result, error = q.pop
246
+
247
+ if error
248
+ raise error
249
+ else
250
+ result
251
+ end
252
+ end
253
+
254
+ # Like send_message, but does not raise an error when an error occurs.
255
+ # Instead, it returns it.
256
+ # @return a 2 element array: [response, error]
257
+ def send_message!(message)
258
+ response = nil; error = nil
259
+ begin
260
+ response = send_message(message)
261
+ rescue => e
262
+ error = e
263
+ end
264
+ [response, error]
265
+ end
266
+
267
+ # This method takes a Message (supplied by the client), and sends it to
268
+ # the configured nameservers. No changes are made to the Message before it
269
+ # is sent (TSIG signatures will be applied if configured on the Resolver).
270
+ # Retries are handled as the Resolver is configured to do.
271
+ # Incoming responses to the query are not cached or validated (although TCP
272
+ # fallback will be performed if the TC bit is set and the (Single)Resolver has
273
+ # ignore_truncation set to false).
274
+ # Note that the Message is left untouched - this means that no OPT records are
275
+ # added, even if the UDP transport for the server is specified at more than 512
276
+ # bytes. If it is desired to use EDNS for this packet, then you should call
277
+ # the Dnsruby::PacketSender#prepare_for_dnssec(msg), or
278
+ # Dnsruby::PacketSender#add_opt_rr(msg)
279
+ # The return value from this method is the [response, error] tuple. Either of
280
+ # these values may be nil - it is up to the client to check.
281
+ #
282
+ # example :
283
+ #
284
+ # require 'dnsruby'
285
+ # include Dnsruby
286
+ # res = Dnsruby::Resolver.new
287
+ # response, error = res.send_plain_message(Message.new('example.com', Types.MX))
288
+ # if error
289
+ # print "Error returned : #{error}\n"
290
+ # else
291
+ # process_response(response)
292
+ # end
293
+ def send_plain_message(message)
294
+ Dnsruby::TheLog.debug('Resolver : send_plain_message')
295
+ message.do_caching = false
296
+ message.do_validation = false
297
+ message.send_raw = true
298
+ q = Queue.new
299
+ send_async(message, q)
300
+ _id, result, error = q.pop
301
+ [result, error]
302
+ end
303
+
304
+
305
+ # Asynchronously send a Message to the server. The send can be done using just
306
+ # Dnsruby. Support for EventMachine has been deprecated.
307
+ #
308
+ # == Dnsruby pure Ruby event loop :
309
+ #
310
+ # A client_queue is supplied by the client,
311
+ # along with an optional client_query_id to identify the response. The client_query_id
312
+ # is generated, if not supplied, and returned to the client.
313
+ # When the response is known,
314
+ # a tuple of (query_id, response_message, exception) will be added to the client_queue.
315
+ #
316
+ # The query is sent synchronously in the caller's thread. The select thread is then used to
317
+ # listen for and process the response (up to pushing it to the client_queue). The client thread
318
+ # is then used to retrieve the response and deal with it.
319
+ #
320
+ # Takes :
321
+ #
322
+ # * msg - the message to send
323
+ # * client_queue - a Queue to push the response to, when it arrives
324
+ # * client_query_id - an optional ID to identify the query to the client
325
+ # * use_tcp - whether to use only TCP (defaults to SingleResolver.use_tcp)
326
+ #
327
+ # Returns :
328
+ #
329
+ # * client_query_id - to identify the query response to the client. This ID is
330
+ # generated if it is not passed in by the client
331
+ #
332
+ # === Example invocations :
333
+ #
334
+ # id = res.send_async(msg, queue)
335
+ # NOT SUPPORTED : id = res.send_async(msg, queue, use_tcp)
336
+ # id = res.send_async(msg, queue, id)
337
+ # id = res.send_async(msg, queue, id, use_tcp)
338
+ #
339
+ # === Example code :
340
+ #
341
+ # require 'dnsruby'
342
+ # res = Dnsruby::Resolver.newsend
343
+ # query_id = 10 # can be any object you like
344
+ # query_queue = Queue.new
345
+ # res.send_async(Message.new('example.com', Types.MX), query_queue, query_id)
346
+ # query_id_2 = res.send_async(Message.new('example.com', Types.A), query_queue)
347
+ # # ...do a load of other stuff here...
348
+ # 2.times do
349
+ # response_id, response, exception = query_queue.pop
350
+ # # You can check the ID to see which query has been answered
351
+ # if exception == nil
352
+ # # deal with good response
353
+ # else
354
+ # # deal with problem
355
+ # end
356
+ # end
357
+ #
358
+ def send_async(msg, client_queue, client_query_id = nil)
359
+ unless @configured
360
+ add_config_nameservers
361
+ end
362
+ # @single_res_mutex.synchronize {
363
+ unless @resolver_ruby # @TODO@ Synchronize this?
364
+ @resolver_ruby = ResolverRuby.new(self)
365
+ end
366
+ # }
367
+ client_query_id = @resolver_ruby.send_async(msg, client_queue, client_query_id)
368
+ if @single_resolvers.length == 0
369
+ Thread.start {
370
+ sleep(@query_timeout == 0 ? 1 : @query_timeout)
371
+ client_queue.push([client_query_id, nil, ResolvTimeout.new('Query timed out - no nameservers configured')])
372
+ }
373
+ end
374
+ client_query_id
375
+ end
376
+
377
+ # Close the Resolver. Unfinished queries are terminated with OtherResolvError.
378
+ def close
379
+ @resolver_ruby.close if @resolver_ruby
380
+ end
381
+
382
+ # Create a new Resolver object. If no parameters are passed in, then the default
383
+ # system configuration will be used. Otherwise, a Hash may be passed in with the
384
+ # following optional elements :
385
+ #
386
+ #
387
+ # * :port
388
+ # * :use_tcp
389
+ # * :tsig
390
+ # * :ignore_truncation
391
+ # * :src_address
392
+ # * :src_address6
393
+ # * :src_port
394
+ # * :recurse
395
+ # * :udp_size
396
+ # * :config_info - see Config
397
+ # * :nameserver - can be either a String or an array of Strings
398
+ # * :packet_timeout
399
+ # * :query_timeout
400
+ # * :retry_times
401
+ # * :retry_delay
402
+ # * :do_caching
403
+ def initialize(*args)
404
+ # @TODO@ Should we allow :namesver to be an RRSet of NS records? Would then need to randomly order them?
405
+ @resolver_ruby = nil
406
+ @src_address = nil
407
+ @src_address6 = nil
408
+ @single_res_mutex = Mutex.new
409
+ @configured = false
410
+ @do_caching = true
411
+ @config = Config.new()
412
+ reset_attributes
413
+
414
+ # Process args
415
+ if args.length == 1
416
+ if args[0].class == Hash
417
+ args[0].keys.each do |key|
418
+ begin
419
+ if key == :config_info
420
+ @config.set_config_info(args[0][:config_info])
421
+ elsif key == :nameserver
422
+ set_config_nameserver(args[0][:nameserver])
423
+ elsif key == :nameservers
424
+ set_config_nameserver(args[0][:nameservers])
425
+ else
426
+ send(key.to_s + '=', args[0][key])
427
+ end
428
+ rescue Exception => e
429
+ Dnsruby.log.error{"Argument #{key} not valid : #{e}\n"}
430
+ end
431
+ end
432
+ elsif args[0].class == String
433
+ set_config_nameserver(args[0])
434
+ elsif args[0].class == Config
435
+ # also accepts a Config object from Dnsruby::Resolv
436
+ @config = args[0]
437
+ end
438
+ else
439
+ # Anything to do?
440
+ end
441
+ update
442
+ end
443
+
444
+ def add_config_nameservers # :nodoc: all
445
+ unless @configured
446
+ @config.get_ready
447
+ end
448
+ @configured = true
449
+ @single_res_mutex.synchronize {
450
+ # Add the Config nameservers
451
+ @config.nameserver.each do |ns|
452
+ res = PacketSender.new({
453
+ server: ns,
454
+ dnssec: @dnssec,
455
+ use_tcp: @use_tcp,
456
+ no_tcp: @no_tcp,
457
+ packet_timeout: @packet_timeout,
458
+ tsig: @tsig,
459
+ ignore_truncation: @ignore_truncation,
460
+ src_address: @src_address,
461
+ src_address6: @src_address6,
462
+ src_port: @src_port,
463
+ recurse: @recurse,
464
+ udp_size: @udp_size})
465
+ @single_resolvers.push(res) if res
466
+ end
467
+ }
468
+ end
469
+
470
+ def set_config_nameserver(n)
471
+ # @TODO@ Should we allow NS RRSet here? If so, then .sort_by {rand}
472
+ @config.get_ready unless @configured
473
+ @configured = true
474
+
475
+ @config.nameserver = n.kind_of?(String) ? [n] : n
476
+ add_config_nameservers
477
+ end
478
+
479
+ def reset_attributes # :nodoc: all
480
+ @resolver_ruby.reset_attributes if @resolver_ruby
481
+
482
+ # Attributes
483
+
484
+ # do_validation tells the Resolver whether to try to validate the response
485
+ # with DNSSEC. This should work for NSEC-signed domains, but NSEC3
486
+ # validation is not currently supported. This attribute now defaults to
487
+ # false. Please let me know if you require NSEC3 validation.
488
+ @do_validation = false
489
+ @query_timeout = DefaultQueryTimeout
490
+ @retry_delay = DefaultRetryDelay
491
+ @retry_times = DefaultRetryTimes
492
+ @packet_timeout = DefaultPacketTimeout
493
+ @port = DefaultPort
494
+ @udp_size = DefaultUDPSize
495
+ @dnssec = DefaultDnssec
496
+ @do_caching= true
497
+ @use_tcp = false
498
+ @no_tcp = false
499
+ @tsig = nil
500
+ @ignore_truncation = false
501
+ @config = Config.new()
502
+ @src_address = nil
503
+ @src_address6 = nil
504
+ @src_port = [0]
505
+ @recurse = true
506
+ @single_res_mutex.synchronize {
507
+ @single_resolvers=[]
508
+ }
509
+ @configured = false
510
+ end
511
+
512
+ def update # :nodoc: all
513
+ # Update any resolvers we have with the latest config
514
+ @single_res_mutex.synchronize do
515
+ @single_resolvers.delete(nil) # Just in case...
516
+ @single_resolvers.each { |res| update_internal_res(res) }
517
+ end
518
+ end
519
+
520
+ # # Add a new SingleResolver to the list of resolvers this Resolver object will
521
+ # # query.
522
+ # def add_resolver(internal) # :nodoc:
523
+ # # @TODO@ Make a new PacketSender from this SingleResolver!!
524
+ # @single_resolvers.push(internal)
525
+ # end
526
+
527
+ def add_server(server)# :nodoc:
528
+ @configured = true
529
+ res = PacketSender.new(server)
530
+ log_and_raise("Can't create server #{server}", ArgumentError) unless res
531
+ update_internal_res(res)
532
+ @single_res_mutex.synchronize { @single_resolvers.push(res) }
533
+ end
534
+
535
+ def update_internal_res(res)
536
+ [:port, :use_tcp, :no_tcp, :tsig, :ignore_truncation, :packet_timeout,
537
+ :src_address, :src_address6, :src_port, :recurse,
538
+ :udp_size, :dnssec].each do |param|
539
+
540
+ res.send(param.to_s + '=', instance_variable_get('@' + param.to_s))
541
+ end
542
+ end
543
+
544
+ def nameservers=(ns)
545
+ self.nameserver=(ns)
546
+ end
547
+
548
+ def nameserver=(n)
549
+ @configured = true
550
+ @single_res_mutex.synchronize { @single_resolvers=[] }
551
+ set_config_nameserver(n)
552
+ add_config_nameservers
553
+ end
554
+
555
+ # --
556
+ # @TODO@ Should really auto-generate these methods.
557
+ # Also, any way to tie them up with SingleResolver RDoc?
558
+ # ++
559
+
560
+ def packet_timeout=(t)
561
+ @packet_timeout = t
562
+ update
563
+ end
564
+
565
+ # The source port to send queries from
566
+ # Returns either a single Fixnum or an Array
567
+ # e.g. '0', or '[60001, 60002, 60007]'
568
+ #
569
+ # Defaults to 0 - random port
570
+ def src_port
571
+ @src_port.length == 1 ? @src_port[0] : @src_port
572
+ end
573
+
574
+ # Can be a single Fixnum or a Range or an Array
575
+ # If an invalid port is selected (one reserved by
576
+ # IANA), then an ArgumentError will be raised.
577
+ #
578
+ # res.src_port=0
579
+ # res.src_port=[60001,60005,60010]
580
+ # res.src_port=60015..60115
581
+ #
582
+ def src_port=(p)
583
+ if Resolver.check_port(p)
584
+ @src_port = Resolver.get_ports_from(p)
585
+ update
586
+ end
587
+ end
588
+
589
+ # Can be a single Fixnum or a Range or an Array
590
+ # If an invalid port is selected (one reserved by
591
+ # IANA), then an ArgumentError will be raised.
592
+ # "0" means "any valid port" - this is only a viable
593
+ # option if it is the only port in the list.
594
+ # An ArgumentError will be raised if "0" is added to
595
+ # an existing set of source ports.
596
+ #
597
+ # res.add_src_port(60000)
598
+ # res.add_src_port([60001,60005,60010])
599
+ # res.add_src_port(60015..60115)
600
+ #
601
+ def add_src_port(p)
602
+ if Resolver.check_port(p, @src_port)
603
+ a = Resolver.get_ports_from(p)
604
+ a.each do |x|
605
+ if (@src_port.length > 0) && (x == 0)
606
+ log_and_raise("src_port of 0 only allowed as only src_port value (currently #{@src_port.length} values",
607
+ ArgumentError)
608
+ end
609
+ @src_port.push(x)
610
+ end
611
+ end
612
+ update
613
+ end
614
+
615
+ def Resolver.check_port(p, src_port=[])
616
+ if p.class != Fixnum
617
+ tmp_src_ports = Array.new(src_port)
618
+ p.each do |x|
619
+ unless Resolver.check_port(x, tmp_src_ports)
620
+ return false
621
+ end
622
+ tmp_src_ports.push(x)
623
+ end
624
+ return true
625
+ end
626
+ if Resolver.port_in_range(p)
627
+ return ! ((p == 0) && (src_port.length > 0))
628
+ else
629
+ Dnsruby.log.error("Illegal port (#{p})")
630
+ log_and_raise("Illegal port #{p}", ArgumentError)
631
+ end
632
+ end
633
+
634
+ def Resolver.port_in_range(p)
635
+ (p == 0) || ((p >= 50000) && (p <= 65535))
636
+ end
637
+
638
+ def Resolver.get_ports_from(p)
639
+ a = []
640
+ if p.class == Fixnum
641
+ a = [p]
642
+ else
643
+ p.each do |x|
644
+ a.push(x)
645
+ end
646
+ end
647
+ a
648
+ end
649
+
650
+ def use_tcp=(on)
651
+ @use_tcp = on
652
+ update
653
+ end
654
+
655
+ def no_tcp=(on)
656
+ @no_tcp=on
657
+ update
658
+ end
659
+
660
+ # Sets the TSIG to sign outgoing messages with.
661
+ # Pass in either a Dnsruby::RR::TSIG, or a key_name and key (or just a key)
662
+ # Pass in nil to stop tsig signing.
663
+ # * res.tsig=(tsig_rr)
664
+ # * res.tsig=(key_name, key) # defaults to hmac-md5
665
+ # * res.tsig=(key_name, key, alg) # e.g. alg = 'hmac-sha1'
666
+ # * res.tsig=nil # Stop the resolver from signing
667
+ def tsig=(t)
668
+ @tsig = t
669
+ update
670
+ end
671
+
672
+ def create_tsig_options(args)
673
+ if args.size > 3
674
+ log_and_raise("Illegal number of arguments (#{args.size}; must be 1, 2, or 3.")
675
+ end
676
+
677
+ options = { type: Types.TSIG, klass: Classes.ANY }
678
+ if args.length >= 2
679
+ options.merge!({ name: args[0], key: args[1] })
680
+ end
681
+ if args.length == 3
682
+ options[:algorithm] == args[2]
683
+ end
684
+
685
+ options
686
+ end; private :create_tsig_options
687
+
688
+
689
+ def Resolver.get_tsig(args)
690
+
691
+ tsig = nil
692
+
693
+ if args.length == 1
694
+ if args[0]
695
+ if args[0].instance_of?(RR::TSIG)
696
+ tsig = args[0]
697
+ elsif args[0].instance_of?(Array)
698
+ tsig = RR.new_from_hash(create_tsig_options(args))
699
+ end
700
+ else
701
+ # Dnsruby.log.debug{'TSIG signing switched off'}
702
+ return nil
703
+ end
704
+ else
705
+ tsig = RR.new_from_hash(create_tsig_options(args))
706
+ end
707
+ Dnsruby.log.info{"TSIG signing now using #{tsig.name}, key=#{tsig.key}"}
708
+ tsig
709
+ end
710
+
711
+
712
+ def ignore_truncation=(on)
713
+ @ignore_truncation = on
714
+ update
715
+ end
716
+
717
+ def src_address=(a)
718
+ @src_address = a
719
+ update
720
+ end
721
+
722
+ def src_address6=(a)
723
+ @src_address6 = a
724
+ update
725
+ end
726
+
727
+ def port=(a)
728
+ @port = a
729
+ update
730
+ end
731
+
732
+ def persistent_tcp=(on)
733
+ @persistent_tcp = on
734
+ update
735
+ end
736
+
737
+ def persistent_udp=(on)
738
+ @persistent_udp = on
739
+ update
740
+ end
741
+
742
+ def do_caching=(on)
743
+ @do_caching=on
744
+ update
745
+ end
746
+
747
+ def recurse=(a)
748
+ @recurse = a
749
+ update
750
+ end
751
+
752
+ def dnssec=(d)
753
+ @dnssec = d
754
+ if d
755
+ # Set the UDP size (RFC 4035 section 4.1)
756
+ if @udp_size < MinDnssecUdpSize
757
+ self.udp_size = MinDnssecUdpSize
758
+ end
759
+ end
760
+ update
761
+ end
762
+
763
+ def udp_size=(s)
764
+ @udp_size = s
765
+ update
766
+ end
767
+
768
+ def single_res_mutex # :nodoc: all
769
+ @single_res_mutex
770
+ end
771
+
772
+ def generate_timeouts(base=0) # :nodoc: all
773
+ # These should be be pegged to the single_resolver they are targetting :
774
+ # e.g. timeouts[timeout1]=nameserver
775
+ timeouts = {}
776
+ retry_delay = @retry_delay
777
+ # @single_res_mutex.synchronize {
778
+ @retry_times.times do |retry_count|
779
+ if retry_count > 0
780
+ retry_delay *= 2
781
+ end
782
+
783
+ @single_resolvers.delete(nil) # Just in case...
784
+ @single_resolvers.each_index do |i|
785
+ res = @single_resolvers[i]
786
+ offset = (i * @retry_delay.to_f / @single_resolvers.length)
787
+ if retry_count == 0
788
+ timeouts[base + offset]=[res, retry_count]
789
+ else
790
+ if timeouts.has_key?(base + retry_delay + offset)
791
+ log_and_raise('Duplicate timeout key!')
792
+ end
793
+ timeouts[base + retry_delay + offset]=[res, retry_count]
794
+ end
795
+ end
796
+ end
797
+ # }
798
+ timeouts
799
+ end
800
+ end
801
+
802
+
803
+ # This class implements the I/O using pure Ruby, with no dependencies.
804
+ # Support for EventMachine has been deprecated.
805
+ class ResolverRuby # :nodoc: all
806
+ def initialize(parent)
807
+ reset_attributes
808
+ @parent=parent
809
+ end
810
+ def reset_attributes # :nodoc: all
811
+ # data structures
812
+ # @mutex=Mutex.new
813
+ @query_list = {}
814
+ @timeouts = {}
815
+ end
816
+ def send_async(msg, client_queue, client_query_id=nil)
817
+ # This is the whole point of the Resolver class.
818
+ # We want to use multiple SingleResolvers to run a query.
819
+ # So we kick off a system with select_thread where we send
820
+ # a query with a queue, but log ourselves as observers for that
821
+ # queue. When a new response is pushed on to the queue, then the
822
+ # select thread will call this class' handler method IN THAT THREAD.
823
+ # When the final response is known, this class then sticks it in
824
+ # to the client queue.
825
+
826
+ q = Queue.new
827
+ if client_query_id.nil?
828
+ client_query_id = Time.now + rand(10000)
829
+ end
830
+
831
+ unless client_queue.kind_of?(Queue)
832
+ log_and_raise('Wrong type for client_queue in Resolver# send_async')
833
+ # @TODO@ Handle different queue tuples - push this to generic send_error method
834
+ client_queue.push([client_query_id, ArgumentError.new('Wrong type of client_queue passed to Dnsruby::Resolver# send_async - should have been Queue, was #{client_queue.class}')])
835
+ return
836
+ end
837
+
838
+ unless msg.kind_of?Message
839
+ Dnsruby.log.error{'Wrong type for msg in Resolver# send_async'}
840
+ # @TODO@ Handle different queue tuples - push this to generic send_error method
841
+ client_queue.push([client_query_id, ArgumentError.new("Wrong type of msg passed to Dnsruby::Resolver# send_async - should have been Message, was #{msg.class}")])
842
+ return
843
+ end
844
+
845
+ tick_needed = false
846
+ # add to our data structures
847
+ # @mutex.synchronize{
848
+ @parent.single_res_mutex.synchronize {
849
+ tick_needed = true if @query_list.empty?
850
+ if @query_list.has_key?(client_query_id)
851
+ Dnsruby.log.error("Duplicate query id requested (#{client_query_id}")
852
+ # @TODO@ Handle different queue tuples - push this to generic send_error method
853
+ client_queue.push([client_query_id, ArgumentError.new('Client query ID already in use')])
854
+ return
855
+ end
856
+ outstanding = []
857
+ @query_list[client_query_id]=[msg, client_queue, q, outstanding]
858
+
859
+ query_timeout = Time.now + @parent.query_timeout
860
+ if @parent.query_timeout == 0
861
+ query_timeout = Time.now + 31536000 # a year from now
862
+ end
863
+ @timeouts[client_query_id] = [query_timeout, generate_timeouts]
864
+ }
865
+
866
+ # Now do querying stuff using SingleResolver
867
+ # All this will be handled by the tick method (if we have 0 as the first timeout)
868
+ st = SelectThread.instance
869
+ st.add_observer(q, self)
870
+ tick if tick_needed
871
+ client_query_id
872
+ end
873
+
874
+ def generate_timeouts # :nodoc: all
875
+ # Create the timeouts for the query from the retry_times and retry_delay attributes.
876
+ # These are created at the same time in case the parameters change during the life of the query.
877
+ #
878
+ # These should be absolute, rather than relative
879
+ # The first value should be Time.now[
880
+ @parent.generate_timeouts(Time.now)
881
+ end
882
+
883
+ # Close the Resolver. Unfinished queries are terminated with OtherResolvError.
884
+ def close
885
+ # @mutex.synchronize {
886
+ @parent.single_res_mutex.synchronize {
887
+ @query_list.each do |client_query_id, values|
888
+ _msg, client_queue, q, _outstanding = values
889
+ send_result_and_stop_querying(client_queue, client_query_id, q, nil,
890
+ OtherResolvError.new('Resolver closing!'))
891
+ end
892
+ }
893
+ end
894
+
895
+ # MUST BE CALLED IN A SYNCHRONIZED BLOCK!
896
+ #
897
+ # Send the result back to the client, and close the socket for that query by removing
898
+ # the query from the select thread.
899
+ def send_result_and_stop_querying(client_queue, client_query_id, select_queue, msg, error) # :nodoc: all
900
+ stop_querying(client_query_id)
901
+ send_result(client_queue, client_query_id, select_queue, msg, error)
902
+ end
903
+
904
+ # MUST BE CALLED IN A SYNCHRONIZED BLOCK!
905
+ #
906
+ # Stops send any more packets for a client-level query
907
+ def stop_querying(client_query_id) # :nodoc: all
908
+ @timeouts.delete(client_query_id)
909
+ end
910
+
911
+ # MUST BE CALLED IN A SYNCHRONIZED BLOCK!
912
+ #
913
+ # Sends the result to the client's queue, and removes the queue observer from the select thread
914
+ def send_result(client_queue, client_query_id, select_queue, msg, error) # :nodoc: all
915
+ stop_querying(client_query_id) # @TODO@ !
916
+ # We might still get some callbacks, which we should ignore
917
+ st = SelectThread.instance
918
+ st.remove_observer(select_queue, self)
919
+ # @mutex.synchronize{
920
+ # Remove the query from all of the data structures
921
+ @query_list.delete(client_query_id)
922
+ # }
923
+ # Return the response to the client
924
+ client_queue.push([client_query_id, msg, error])
925
+ end
926
+
927
+ # This method is called twice a second from the select loop, in the select thread.
928
+ # It should arguably be called from another worker thread... (which also handles the queue)
929
+ # Each tick, we check if any timeouts have occurred. If so, we take the appropriate action :
930
+ # Return a timeout to the client, or send a new query
931
+ def tick # :nodoc: all
932
+ # Handle the tick
933
+ # Do we have any retries due to be sent yet?
934
+ # @mutex.synchronize{
935
+ @parent.single_res_mutex.synchronize {
936
+ time_now = Time.now
937
+ @timeouts.keys.each do |client_query_id|
938
+ msg, client_queue, select_queue, outstanding = @query_list[client_query_id]
939
+ query_timeout, timeouts = @timeouts[client_query_id]
940
+ if query_timeout < Time.now
941
+ # Time the query out
942
+ send_result_and_stop_querying(client_queue, client_query_id, select_queue, nil,
943
+ ResolvTimeout.new('Query timed out'))
944
+ next
945
+ end
946
+ timeouts_done = []
947
+ timeouts.keys.sort.each do |timeout|
948
+ if timeout < time_now
949
+ # Send the next query
950
+ res, retry_count = timeouts[timeout]
951
+ id = [res, msg, client_query_id, retry_count]
952
+ Dnsruby.log.debug("Sending msg to #{res.server}")
953
+ # We should keep a list of the queries which are outstanding
954
+ outstanding.push(id)
955
+ timeouts_done.push(timeout)
956
+ timeouts.delete(timeout)
957
+
958
+ # Pick a new QID here @TODO@ !!!
959
+ # msg.header.id = rand(65535);
960
+ # print "New query : #{new_msg}\n"
961
+ res.send_async(msg, select_queue, id)
962
+ else
963
+ break
964
+ end
965
+ end
966
+ timeouts_done.each { |t| timeouts.delete(t) }
967
+ end
968
+ }
969
+ end
970
+
971
+ # This method is called by the SelectThread (in the select thread) when the queue has a new item on it.
972
+ # The queue interface is used to separate producer/consumer threads, but we're using it here in one thread.
973
+ # It's probably a good idea to create a new "worker thread" to take items from the select thread queue and
974
+ # call this method in the worker thread.
975
+ #
976
+ def handle_queue_event(queue, id) # :nodoc: all
977
+ # Time to process a new queue event.
978
+ # If we get a callback for an ID we don't know about, don't worry -
979
+ # just ignore it. It may be for a query we've already completed.
980
+ #
981
+ # So, get the next response from the queue (presuming there is one!)
982
+ #
983
+ # @TODO@ Tick could poll the queue and then call this method if needed - no need for observer interface.
984
+ # @TODO@ Currently, tick and handle_queue_event called from select_thread - could have thread chuck events in to tick_queue. But then, clients would have to call in on other thread!
985
+ #
986
+ # So - two types of response :
987
+ # 1) we've got a coherent response (or error) - stop sending more packets for that query!
988
+ # 2) we've validated the response - it's ready to be sent to the client
989
+ #
990
+ # so need two more methods :
991
+ # handleValidationResponse : basically calls send_result_and_stop_querying and
992
+ # handleValidationError : does the same as handleValidationResponse, but for errors
993
+ # can leave handleError alone
994
+ # but need to change handleResponse to stop sending, rather than send_result_and_stop_querying.
995
+ #
996
+ # @TODO@ Also, we could really do with a MaxValidationTimeout - if validation not OK within
997
+ # this time, then raise Timeout (and stop validation)?
998
+ #
999
+ # @TODO@ Also, should there be some facility to stop validator following same chain
1000
+ # concurrently?
1001
+ #
1002
+ # @TODO@ Also, should have option to speak only to configured resolvers (not follow authoritative chain)
1003
+ #
1004
+ if queue.empty?
1005
+ log_and_raise('Severe internal error - Queue empty in handle_queue_event')
1006
+ end
1007
+ event_id, event_type, response, error = queue.pop
1008
+ # We should remove this packet from the list of outstanding packets for this query
1009
+ _resolver, _msg, client_query_id, _retry_count = id
1010
+ if id != event_id
1011
+ log_and_raise("Serious internal error!! #{id} expected, #{event_id} received")
1012
+ end
1013
+ # @mutex.synchronize{
1014
+ @parent.single_res_mutex.synchronize {
1015
+ if @query_list[client_query_id] == nil
1016
+ # print "Dead query response - ignoring\n"
1017
+ Dnsruby.log.debug{'Ignoring response for dead query'}
1018
+ return
1019
+ end
1020
+ _msg, _client_queue, _select_queue, outstanding = @query_list[client_query_id]
1021
+ if event_type == Resolver::EventType::RECEIVED ||
1022
+ event_type == Resolver::EventType::ERROR
1023
+ unless outstanding.include?(id)
1024
+ log_and_raise("Query id not on outstanding list! #{outstanding.length} items. #{id} not on #{outstanding}")
1025
+ end
1026
+ outstanding.delete(id)
1027
+ end
1028
+ # }
1029
+ if event_type == Resolver::EventType::RECEIVED
1030
+ # if (event.kind_of?(Exception))
1031
+ if error
1032
+ handle_error_response(queue, event_id, error, response)
1033
+ else # if event.kind_of?(Message)
1034
+ handle_response(queue, event_id, response)
1035
+ # else
1036
+ # Dnsruby.log.error('Random object #{event.class} returned through queue to Resolver')
1037
+ end
1038
+ elsif event_type == Resolver::EventType::VALIDATED
1039
+ if error
1040
+ handle_validation_error(queue, event_id, error, response)
1041
+ else
1042
+ handle_validation_response(queue, event_id, response)
1043
+ end
1044
+ elsif event_type == Resolver::EventType::ERROR
1045
+ handle_error_response(queue, event_id, error, response)
1046
+ else
1047
+ # print "ERROR - UNKNOWN EVENT TYPE IN RESOLVER : #{event_type}\n"
1048
+ TheLog.error("ERROR - UNKNOWN EVENT TYPE IN RESOLVER : #{event_type}")
1049
+ end
1050
+ }
1051
+ end
1052
+
1053
+ def handle_error_response(select_queue, query_id, error, response) # :nodoc: all
1054
+ # Handle an error
1055
+ # @mutex.synchronize{
1056
+ Dnsruby.log.debug{"handling error #{error.class}, #{error}"}
1057
+ # Check what sort of error it was :
1058
+ resolver, _msg, client_query_id, _retry_count = query_id
1059
+ _msg, client_queue, select_queue, outstanding = @query_list[client_query_id]
1060
+ if error.kind_of?(ResolvTimeout)
1061
+ # - if it was a timeout, then check which number it was, and how many retries are expected on that server
1062
+ # - if it was the last retry, on the last server, then return a timeout to the client (and clean up)
1063
+ # - otherwise, continue
1064
+ # Do we have any more packets to send to this resolver?
1065
+
1066
+ decrement_resolver_priority(resolver)
1067
+ timeouts = @timeouts[client_query_id]
1068
+ if outstanding.empty? && timeouts && timeouts[1].values.empty?
1069
+ Dnsruby.log.debug{'Sending timeout to client'}
1070
+ send_result_and_stop_querying(client_queue, client_query_id, select_queue, response, error)
1071
+ end
1072
+ elsif error.kind_of?(NXDomain)
1073
+ # - if it was an NXDomain, then return that to the client, and stop all new queries (and clean up)
1074
+ # send_result_and_stop_querying(client_queue, client_query_id, select_queue, response, error)
1075
+ increment_resolver_priority(resolver) unless response.cached
1076
+ stop_querying(client_query_id)
1077
+ # @TODO@ Does the client want notified at this point?
1078
+ else
1079
+ # - if it was any other error, then remove that server from the list for that query
1080
+ # If a Too Many Open Files error, then don't remove, but let retry work.
1081
+ timeouts = @timeouts[client_query_id]
1082
+ unless error.to_s =~ /Errno::EMFILE/
1083
+ Dnsruby.log.debug{"Removing #{resolver.server} from resolver list for this query"}
1084
+ if timeouts
1085
+ timeouts[1].each do |key, value|
1086
+ res = value[0]
1087
+ if res == resolver
1088
+ timeouts[1].delete(key)
1089
+ end
1090
+ end
1091
+ end
1092
+ # Also stick it to the back of the list for future queries
1093
+ demote_resolver(resolver)
1094
+ else
1095
+ Dnsruby.log.debug("NOT Removing #{resolver.server} due to Errno::EMFILE")
1096
+ end
1097
+ # - if it was the last server, then return an error to the client (and clean up)
1098
+ if outstanding.empty? && ((!timeouts) || (timeouts && timeouts[1].values.empty?))
1099
+ # if outstanding.empty?
1100
+ Dnsruby.log.debug{'Sending error to client'}
1101
+ send_result_and_stop_querying(client_queue, client_query_id, select_queue, response, error)
1102
+ end
1103
+ end
1104
+ # @TODO@ If we're still sending packets for this query, but none are outstanding, then
1105
+ # jumpstart the next query?
1106
+ # }
1107
+ end
1108
+
1109
+ # TO BE CALLED IN A SYNCHRONIZED BLOCK
1110
+ def increment_resolver_priority(res)
1111
+ TheLog.debug("Incrementing resolver priority for #{res.server}\n")
1112
+ # @parent.single_res_mutex.synchronize {
1113
+ index = @parent.single_resolvers.index(res)
1114
+ if index > 0
1115
+ @parent.single_resolvers.delete(res)
1116
+ @parent.single_resolvers.insert(index-1,res)
1117
+ end
1118
+ # }
1119
+ end
1120
+
1121
+ # TO BE CALLED IN A SYNCHRONIZED BLOCK
1122
+ def decrement_resolver_priority(res)
1123
+ TheLog.debug("Decrementing resolver priority for #{res.server}\n")
1124
+ # @parent.single_res_mutex.synchronize {
1125
+ index = @parent.single_resolvers.index(res)
1126
+ if index < @parent.single_resolvers.length
1127
+ @parent.single_resolvers.delete(res)
1128
+ @parent.single_resolvers.insert(index+1,res)
1129
+ end
1130
+ # }
1131
+ end
1132
+
1133
+ # TO BE CALLED IN A SYNCHRONIZED BLOCK
1134
+ def demote_resolver(res)
1135
+ TheLog.debug("Demoting resolver priority for #{res.server} to bottom\n")
1136
+ # @parent.single_res_mutex.synchronize {
1137
+ @parent.single_resolvers.delete(res)
1138
+ @parent.single_resolvers.push(res)
1139
+ # }
1140
+ end
1141
+
1142
+ def handle_response(select_queue, query_id, response) # :nodoc: all
1143
+ # Handle a good response
1144
+ # Should also stick resolver more to the front of the list for future queries
1145
+ Dnsruby.log.debug('Handling good response')
1146
+ resolver, _msg, client_query_id, _retry_count = query_id
1147
+ increment_resolver_priority(resolver) unless response.cached
1148
+ # @mutex.synchronize{
1149
+ _query, _client_queue, s_queue, _outstanding = @query_list[client_query_id]
1150
+ if s_queue != select_queue
1151
+ log_and_raise("Serious internal error : expected select queue #{s_queue}, got #{select_queue}")
1152
+ end
1153
+ stop_querying(client_query_id)
1154
+ # @TODO@ Does the client want notified at this point?
1155
+ # client_queue.push([client_query_id, Resolver::EventType::RECEIVED, msg, nil])
1156
+ # }
1157
+ end
1158
+
1159
+ def handle_validation_response(select_queue, query_id, response) # :nodoc: all
1160
+ _resolver, _msg, client_query_id, _retry_count = query_id
1161
+ # @mutex.synchronize {
1162
+ _query, client_queue, s_queue, _outstanding = @query_list[client_query_id]
1163
+ if s_queue != select_queue
1164
+ log_and_raise("Serious internal error : expected select queue #{s_queue}, got #{select_queue}")
1165
+ end
1166
+ if response.rcode == RCode.NXDOMAIN
1167
+ send_result(client_queue, client_query_id, select_queue, response, NXDomain.new)
1168
+ else
1169
+ # @TODO@ Was there an error validating? Should we raise an exception for certain security levels?
1170
+ # This should be configurable by the client.
1171
+ send_result(client_queue, client_query_id, select_queue, response, nil)
1172
+ # }
1173
+ end
1174
+ end
1175
+
1176
+ def handle_validation_error(select_queue, query_id, error, response)
1177
+ _resolver, _msg, client_query_id, _retry_count = query_id
1178
+ _query, client_queue, s_queue, _outstanding = @query_list[client_query_id]
1179
+ if s_queue != select_queue
1180
+ log_and_raise("Serious internal error : expected select queue #{s_queue}, got #{select_queue}")
1181
+ end
1182
+ # For some errors, we immediately send result. For others, should we retry?
1183
+ # Either :
1184
+ # handle_error_response(queue, event_id, error, response)
1185
+ # Or:
1186
+ send_result(client_queue, client_query_id, select_queue, response, error)
1187
+ #
1188
+ #
1189
+ end
1190
+ end
1191
+ end
1192
+ require 'dnsruby/single_resolver'