Class: Async::DNS::Resolver
- Inherits:
-
Object
- Object
- Async::DNS::Resolver
- Defined in:
- lib/async/dns/resolver.rb
Defined Under Namespace
Classes: Request
Constant Summary collapse
- DEFAULT_TIMEOUT =
Wait for up to 5 seconds for a response. Override with ‘options`
5.0
- DEFAULT_DELAY =
10ms wait between making requests. Override with ‘options`
0.01
- DEFAULT_RETRIES =
Try a given request 10 times before failing. Override with ‘options`.
10
Instance Attribute Summary collapse
-
#origin ⇒ Object
Returns the value of attribute origin.
Instance Method Summary collapse
-
#addresses_for(name, resource_class = Resolv::DNS::Resource::IN::A, options = {}) ⇒ Object
Yields a list of ‘Resolv::IPv4` and `Resolv::IPv6` addresses for the given `name` and `resource_class`.
-
#dispatch_request(message, task: Async::Task.current) ⇒ Object
Send the message to available servers.
- #fully_qualified_name(name) ⇒ Object
-
#initialize(endpoints, origin: nil, logger: Console.logger, timeout: DEFAULT_TIMEOUT) ⇒ Resolver
constructor
Servers are specified in the same manor as options, e.g.
-
#next_id! ⇒ Object
Provides the next sequence identification number which is used to keep track of DNS messages.
-
#query(name, resource_class = Resolv::DNS::Resource::IN::A) ⇒ Object
Look up a named resource of the given resource_class.
Constructor Details
#initialize(endpoints, origin: nil, logger: Console.logger, timeout: DEFAULT_TIMEOUT) ⇒ Resolver
Servers are specified in the same manor as options, e.g.
[:tcp/:udp, address, port]
In the case of multiple servers, they will be checked in sequence.
49 50 51 52 53 54 55 |
# File 'lib/async/dns/resolver.rb', line 49 def initialize(endpoints, origin: nil, logger: Console.logger, timeout: DEFAULT_TIMEOUT) @endpoints = endpoints @origin = origin @logger = logger @timeout = timeout end |
Instance Attribute Details
#origin ⇒ Object
Returns the value of attribute origin.
57 58 59 |
# File 'lib/async/dns/resolver.rb', line 57 def origin @origin end |
Instance Method Details
#addresses_for(name, resource_class = Resolv::DNS::Resource::IN::A, options = {}) ⇒ Object
Yields a list of ‘Resolv::IPv4` and `Resolv::IPv6` addresses for the given `name` and `resource_class`. Raises a ResolutionFailure if no severs respond.
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/async/dns/resolver.rb', line 93 def addresses_for(name, resource_class = Resolv::DNS::Resource::IN::A, = {}) name = fully_qualified_name(name) cache = .fetch(:cache, {}) retries = .fetch(:retries, DEFAULT_RETRIES) delay = .fetch(:delay, DEFAULT_DELAY) records = lookup(name, resource_class, cache) do |lookup_name, lookup_resource_class| response = nil retries.times do |i| # Wait 10ms before trying again: sleep delay if delay and i > 0 response = query(lookup_name, lookup_resource_class) break if response end response or raise ResolutionFailure.new("Could not resolve #{name} after #{retries} attempt(s).") end addresses = [] if records records.each do |record| if record.respond_to? :address addresses << record.address else # The most common case here is that record.class is IN::CNAME and we need to figure out the address. Usually the upstream DNS server would have replied with this too, and this will be loaded from the response if possible without requesting additional information. addresses += addresses_for(record.name, record.class, .merge(cache: cache)) end end end if addresses.size > 0 return addresses else raise ResolutionFailure.new("Could not find any addresses for #{name}.") end end |
#dispatch_request(message, task: Async::Task.current) ⇒ Object
Send the message to available servers. If no servers respond correctly, nil is returned. This result indicates a failure of the resolver to correctly contact any server and get a valid response.
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/async/dns/resolver.rb', line 136 def dispatch_request(, task: Async::Task.current) request = Request.new(, @endpoints) request.each do |endpoint| @logger.debug "[#{.id}] Sending request #{.question.inspect} to address #{endpoint.inspect}" if @logger begin response = nil task.with_timeout(@timeout) do @logger.debug "[#{.id}] -> Try address #{endpoint}" if @logger response = try_server(request, endpoint) @logger.debug "[#{.id}] <- Try address #{endpoint} = #{response}" if @logger end if valid_response(, response) return response end rescue Async::TimeoutError @logger.debug "[#{.id}] Request timed out!" if @logger rescue InvalidResponseError @logger.warn "[#{.id}] Invalid response from network: #{$!}!" if @logger rescue DecodeError @logger.warn "[#{.id}] Error while decoding data from network: #{$!}!" if @logger rescue IOError, Errno::ECONNRESET @logger.warn "[#{.id}] Error while reading from network: #{$!}!" if @logger rescue EOFError @logger.warn "[#{.id}] Could not read complete response from network: #{$!}" if @logger end end return nil end |
#fully_qualified_name(name) ⇒ Object
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/async/dns/resolver.rb', line 59 def fully_qualified_name(name) # If we are passed an existing deconstructed name: if Resolv::DNS::Name === name if name.absolute? return name else return name.with_origin(@origin) end end # ..else if we have a string, we need to do some basic processing: if name.end_with? '.' return Resolv::DNS::Name.create(name) else return Resolv::DNS::Name.create(name).with_origin(@origin) end end |
#next_id! ⇒ Object
Provides the next sequence identification number which is used to keep track of DNS messages.
78 79 80 81 |
# File 'lib/async/dns/resolver.rb', line 78 def next_id! # Using sequential numbers for the query ID is generally a bad thing because over UDP they can be spoofed. 16-bits isn't hard to guess either, but over UDP we also use a random port, so this makes effectively 32-bits of entropy to guess per request. SecureRandom.random_number(2**16) end |
#query(name, resource_class = Resolv::DNS::Resource::IN::A) ⇒ Object
Look up a named resource of the given resource_class.
84 85 86 87 88 89 90 |
# File 'lib/async/dns/resolver.rb', line 84 def query(name, resource_class = Resolv::DNS::Resource::IN::A) = Resolv::DNS::Message.new(next_id!) .rd = 1 .add_question fully_qualified_name(name), resource_class dispatch_request() end |