rubydns 0.6.7 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -19,6 +19,7 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require 'rubydns/message'
22
+ require 'rubydns/binary_string'
22
23
 
23
24
  module RubyDNS
24
25
  class InvalidProtocolError < StandardError
@@ -117,6 +118,7 @@ module RubyDNS
117
118
  try_next_server!
118
119
  end
119
120
 
121
+ # Once either an exception or message is received, we update the status of this request.
120
122
  def process_response!(response)
121
123
  finish_request!
122
124
 
@@ -142,6 +144,7 @@ module RubyDNS
142
144
 
143
145
  private
144
146
 
147
+ # Closes any connections and cancels any timeout.
145
148
  def finish_request!
146
149
  cancel_timeout
147
150
 
@@ -18,21 +18,127 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
+ require 'fiber'
22
+
21
23
  require 'rubydns/transaction'
22
24
  require 'rubydns/extensions/logger'
23
25
 
24
26
  module RubyDNS
25
27
 
26
- # This class provides the core of the DSL. It contains a list of rules which
27
- # are used to match against incoming DNS questions. These rules are used to
28
- # generate responses which are either DNS resource records or failures.
29
28
  class Server
29
+ # The default server interfaces
30
+ DEFAULT_INTERFACES = [[:udp, "0.0.0.0", 53], [:tcp, "0.0.0.0", 53]]
31
+
32
+ # Instantiate a server with a block
33
+ #
34
+ # server = Server.new do
35
+ # match(/server.mydomain.com/, IN::A) do |transaction|
36
+ # transaction.respond!("1.2.3.4")
37
+ # end
38
+ # end
39
+ #
40
+ def initialize
41
+ @logger = Logger.new($stderr)
42
+ end
43
+
44
+ attr_accessor :logger
45
+
46
+ # Fire the named event as part of running the server.
47
+ def fire(event_name)
48
+ end
49
+
50
+ # Give a name and a record type, try to match a rule and use it for processing the given arguments.
51
+ def process(name, resource_class, transaction)
52
+ raise NotImplementedError.new
53
+ end
54
+
55
+ # Process a block with the current fiber. To resume processing from the block, call `fiber.resume`. You shouldn't call `fiber.resume` until after the top level block has returned.
56
+ def defer(&block)
57
+ fiber = Fiber.current
58
+
59
+ yield(fiber)
60
+
61
+ Fiber.yield
62
+ end
63
+
64
+ # Process an incoming DNS message. Returns a serialized message to be sent back to the client.
65
+ def process_query(query, options = {}, &block)
66
+ # Setup answer
67
+ answer = Resolv::DNS::Message::new(query.id)
68
+ answer.qr = 1 # 0 = Query, 1 = Response
69
+ answer.opcode = query.opcode # Type of Query; copy from query
70
+ answer.aa = 1 # Is this an authoritative response: 0 = No, 1 = Yes
71
+ answer.rd = query.rd # Is Recursion Desired, copied from query
72
+ answer.ra = 0 # Does name server support recursion: 0 = No, 1 = Yes
73
+ answer.rcode = 0 # Response code: 0 = No errors
74
+
75
+ Fiber.new do
76
+ transaction = nil
77
+
78
+ begin
79
+ query.question.each do |question, resource_class|
80
+ @logger.debug "Processing question #{question} #{resource_class}..."
81
+
82
+ transaction = Transaction.new(self, query, question, resource_class, answer, options)
83
+
84
+ transaction.process
85
+ end
86
+ rescue
87
+ @logger.error "Exception thrown while processing #{transaction}!"
88
+ RubyDNS.log_exception(@logger, $!)
89
+
90
+ answer.rcode = Resolv::DNS::RCode::ServFail
91
+ end
92
+
93
+ yield answer
94
+ end.resume
95
+ end
96
+
97
+ #
98
+ # By default the server runs on port 53, both TCP and UDP, which is usually a priviledged port and requires root access to bind. You can change this by specifying `options[:listen]` which should contain an array of `[protocol, interface address, port]` specifications.
99
+ #
100
+ # INTERFACES = [[:udp, "0.0.0.0", 5300]]
101
+ # RubyDNS::run_server(:listen => INTERFACES) do
102
+ # ...
103
+ # end
104
+ #
105
+ # You can specify already connected sockets if need be:
106
+ #
107
+ # socket = UDPSocket.new; socket.bind("0.0.0.0", 53)
108
+ # Process::Sys.setuid(server_uid)
109
+ # INTERFACES = [socket]
110
+ #
111
+ def run(options = {})
112
+ @logger.info "Starting RubyDNS server (v#{RubyDNS::VERSION})..."
113
+
114
+ interfaces = options[:listen] || DEFAULT_INTERFACES
115
+
116
+ fire(:setup)
117
+
118
+ # Setup server sockets
119
+ interfaces.each do |spec|
120
+ @logger.info "Listening on #{spec.join(':')}"
121
+ if spec[0] == :udp
122
+ EventMachine.open_datagram_socket(spec[1], spec[2], UDPHandler, self)
123
+ elsif spec[0] == :tcp
124
+ EventMachine.start_server(spec[1], spec[2], TCPHandler, self)
125
+ end
126
+ end
127
+
128
+ fire(:start)
129
+ end
130
+ end
131
+
132
+ # Provides the core of the RubyDNS domain-specific language (DSL). It contains a list of rules which are used to match against incoming DNS questions. These rules are used to generate responses which are either DNS resource records or failures.
133
+ class RuleBasedServer < Server
134
+ # Represents a single rule in the server.
30
135
  class Rule
31
136
  def initialize(pattern, callback)
32
137
  @pattern = pattern
33
138
  @callback = callback
34
139
  end
35
140
 
141
+ # Returns true if the name and resource_class are sufficient:
36
142
  def match(name, resource_class)
37
143
  # If the pattern doesn't specify any resource classes, we implicitly pass this test:
38
144
  return true if @pattern.size < 2
@@ -45,6 +151,7 @@ module RubyDNS
45
151
  end
46
152
  end
47
153
 
154
+ # Invoke the rule, if it matches the incoming request, it is evaluated and returns `true`, otherwise returns `false`.
48
155
  def call(server, name, resource_class, *args)
49
156
  unless match(name, resource_class)
50
157
  server.logger.debug "Resource class #{resource_class} failed to match #{@pattern[1].inspect}!"
@@ -52,27 +159,38 @@ module RubyDNS
52
159
  return false
53
160
  end
54
161
 
55
- # Match succeeded against name?
162
+ # Does this rule match against the supplied name?
56
163
  case @pattern[0]
57
164
  when Regexp
58
165
  match_data = @pattern[0].match(name)
166
+
59
167
  if match_data
60
168
  server.logger.debug "Regexp pattern matched with #{match_data.inspect}."
61
- return @callback[*args, match_data]
169
+
170
+ @callback[*args, match_data]
171
+
172
+ return true
62
173
  end
63
174
  when String
64
175
  if @pattern[0] == name
65
176
  server.logger.debug "String pattern matched."
66
- return @callback[*args]
177
+
178
+ @callback[*args]
179
+
180
+ return true
67
181
  end
68
182
  else
69
183
  if (@pattern[0].call(name, resource_class) rescue false)
70
184
  server.logger.debug "Callable pattern matched."
71
- return @callback[*args]
185
+
186
+ @callback[*args]
187
+
188
+ return true
72
189
  end
73
190
  end
74
191
 
75
192
  server.logger.debug "No pattern matched."
193
+
76
194
  # We failed to match the pattern.
77
195
  return false
78
196
  end
@@ -84,19 +202,19 @@ module RubyDNS
84
202
 
85
203
  # Instantiate a server with a block
86
204
  #
87
- # server = Server.new do
88
- # match(/server.mydomain.com/, IN::A) do |transaction|
89
- # transaction.respond!("1.2.3.4")
90
- # end
91
- # end
205
+ # server = Server.new do
206
+ # match(/server.mydomain.com/, IN::A) do |transaction|
207
+ # transaction.respond!("1.2.3.4")
208
+ # end
209
+ # end
92
210
  #
93
211
  def initialize(&block)
212
+ super()
213
+
94
214
  @events = {}
95
215
  @rules = []
96
216
  @otherwise = nil
97
-
98
- @logger = Logger.new($stderr)
99
-
217
+
100
218
  if block_given?
101
219
  instance_eval &block
102
220
  end
@@ -104,23 +222,21 @@ module RubyDNS
104
222
 
105
223
  attr_accessor :logger
106
224
 
107
- # This function connects a pattern with a block. A pattern is either
108
- # a String or a Regex instance. Optionally, a second argument can be
109
- # provided which is either a String, Symbol or Array of resource record
110
- # types which the rule matches against.
225
+ # This function connects a pattern with a block. A pattern is either a String or a Regex instance. Optionally, a second argument can be provided which is either a String, Symbol or Array of resource record types which the rule matches against.
111
226
  #
112
- # match("www.google.com")
113
- # match("gmail.com", IN::MX)
114
- # match(/g?mail.(com|org|net)/, [IN::MX, IN::A])
227
+ # match("www.google.com")
228
+ # match("gmail.com", IN::MX)
229
+ # match(/g?mail.(com|org|net)/, [IN::MX, IN::A])
115
230
  #
116
231
  def match(*pattern, &block)
117
232
  @rules << Rule.new(pattern, block)
118
233
  end
119
234
 
120
235
  # Register a named event which may be invoked later using #fire
121
- # on(:start) do |server|
122
- # RExec.change_user(RUN_AS)
123
- # end
236
+ #
237
+ # on(:start) do |server|
238
+ # RExec.change_user(RUN_AS)
239
+ # end
124
240
  def on(event_name, &block)
125
241
  @events[event_name] = block
126
242
  end
@@ -134,48 +250,51 @@ module RubyDNS
134
250
  end
135
251
  end
136
252
 
137
- # Specify a default block to execute if all other rules fail to match.
138
- # This block is typially used to pass the request on to another server
139
- # (i.e. recursive request).
253
+ # Specify a default block to execute if all other rules fail to match. This block is typially used to pass the request on to another server (i.e. recursive request).
140
254
  #
141
- # otherwise do |transaction|
142
- # transaction.passthrough!($R)
143
- # end
255
+ # otherwise do |transaction|
256
+ # transaction.passthrough!($R)
257
+ # end
144
258
  #
145
259
  def otherwise(&block)
146
260
  @otherwise = block
147
261
  end
148
-
262
+
263
+ # If you match a rule, but decide within the rule that it isn't the correct one to use, you can call `next!` to evaluate the next rule - in other words, to continue falling down through the list of rules.
149
264
  def next!
150
265
  throw :next
151
266
  end
152
-
153
- # Give a name and a record type, try to match a rule and use it for
154
- # processing the given arguments.
155
- #
156
- # If a rule returns false, it is considered that the rule failed and
157
- # futher matching is carried out.
267
+
268
+ # Give a name and a record type, try to match a rule and use it for processing the given arguments.
158
269
  def process(name, resource_class, *args)
159
270
  @logger.debug "Searching for #{name} #{resource_class.name}"
160
-
271
+
161
272
  @rules.each do |rule|
162
273
  @logger.debug "Checking rule #{rule}..."
163
-
274
+
164
275
  catch (:next) do
165
276
  # If the rule returns true, we assume that it was successful and no further rules need to be evaluated.
166
- return true if rule.call(self, name, resource_class, *args)
277
+ return if rule.call(self, name, resource_class, *args)
167
278
  end
168
279
  end
169
-
280
+
170
281
  if @otherwise
171
282
  @otherwise.call(*args)
172
283
  else
173
284
  @logger.warn "Failed to handle #{name} #{resource_class.name}!"
174
285
  end
175
286
  end
176
-
177
- # Process an incoming DNS message. Returns a serialized message to be
178
- # sent back to the client.
287
+
288
+ # Process a block with the current fiber. To resume processing from the block, call `fiber.resume`. You shouldn't call `fiber.resume` until after the top level block has returned.
289
+ def defer(&block)
290
+ fiber = Fiber.current
291
+
292
+ yield(fiber)
293
+
294
+ Fiber.yield
295
+ end
296
+
297
+ # Process an incoming DNS message. Returns a serialized message to be sent back to the client.
179
298
  def process_query(query, options = {}, &block)
180
299
  # Setup answer
181
300
  answer = Resolv::DNS::Message::new(query.id)
@@ -185,57 +304,27 @@ module RubyDNS
185
304
  answer.rd = query.rd # Is Recursion Desired, copied from query
186
305
  answer.ra = 0 # Does name server support recursion: 0 = No, 1 = Yes
187
306
  answer.rcode = 0 # Response code: 0 = No errors
188
-
189
- # 1/ This chain contains a reverse list of question lambdas.
190
- chain = []
191
-
192
- # 4/ Finally, the answer is given back to the calling block:
193
- chain << lambda do
194
- @logger.debug "Passing answer back to caller..."
195
- yield answer
196
- end
197
-
198
- # There may be multiple questions per query
199
- query.question.reverse.each do |question, resource_class|
200
- next_link = chain.last
201
-
202
- chain << lambda do
203
- @logger.debug "Processing question #{question} #{resource_class}..."
204
-
205
- transaction = Transaction.new(self, query, question, resource_class, answer, options)
206
-
207
- # Call the next link in the chain:
208
- transaction.callback do
209
- # 3/ ... which calls the previous item in the chain, i.e. the next question to be answered:
210
- next_link.call
211
- end
212
-
213
- # If there was an error, log it and fail:
214
- transaction.errback do |response|
215
- if Exception === response
216
- @logger.error "Exception thrown while processing #{transaction}!"
217
- RubyDNS.log_exception(@logger, response)
218
- else
219
- @logger.error "Failure while processing #{transaction}!"
220
- @logger.error "#{response.inspect}"
221
- end
222
-
223
- answer.rcode = Resolv::DNS::RCode::ServFail
224
-
225
- chain.first.call
226
- end
227
-
228
- begin
229
- # Transaction.process will call succeed if it wasn't deferred:
307
+
308
+ Fiber.new do
309
+ transaction = nil
310
+
311
+ begin
312
+ query.question.each do |question, resource_class|
313
+ @logger.debug "Processing question #{question} #{resource_class}..."
314
+
315
+ transaction = Transaction.new(self, query, question, resource_class, answer, options)
316
+
230
317
  transaction.process
231
- rescue
232
- transaction.fail($!)
233
318
  end
319
+ rescue
320
+ @logger.error "Exception thrown while processing #{transaction}!"
321
+ RubyDNS.log_exception(@logger, $!)
322
+
323
+ answer.rcode = Resolv::DNS::RCode::ServFail
234
324
  end
235
- end
236
-
237
- # 2/ We call the last lambda...
238
- chain.last.call
325
+
326
+ yield answer
327
+ end.resume
239
328
  end
240
329
  end
241
330
  end
@@ -21,11 +21,16 @@
21
21
  require 'eventmachine'
22
22
 
23
23
  module RubyDNS
24
-
25
- # This class provides all details of a single DNS question and answer. This
26
- # is used by the DSL to provide DNS related functionality.
24
+
25
+ class PassthroughError < StandardError
26
+ end
27
+
28
+ # This class provides all details of a single DNS question and answer. This is used by the DSL to provide DNS related functionality.
29
+ #
30
+ # The main functions to complete the trasaction are: {#append!} (evaluate a new query and append the results), {#passthrough!} (pass the query to an upstream server), {#respond!} (compute a specific response) and {#fail!} (fail with an error code).
27
31
  class Transaction
28
- include EventMachine::Deferrable
32
+ # The default time used for responses (24 hours).
33
+ DEFAULT_TTL = 86400
29
34
 
30
35
  def initialize(server, query, question, resource_class, answer, options = {})
31
36
  @server = server
@@ -36,12 +41,12 @@ module RubyDNS
36
41
 
37
42
  @options = options
38
43
 
39
- @deferred = false
40
44
  @question_appended = false
45
+
46
+ @fiber = nil
41
47
  end
42
48
 
43
- # The resource_class that was requested. This is typically used to generate a
44
- # response.
49
+ # The resource_class that was requested. This is typically used to generate a response.
45
50
  attr :resource_class
46
51
 
47
52
  # The incoming query which is a set of questions.
@@ -56,198 +61,154 @@ module RubyDNS
56
61
  # Any options or configuration associated with the given transaction.
57
62
  attr :options
58
63
 
59
- # Return the name of the question, which is typically the requested hostname.
64
+ # The name of the question, which is typically the requested hostname.
60
65
  def name
61
66
  @question.to_s
62
67
  end
63
-
64
- # Suitable for debugging purposes
68
+
69
+ # Shows the question name and resource class. Suitable for debugging purposes.
65
70
  def to_s
66
71
  "#{name} #{@resource_class.name}"
67
72
  end
68
-
69
- # Run a new query through the rules with the given name and resource type. The
70
- # results of this query are appended to the current transactions <tt>answer</tt>.
71
- def append_query!(name, resource_class = nil, options = {})
73
+
74
+ # Run a new query through the rules with the given name and resource type. The results of this query are appended to the current transaction's `answer`.
75
+ def append!(name, resource_class = nil, options = {})
72
76
  Transaction.new(@server, @query, name, resource_class || @resource_class, @answer, options).process
73
77
  end
74
-
75
- def process(&finished)
76
- @server.process(name, @resource_class, self)
77
-
78
- unless @deferred
79
- succeed(self)
80
- end
81
- end
82
-
83
- def defer!
84
- @deferred = true
85
- end
86
-
87
- # Use the given resolver to respond to the question. The default functionality is
88
- # implemented by passthrough, and if a reply is received, it will be merged with the
89
- # answer for this transaction.
78
+
79
+ # Use the given resolver to respond to the question. Uses `passthrough` to do the lookup and merges the result.
80
+ #
81
+ # If a block is supplied, this function yields with the `reply` and `reply_name` if successful. This could be used, for example, to update a cache or modify the reply.
82
+ #
83
+ # If recursion is not requested, the result is `fail!(:Refused)`. This check is ignored if an explicit `options[:name]` or `options[:force]` is given.
90
84
  #
91
- # If a block is supplied, this function yields with the reply and reply_name if
92
- # successful. This could be used, for example, to update a cache or modify the
93
- # reply.
85
+ # If the resolver does not respond, the result is `fail!(:NXDomain)`.
94
86
  def passthrough!(resolver, options = {}, &block)
95
- passthrough(resolver, options) do |response|
96
- if block_given?
97
- yield response
98
- end
99
-
100
- @answer.merge!(response)
87
+ if @query.rd || options[:force] || options[:name]
88
+ passthrough(resolver, options) do |response|
89
+ if block_given?
90
+ yield response
91
+ end
101
92
 
102
- succeed if @deferred
93
+ @answer.merge!(response)
94
+ end
95
+ else
96
+ raise PassthroughError.new("Request is not recursive!")
103
97
  end
104
-
105
- true
106
98
  end
107
99
 
108
- # Use the given resolver to respond to the question. If recursion is
109
- # not requested, the result is <tt>failure!(:Refused)</tt>. If the resolver does
110
- # not respond, the result is <tt>failure!(:NXDomain)</tt>
111
- #
112
- # If a block is supplied, this function yields with the reply and reply_name if
113
- # successful. This block is responsible for doing something useful with the reply,
114
- # such as merging it or conditionally discarding it.
100
+ # Use the given resolver to respond to the question.
101
+ #
102
+ # A block must be supplied, and provided a valid response is received from the upstream server, this function yields with the reply and reply_name.
115
103
  #
116
- # A second argument, options, provides some control over the passthrough process.
117
- # :force => true, ensures that the query will occur even if recursion is not requested.
118
- # :name => resource_name, override the name for the request as it is passed to
119
- # the resolver. Implies :force.
120
- # :resource_class => class, overried the resource class for a request as it is passed
121
- # to the resolver.
104
+ # If `options[:name]` is provided, this overrides the default query name sent to the upstream server. The same logic applies to `options[:resource_class]`.
122
105
  def passthrough(resolver, options = {}, &block)
123
- if @query.rd || options[:force] || options[:name]
124
- # Resolver is asynchronous, so we are now deferred:
125
- defer!
126
-
127
- query_name = options[:name] || name
128
- query_resource_class = options[:resource_class] || resource_class
129
-
106
+ query_name = options[:name] || name
107
+ query_resource_class = options[:resource_class] || resource_class
108
+
109
+ response = @server.defer do |handle|
130
110
  resolver.query(query_name, query_resource_class) do |response|
131
- case response
132
- when RubyDNS::Message
133
- yield response
134
- when RubyDNS::ResolutionFailure
135
- failure!(:ServFail)
136
- else
137
- # This shouldn't ever happen, but if it does for some reason we shouldn't hang.
138
- fail(response)
139
- end
111
+ # Not sure if this is potentially a race condition? Could fiber.resume occur before Fiber.yield?
112
+ handle.resume(response)
140
113
  end
141
- else
142
- failure!(:Refused)
143
114
  end
144
115
 
145
- true
116
+ case response
117
+ when RubyDNS::Message
118
+ yield response
119
+ when RubyDNS::ResolutionFailure
120
+ fail!(:ServFail)
121
+ else
122
+ throw PassthroughError.new("Bad response from query: #{response.inspect}")
123
+ end
146
124
  end
147
-
148
- # Respond to the given query with a resource record. The arguments to this
149
- # function depend on the <tt>resource_class</tt> requested. The last argument
150
- # can optionally be a hash of options.
151
- #
152
- # <tt>options[:resource_class]</tt>:: Override the default <tt>resource_class</tt>
153
- # <tt>options[:ttl]</tt>:: Specify the TTL for the resource
154
- # <tt>options[:name]</tt>:: Override the name (question) of the response.
155
- #
156
- # for A records:: <tt>respond!("1.2.3.4")</tt>
157
- # for MX records:: <tt>respond!(10, Name.create("mail.blah.com"))</tt>
125
+
126
+ # Respond to the given query with a resource record. The arguments to this function depend on the `resource_class` requested. This function instantiates the resource class with the supplied arguments, and then passes it to {#append!}.
158
127
  #
159
- # This function instantiates the resource class with the supplied arguments, and
160
- # then passes it to <tt>append!</tt>.
128
+ # e.g. For A records: `respond!("1.2.3.4")`, For MX records: `respond!(10, Name.create("mail.blah.com"))`
129
+
130
+ # The last argument can optionally be a hash of `options`. If `options[:resource_class]` is provided, it overrides the default resource class of transaction. Additional `options` are passed to {#append!}.
161
131
  #
162
- # See <tt>Resolv::DNS::Resource</tt> for more information about the various
163
- # <tt>resource_class</tt>s available.
164
- # http://www.ruby-doc.org/stdlib/libdoc/resolv/rdoc/index.html
165
- def respond! (*data)
166
- options = data.last.kind_of?(Hash) ? data.pop : {}
132
+ # See `Resolv::DNS::Resource` for more information about the various `resource_classes` available (http://www.ruby-doc.org/stdlib/libdoc/resolv/rdoc/index.html).
133
+ def respond!(*args)
134
+ append_question!
135
+
136
+ options = args.last.kind_of?(Hash) ? args.pop : {}
167
137
  resource_class = options[:resource_class] || @resource_class
168
138
 
169
139
  if resource_class == nil
170
- raise ArgumentError, "Could not instantiate resource #{resource_class}!"
140
+ raise ArgumentError.new("Could not instantiate resource #{resource_class}!")
171
141
  end
172
142
 
173
143
  @server.logger.info "Resource class: #{resource_class.inspect}"
174
- resource = resource_class.new(*data)
144
+ resource = resource_class.new(*args)
175
145
  @server.logger.info "Resource: #{resource.inspect}"
176
146
 
177
- append!(resource, options)
147
+ add([resource], options)
178
148
  end
179
-
180
- # Append a given set of resources to the answer. The last argument can
181
- # optionally be a hash of options.
182
- #
183
- # <tt>options[:ttl]</tt>:: Specify the TTL for the resource
184
- # <tt>options[:name]</tt>:: Override the name (question) of the response.
185
- # <tt>options[:section]</tt>:: Specify whether the response should go in the `:answer`
186
- # `:authority` or `:additional` section.
187
- #
188
- # This function can be used to supply multiple responses to a given question.
189
- # For example, each argument is expected to be an instantiated resource from
190
- # <tt>Resolv::DNS::Resource</tt> module.
191
- def append! (*resources)
192
- append_question!
193
-
194
- if resources.last.kind_of?(Hash)
195
- options = resources.pop
196
- else
197
- options = {}
198
- end
199
-
149
+
150
+ # Append a list of resources.
151
+ #
152
+ # By default resources are appended to the `answers` section, but this can be changed by setting `options[:section]` to either `:authority` or `:additional`.
153
+ #
154
+ # The time-to-live (TTL) of the resources can be specified using `options[:ttl]` and defaults to `DEFAULT_TTL`.
155
+ def add(resources, options = {})
200
156
  # Use the default options if provided:
201
157
  options = options.merge(@options)
202
-
203
- options[:ttl] ||= 16000
204
- options[:name] ||= @question.to_s + "."
205
158
 
206
- method = ("add_" + (options[:section] || 'answer').to_s).to_sym
207
-
159
+ ttl = options[:ttl] || DEFAULT_TTL
160
+ name = options[:name] || @question.to_s + "."
161
+
162
+ section = (options[:section] || 'answer').to_sym
163
+ method = "add_#{section}".to_sym
164
+
208
165
  resources.each do |resource|
209
166
  @server.logger.debug "#{method}: #{resource.inspect} #{resource.class::TypeValue} #{resource.class::ClassValue}"
210
167
 
211
- @answer.send(method, options[:name], options[:ttl], resource)
168
+ @answer.send(method, name, ttl, resource)
212
169
  end
213
-
214
- succeed if @deferred
215
-
216
- true
217
170
  end
218
-
219
- # This function indicates that there was a failure to resolve the given
220
- # question. The single argument must be an integer error code, typically
221
- # given by the constants in <tt>Resolv::DNS::RCode</tt>.
171
+
172
+ # This function indicates that there was a failure to resolve the given question. The single argument must be an integer error code, typically given by the constants in {Resolv::DNS::RCode}.
222
173
  #
223
- # The easiest way to use this function it to simply supply a symbol. Here is
224
- # a list of the most commonly used ones:
174
+ # The easiest way to use this function it to simply supply a symbol. Here is a list of the most commonly used ones:
225
175
  #
226
- # <tt>:NoError</tt>:: No error occurred.
227
- # <tt>:FormErr</tt>:: The incoming data was not formatted correctly.
228
- # <tt>:ServFail</tt>:: The operation caused a server failure (internal error, etc).
229
- # <tt>:NXDomain</tt>:: Non-eXistant Domain (domain record does not exist).
230
- # <tt>:NotImp</tt>:: The operation requested is not implemented.
231
- # <tt>:Refused</tt>:: The operation was refused by the server.
232
- # <tt>:NotAuth</tt>:: The server is not authoritive for the zone.
176
+ # - `:NoError`: No error occurred.
177
+ # - `:FormErr`: The incoming data was not formatted correctly.
178
+ # - `:ServFail`: The operation caused a server failure (internal error, etc).
179
+ # - `:NXDomain`: Non-eXistant Domain (domain record does not exist).
180
+ # - `:NotImp`: The operation requested is not implemented.
181
+ # - `:Refused`: The operation was refused by the server.
182
+ # - `:NotAuth`: The server is not authoritive for the zone.
233
183
  #
234
- # See http://www.rfc-editor.org/rfc/rfc2929.txt for more information
235
- # about DNS error codes (specifically, page 3).
236
- def failure! (rcode)
184
+ # See [RFC2929](http://www.rfc-editor.org/rfc/rfc2929.txt) for more information about DNS error codes (specifically, page 3).
185
+ #
186
+ # **This function will complete deferred transactions.**
187
+ def fail!(rcode)
237
188
  append_question!
238
-
189
+
239
190
  if rcode.kind_of? Symbol
240
191
  @answer.rcode = Resolv::DNS::RCode.const_get(rcode)
241
192
  else
242
193
  @answer.rcode = rcode.to_i
243
194
  end
244
-
245
- # The transaction itself has completed, but contains a failure:
246
- succeed(rcode) if @deferred
247
-
248
- true
249
195
  end
250
-
196
+
197
+ # @deprecated
198
+ def failure!(*args)
199
+ @server.logger.warn "failure! is deprecated, use fail! instead"
200
+
201
+ fail!(*args)
202
+ end
203
+
204
+ # A helper method to process the transaction on the given server. Unless the transaction is deferred, it will {#succeed} on completion.
205
+ def process
206
+ @server.process(name, @resource_class, self)
207
+ end
208
+
209
+ protected
210
+
211
+ # A typical response to a DNS request includes both the question and answer. This helper appends the question unless it looks like the user is already managing that aspect of the response.
251
212
  def append_question!
252
213
  if @answer.question.size == 0
253
214
  @answer.add_question(@question, @resource_class) unless @question_appended