Class: Dnsruby::Resolver
- Inherits:
-
Object
- Object
- Dnsruby::Resolver
- Defined in:
- lib/Dnsruby/Resolver.rb
Overview
Description
Dnsruby::Resolver is a DNS stub resolver. This class uses a set of SingleResolvers to perform queries with retries across multiple nameservers.
The retry policy is a combination of the Net::DNS and dnsjava approach, and has the option of :
-
A total timeout for the query (defaults to 0, meaning “no total timeout”)
-
A retransmission system that targets the namervers concurrently once the first query round is
complete, but in which the total time per query round is split between the number of nameservers
targetted for the first round. and total time for query round is doubled for each query round
Note that, if a total timeout is specified, then that will apply regardless of the retry policy (i.e. it may cut retries short).
Note also that these timeouts are distinct from the SingleResolver’s packet_timeout
Methods
Synchronous
These methods raise an exception or return a response message with rcode==NOERROR
-
Dnsruby::Resolver#send_message(msg)
-
Dnsruby::Resolver#query(name [, type [, klass]])
Asynchronous
These methods use a response queue to return the response and the error
-
Dnsruby::Resolver#send_async(msg, response_queue, query_id)
Event Loop
Dnsruby runs a pure Ruby event loop to handle I/O in a single thread. It is also possible to configure Dnsruby to use EventMachine instead. See the Dnsruby::Resolver::use_eventmachine method for details.
Note that, if using Dnsruby from an EventMachine loop, you will need to tell Dnsruby not to start the event loop itself :
Dnsruby::Resolver::use_eventmachine(true)
Dnsruby::Resolver::start_eventmachine_loop(false)
Constant Summary collapse
- DefaultQueryTimeout =
0
- DefaultPacketTimeout =
10
- DefaultRetryTimes =
4
- DefaultRetryDelay =
5
- DefaultPort =
53
- DefaultUDPSize =
512
- @@event_machine_available =
false
- @@use_eventmachine =
false
- @@start_eventmachine_loop =
true
Instance Attribute Summary collapse
-
#config ⇒ Object
readonly
The current Config.
-
#ignore_truncation ⇒ Object
Should truncation be ignored? i.e.
-
#packet_timeout ⇒ Object
The timeout for any individual packet.
-
#persistent_tcp ⇒ Object
Should TCP queries be sent on a persistent socket?.
-
#persistent_udp ⇒ Object
Should UDP queries be sent on a persistent socket?.
-
#port ⇒ Object
The port to send queries to on the resolver.
-
#query_timeout ⇒ Object
Note that this timeout represents the total time a query may run for - multiple packets can be sent to multiple nameservers in this time.
-
#recurse ⇒ Object
Should the Recursion Desired bit be set?.
-
#retry_delay ⇒ Object
The query will be tried across nameservers retry_times times, with a delay of retry_delay seconds between each retry.
-
#retry_times ⇒ Object
The query will be tried across nameservers retry_times times, with a delay of retry_delay seconds between each retry.
-
#single_resolvers ⇒ Object
readonly
The array of SingleResolvers used for sending query messages.
-
#src_address ⇒ Object
The source address to send queries from.
-
#src_port ⇒ Object
The source port to send queries from.
-
#tsig ⇒ Object
Returns the value of attribute tsig.
-
#udp_size ⇒ Object
The maximum UDP size to be used.
-
#use_tcp ⇒ Object
Should TCP be used as a transport rather than UDP?.
Class Method Summary collapse
-
.eventmachine? ⇒ Boolean
Check whether EventMachine will be used by Dnsruby.
-
.start_eventmachine_loop(on = true) ⇒ Object
If EventMachine is being used, then this method tells Dnsruby whether or not to start the EventMachine loop.
-
.start_eventmachine_loop? ⇒ Boolean
Checks whether Dnsruby will start the EventMachine loop when required.
-
.use_eventmachine(on = true) ⇒ Object
Tell Dnsruby to use EventMachine for I/O.
Instance Method Summary collapse
- #add_config_nameservers ⇒ Object
-
#add_resolver(single) ⇒ Object
Add a new SingleResolver to the list of resolvers this Resolver object will query.
-
#close ⇒ Object
Close the Resolver.
-
#generate_timeouts(base = 0) ⇒ Object
:nodoc: all.
-
#initialize(*args) ⇒ Resolver
constructor
Create a new Resolver object.
- #nameserver=(n) ⇒ Object
-
#query(name, type = Types.A, klass = Classes.IN) ⇒ Object
Query for a n.
-
#reset_attributes ⇒ Object
:nodoc: all.
-
#send_async(*args) ⇒ Object
Asynchronously send a Message to the server.
-
#send_message(message) ⇒ Object
Send a message, and wait for the response.
- #set_config_nameserver(n) ⇒ Object
-
#update ⇒ Object
:nodoc: all.
Constructor Details
#initialize(*args) ⇒ Resolver
Create a new Resolver object. If no parameters are passed in, then the default system configuration will be used. Otherwise, a Hash may be passed in with the following optional elements :
-
:port
-
:use_tcp
-
:tsig
-
:ignore_truncation
-
:src_address
-
:src_port
-
:persistent_tcp
-
:persistent_udp
-
:recurse
-
:udp_size
-
:config_info - see Config
-
:nameserver - can be either a String or an array of Strings
-
:packet_timeout
-
:query_timeout
-
:retry_times
-
:retry_delay
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 |
# File 'lib/Dnsruby/Resolver.rb', line 309 def initialize(*args) @resolver_em = nil @resolver_ruby = nil @src_address = nil reset_attributes # Process args if (args.length==1) if (args[0].class == Hash) args[0].keys.each do |key| begin if (key == :config_info) @config.set_config_info(args[0][:config_info]) elsif (key==:nameserver) set_config_nameserver(args[0][:nameserver]) else send(key.to_s+"=", args[0][key]) end rescue Exception TheLog.error("Argument #{key} not valid\n") end end elsif (args[0].class == String) set_config_nameserver(args[0]) elsif (args[0].class == Config) # also accepts a Config object from Dnsruby::Resolv @config = args[0] end else # Anything to do? end if (@single_resolvers==[]) add_config_nameservers end update end |
Instance Attribute Details
#config ⇒ Object (readonly)
The current Config
101 102 103 |
# File 'lib/Dnsruby/Resolver.rb', line 101 def config @config end |
#ignore_truncation ⇒ Object
Should truncation be ignored? i.e. the TC bit is ignored and thus the resolver will not requery over TCP if TC is set
82 83 84 |
# File 'lib/Dnsruby/Resolver.rb', line 82 def ignore_truncation @ignore_truncation end |
#packet_timeout ⇒ Object
The timeout for any individual packet. This is the timeout used by SingleResolver
107 108 109 |
# File 'lib/Dnsruby/Resolver.rb', line 107 def packet_timeout @packet_timeout end |
#persistent_tcp ⇒ Object
Should TCP queries be sent on a persistent socket?
90 91 92 |
# File 'lib/Dnsruby/Resolver.rb', line 90 def persistent_tcp @persistent_tcp end |
#persistent_udp ⇒ Object
Should UDP queries be sent on a persistent socket?
92 93 94 |
# File 'lib/Dnsruby/Resolver.rb', line 92 def persistent_udp @persistent_udp end |
#port ⇒ Object
The port to send queries to on the resolver
72 73 74 |
# File 'lib/Dnsruby/Resolver.rb', line 72 def port @port end |
#query_timeout ⇒ Object
Note that this timeout represents the total time a query may run for - multiple packets can be sent to multiple nameservers in this time. This is distinct from the SingleResolver per-packet timeout The query_timeout is not required - it will default to 0, which means “do not use query_timeout”. If this is the case then the timeout will be dictated by the retry_times and retry_delay attributes
114 115 116 |
# File 'lib/Dnsruby/Resolver.rb', line 114 def query_timeout @query_timeout end |
#recurse ⇒ Object
Should the Recursion Desired bit be set?
95 96 97 |
# File 'lib/Dnsruby/Resolver.rb', line 95 def recurse @recurse end |
#retry_delay ⇒ Object
The query will be tried across nameservers retry_times times, with a delay of retry_delay seconds between each retry. The first time round, retry_delay will be divided by the number of nameservers being targetted, and a new nameserver will be queried with the resultant delay.
119 120 121 |
# File 'lib/Dnsruby/Resolver.rb', line 119 def retry_delay @retry_delay end |
#retry_times ⇒ Object
The query will be tried across nameservers retry_times times, with a delay of retry_delay seconds between each retry. The first time round, retry_delay will be divided by the number of nameservers being targetted, and a new nameserver will be queried with the resultant delay.
119 120 121 |
# File 'lib/Dnsruby/Resolver.rb', line 119 def retry_times @retry_times end |
#single_resolvers ⇒ Object (readonly)
The array of SingleResolvers used for sending query messages
104 105 106 |
# File 'lib/Dnsruby/Resolver.rb', line 104 def single_resolvers @single_resolvers end |
#src_address ⇒ Object
The source address to send queries from
85 86 87 |
# File 'lib/Dnsruby/Resolver.rb', line 85 def src_address @src_address end |
#src_port ⇒ Object
The source port to send queries from
87 88 89 |
# File 'lib/Dnsruby/Resolver.rb', line 87 def src_port @src_port end |
#tsig ⇒ Object
Returns the value of attribute tsig.
78 79 80 |
# File 'lib/Dnsruby/Resolver.rb', line 78 def tsig @tsig end |
#udp_size ⇒ Object
The maximum UDP size to be used
98 99 100 |
# File 'lib/Dnsruby/Resolver.rb', line 98 def udp_size @udp_size end |
#use_tcp ⇒ Object
Should TCP be used as a transport rather than UDP?
75 76 77 |
# File 'lib/Dnsruby/Resolver.rb', line 75 def use_tcp @use_tcp end |
Class Method Details
.eventmachine? ⇒ Boolean
Check whether EventMachine will be used by Dnsruby
500 501 502 |
# File 'lib/Dnsruby/Resolver.rb', line 500 def Resolver.eventmachine? return @@use_eventmachine end |
.start_eventmachine_loop(on = true) ⇒ Object
If EventMachine is being used, then this method tells Dnsruby whether or not to start the EventMachine loop. If you want to use Dnsruby client code as is, but using EventMachine for I/O, then Dnsruby must start the EventMachine loop for you. This is the default behaviour. If you want to use EventMachine-style code, where everything is wrapped up in an EventMachine::run{} call, then this method should be called with false as the parameter.
Takes a bool argument to say whether or not to start the event loop when required.
512 513 514 515 516 517 518 519 |
# File 'lib/Dnsruby/Resolver.rb', line 512 def Resolver.start_eventmachine_loop(on=true) @@start_eventmachine_loop=on if (on) TheLog.info("EventMachine loop will be started by Dnsruby") else TheLog.info("EventMachine loop will not be started by Dnsruby") end end |
.start_eventmachine_loop? ⇒ Boolean
Checks whether Dnsruby will start the EventMachine loop when required.
521 522 523 |
# File 'lib/Dnsruby/Resolver.rb', line 521 def Resolver.start_eventmachine_loop? return @@start_eventmachine_loop end |
.use_eventmachine(on = true) ⇒ Object
Tell Dnsruby to use EventMachine for I/O.
If EventMachine is not used, then the pure Ruby event loop in Dnsruby will be used instead.
If EventMachine is not available on the platform, then a RuntimeError will be raised.
Takes a bool to say whether or not to use EventMachine.
488 489 490 491 492 493 494 495 496 497 498 |
# File 'lib/Dnsruby/Resolver.rb', line 488 def Resolver.use_eventmachine(on=true) if (on && !@@event_machine_available) raise RuntimeError.new("EventMachine is not available in this environment!") end @@use_eventmachine = on if (on) TheLog.info("EventMachine will be used for IO") else TheLog.info("EventMachine will not be used for IO") end end |
Instance Method Details
#add_config_nameservers ⇒ Object
346 347 348 349 350 351 |
# File 'lib/Dnsruby/Resolver.rb', line 346 def add_config_nameservers # Add the Config nameservers @config.nameserver.each do |ns| @single_resolvers.push(SingleResolver.new({:server=>ns})) end end |
#add_resolver(single) ⇒ Object
Add a new SingleResolver to the list of resolvers this Resolver object will query.
402 403 404 |
# File 'lib/Dnsruby/Resolver.rb', line 402 def add_resolver(single) @single_resolvers.push(single) end |
#close ⇒ Object
Close the Resolver. Unfinished queries are terminated with OtherResolvError.
284 285 286 |
# File 'lib/Dnsruby/Resolver.rb', line 284 def close [@resolver_em, @resolver_ruby].each do |r| r.close if r end end |
#generate_timeouts(base = 0) ⇒ Object
:nodoc: all
524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 |
# File 'lib/Dnsruby/Resolver.rb', line 524 def generate_timeouts(base=0) #:nodoc: all #These should be be pegged to the single_resolver they are targetting : # e.g. timeouts[timeout1]=nameserver timeouts = {} retry_delay = @retry_delay @retry_times.times do |retry_count| if (retry_count>0) retry_delay *= 2 end servers=[] @single_resolvers.each do |r| servers.push(r.server) end @single_resolvers.each_index do |i| res= @single_resolvers[i] offset = (i*@retry_delay.to_f/@single_resolvers.length) if (retry_count==0) timeouts[base+offset]=[res, retry_count] else if (timeouts.has_key?(base+retry_delay+offset)) TheLog.error("Duplicate timeout key!") raise RuntimeError.new("Duplicate timeout key!") end timeouts[base+retry_delay+offset]=[res, retry_count] end end end return timeouts end |
#nameserver=(n) ⇒ Object
406 407 408 409 410 |
# File 'lib/Dnsruby/Resolver.rb', line 406 def nameserver=(n) @single_resolvers=[] set_config_nameserver(n) add_config_nameservers end |
#query(name, type = Types.A, klass = Classes.IN) ⇒ Object
Query for a n. If a valid Message is received, then it is returned to the caller. Otherwise an exception (a Dnsruby::ResolvError or Dnsruby::ResolvTimeout) is raised.
require 'Dnsruby'
res = Dnsruby::Resolver.new
response = res.query("example.com") # defaults to Types.A, Classes.IN
response = res.query("example.com", Types.MX)
response = res.query("208.77.188.166") # IPv4 address so PTR query will be made
response = res.query("208.77.188.166", Types.PTR)
137 138 139 140 141 142 |
# File 'lib/Dnsruby/Resolver.rb', line 137 def query(name, type=Types.A, klass=Classes.IN) msg = Message.new msg.header.rd = 1 msg.add_question(name, type, klass) return (msg) end |
#reset_attributes ⇒ Object
:nodoc: all
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 |
# File 'lib/Dnsruby/Resolver.rb', line 361 def reset_attributes #:nodoc: all if (@resolver_em) @resolver_em.reset_attributes end if (@resolver_ruby) @resolver_ruby.reset_attributes end # Attributes @query_timeout = DefaultQueryTimeout @retry_delay = DefaultRetryDelay @retry_times = DefaultRetryTimes @packet_timeout = DefaultPacketTimeout @port = DefaultPort @udp_size = DefaultUDPSize @use_tcp = false @tsig = nil @ignore_truncation = false @config = Config.new() @src_addr = '0.0.0.0' @src_port = 0 @recurse = true @persistent_udp = false @persistent_tcp = false @single_resolvers=[] end |
#send_async(*args) ⇒ Object
Asynchronously send a Message to the server. The send can be done using just Dnsruby, or using EventMachine.
Dnsruby pure Ruby event loop :
A client_queue is supplied by the client, along with an optional client_query_id to identify the response. The client_query_id is generated, if not supplied, and returned to the client. When the response is known, a tuple of (query_id, response_message, exception) will be added to the client_queue.
The query is sent synchronously in the caller’s thread. The select thread is then used to listen for and process the response (up to pushing it to the client_queue). The client thread is then used to retrieve the response and deal with it.
Takes :
-
msg - the message to send
-
client_queue - a Queue to push the response to, when it arrives
-
client_query_id - an optional ID to identify the query to the client
-
use_tcp - whether to use TCP (defaults to SingleResolver.use_tcp)
Returns :
-
client_query_id - to identify the query response to the client. This ID is
generated if it is not passed in by the client
Example invocations :
id = res.send_async(msg, queue)
NOT SUPPORTED : id = res.send_async(msg, queue, use_tcp)
id = res.send_async(msg, queue, id)
id = res.send_async(msg, queue, id, use_tcp)
Example code :
require 'Dnsruby'
res = Dnsruby::Resolver.new
query_id = 10 # can be any object you like
query_queue = Queue.new
res.send_async(Message.new("example.com", Types.MX), query_queue, query_id)
query_id_2 = res.send_async(Message.new("example.com", Types.A), query_queue)
# ...do a load of other stuff here...
2.times do
response_id, response, exception = query_queue.pop
# You can check the ID to see which query has been answered
if (exception == nil)
# deal with good response
else
# deal with problem
end
end
If EventMachine is being used :
If EventMachine is being used (see Dnsruby::Resolver::use_eventmachine, then this method returns an EM::Deferrable object. When the response is known, then the Deferrable will complete. If a queue (and ID) is passed in, then the response will also be pushed to the Queue. Note that an ID is not automatically generated by this version.
Example invocations :
deferrable = res.send_async(msg)
deferrable = res.send_async(msg, use_tcp)
deferrable = res.send_async(msg, q, id, use_tcp)
Example code
-
Here is an example of using the code in an EventMachine style :
require 'Dnsruby' require 'eventmachine' res = Dnsruby::Resolver.new Dnsruby::Resolver.use_eventmachine Dnsruby::Resolver.start_eventmachine_loop(false) EventMachine::run { df = res.send_async(Dnsruby::Message.new("example.com")) df.callback {|msg| puts "Response : #{msg}" EM.stop} df.errback {|msg, err| puts "Response : #{msg}" puts "Error: #{err}" EM.stop} }
-
And an example in a normal Dnsruby style :
require 'Dnsruby' res = Dnsruby::Resolver.new Dnsruby::Resolver.use_eventmachine Dnsruby::Resolver.start_eventmachine_loop(true) # default q = Queue.new id = res.send_async(Dnsruby::Message.new("example.com"),q) id, response, error = q.pop
269 270 271 272 273 274 275 276 277 278 279 280 281 |
# File 'lib/Dnsruby/Resolver.rb', line 269 def send_async(*args) # msg, client_queue, client_query_id) if (Resolver.eventmachine?) if (!@resolver_em) @resolver_em = ResolverEM.new(self) end return @resolver_em.send_async(*args) else if (!@resolver_ruby) # @TODO@ Synchronize this? @resolver_ruby = ResolverRuby.new(self) end return @resolver_ruby.send_async(*args) end end |
#send_message(message) ⇒ Object
Send a message, and wait for the response. If a valid Message is received, then it is returned to the caller. Otherwise an exception (a Dnsruby::ResolvError or Dnsruby::ResolvTimeout) is raised.
send_async is called internally.
example :
require 'Dnsruby'
res = Dnsruby::Resolver.new
begin
response = res.(Message.new("example.com", Types.MX))
rescue ResolvError
# ...
rescue ResolvTimeout
# ...
end
160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/Dnsruby/Resolver.rb', line 160 def () TheLog.debug("Resolver : sending message") q = Queue.new send_async(, q) id, result, error = q.pop TheLog.debug("Resolver : result received") if (error != nil) raise error else return result end end |
#set_config_nameserver(n) ⇒ Object
353 354 355 356 357 358 359 |
# File 'lib/Dnsruby/Resolver.rb', line 353 def set_config_nameserver(n) if (n).kind_of?String @config.nameserver=[n] else @config.nameserver=n end end |
#update ⇒ Object
:nodoc: all
388 389 390 391 392 393 394 395 396 397 398 |
# File 'lib/Dnsruby/Resolver.rb', line 388 def update #:nodoc: all #Update any resolvers we have with the latest config @single_resolvers.each do |res| [:port, :use_tcp, :tsig, :ignore_truncation, :packet_timeout, :src_address, :src_port, :persistent_tcp, :persistent_udp, :recurse, :udp_size].each do |param| res.send(param.to_s+"=", instance_variable_get("@"+param.to_s)) end end end |