Class: Celluloid::DNS::Resolver

Inherits:
Object
  • Object
show all
Includes:
IO
Defined in:
lib/celluloid/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

Instance Method Summary collapse

Constructor Details

#initialize(servers, options = {}) ⇒ 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.



51
52
53
54
55
56
57
58
59
# File 'lib/celluloid/dns/resolver.rb', line 51

def initialize(servers, options = {})
	@servers = servers
	
	@options = options
	
	@origin = options[:origin] || nil
	
	@logger = options[:logger] || Celluloid.logger
end

Instance Attribute Details

#originObject

Returns the value of attribute origin.



61
62
63
# File 'lib/celluloid/dns/resolver.rb', line 61

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.



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
134
135
136
137
# File 'lib/celluloid/dns/resolver.rb', line 97

def addresses_for(name, resource_class = Resolv::DNS::Resource::IN::A, options = {})
	name = fully_qualified_name(name)
	
	cache = options.fetch(:cache, {})
	retries = options.fetch(:retries, DEFAULT_RETRIES)
	delay = options.fetch(:delay, DEFAULT_DELAY)
	
	records = lookup(name, resource_class, cache) do |name, resource_class|
		response = nil
		
		retries.times do |i|
			# Wait 10ms before trying again:
			sleep delay if delay and i > 0
			
			response = query(name, resource_class)
			
			break if response
		end
		
		response or abort 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, options.merge(cache: cache))
			end
		end
	end
	
	if addresses.size > 0
		return addresses
	else
		abort ResolutionFailure.new("Could not find any addresses for #{name}.")
	end
end

#dispatch_request(message) ⇒ 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.



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
169
170
171
172
173
174
175
# File 'lib/celluloid/dns/resolver.rb', line 144

def dispatch_request(message)
	request = Request.new(message, @servers)
	
	request.each do |server|
		@logger.debug "[#{message.id}] Sending request #{message.question.inspect} to server #{server.inspect}" if @logger
		
		begin
			response = nil
			
			# This may be causing a problem, perhaps try:
			# 	after(timeout) { socket.close }
			# https://github.com/celluloid/celluloid-io/issues/121
			timeout(request_timeout) do
				response = try_server(request, server)
			end
			
			if valid_response(message, response)
				return response
			end
		rescue TaskTimeout
			@logger.debug "[#{message.id}] Request timed out!" if @logger
		rescue InvalidResponseError
			@logger.warn "[#{message.id}] Invalid response from network: #{$!}!" if @logger
		rescue DecodeError
			@logger.warn "[#{message.id}] Error while decoding data from network: #{$!}!" if @logger
		rescue IOError
			@logger.warn "[#{message.id}] Error while reading from network: #{$!}!" if @logger
		end
	end
	
	return nil
end

#fully_qualified_name(name) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/celluloid/dns/resolver.rb', line 63

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.



82
83
84
85
# File 'lib/celluloid/dns/resolver.rb', line 82

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.



88
89
90
91
92
93
94
# File 'lib/celluloid/dns/resolver.rb', line 88

def query(name, resource_class = Resolv::DNS::Resource::IN::A)
	message = Resolv::DNS::Message.new(next_id!)
	message.rd = 1
	message.add_question fully_qualified_name(name), resource_class
	
	dispatch_request(message)
end

#request_timeoutObject



139
140
141
# File 'lib/celluloid/dns/resolver.rb', line 139

def request_timeout
	@options[:timeout] || DEFAULT_TIMEOUT
end