rubydns 1.0.3 → 2.0.0.pre.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +4 -0
- data/.travis.yml +9 -12
- data/Gemfile +4 -1
- data/README.md +49 -151
- data/Rakefile +2 -7
- data/examples/basic-dns.rb +24 -0
- data/examples/cname.rb +25 -0
- data/examples/flakey-dns.rb +2 -2
- data/examples/simple.rb +25 -0
- data/examples/soa-dns.rb +82 -0
- data/examples/test-dns-1.rb +83 -0
- data/examples/test-dns-2.rb +83 -0
- data/examples/wikipedia-dns.rb +4 -18
- data/lib/rubydns.rb +9 -23
- data/lib/rubydns/{server.rb → rule_based_server.rb} +2 -160
- data/lib/rubydns/version.rb +1 -1
- data/rubydns.gemspec +3 -6
- data/spec/rubydns/daemon_spec.rb +26 -22
- data/spec/rubydns/injected_supervisor_spec.rb +10 -7
- data/spec/rubydns/passthrough_spec.rb +31 -24
- data/spec/spec_helper.rb +43 -0
- metadata +21 -100
- data/lib/rubydns/chunked.rb +0 -34
- data/lib/rubydns/extensions/resolv.rb +0 -85
- data/lib/rubydns/extensions/string.rb +0 -28
- data/lib/rubydns/handler.rb +0 -188
- data/lib/rubydns/logger.rb +0 -31
- data/lib/rubydns/message.rb +0 -76
- data/lib/rubydns/resolver.rb +0 -294
- data/lib/rubydns/system.rb +0 -146
- data/lib/rubydns/transaction.rb +0 -204
- data/lib/rubydns/transport.rb +0 -75
- data/spec/rubydns/celluloid_bug_spec.rb +0 -92
- data/spec/rubydns/ipv6_spec.rb +0 -70
- data/spec/rubydns/message_spec.rb +0 -56
- data/spec/rubydns/origin_spec.rb +0 -106
- data/spec/rubydns/resolver_performance_spec.rb +0 -110
- data/spec/rubydns/resolver_spec.rb +0 -152
- data/spec/rubydns/server/bind9/generate-local.rb +0 -25
- data/spec/rubydns/server/bind9/local.zone +0 -5014
- data/spec/rubydns/server/bind9/named.conf +0 -14
- data/spec/rubydns/server/bind9/named.run +0 -0
- data/spec/rubydns/server/million.rb +0 -85
- data/spec/rubydns/server/rubydns.stackprof +0 -0
- data/spec/rubydns/server_performance_spec.rb +0 -136
- data/spec/rubydns/slow_server_spec.rb +0 -89
- data/spec/rubydns/socket_spec.rb +0 -77
- data/spec/rubydns/system_spec.rb +0 -60
- data/spec/rubydns/transaction_spec.rb +0 -138
- data/spec/rubydns/truncation_spec.rb +0 -59
data/lib/rubydns/resolver.rb
DELETED
@@ -1,294 +0,0 @@
|
|
1
|
-
# Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
-
#
|
3
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
-
# of this software and associated documentation files (the "Software"), to deal
|
5
|
-
# in the Software without restriction, including without limitation the rights
|
6
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
-
# copies of the Software, and to permit persons to whom the Software is
|
8
|
-
# furnished to do so, subject to the following conditions:
|
9
|
-
#
|
10
|
-
# The above copyright notice and this permission notice shall be included in
|
11
|
-
# all copies or substantial portions of the Software.
|
12
|
-
#
|
13
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
-
# THE SOFTWARE.
|
20
|
-
|
21
|
-
require_relative 'handler'
|
22
|
-
|
23
|
-
require 'securerandom'
|
24
|
-
require 'celluloid/io'
|
25
|
-
|
26
|
-
module RubyDNS
|
27
|
-
class InvalidProtocolError < StandardError
|
28
|
-
end
|
29
|
-
|
30
|
-
class InvalidResponseError < StandardError
|
31
|
-
end
|
32
|
-
|
33
|
-
class ResolutionFailure < StandardError
|
34
|
-
end
|
35
|
-
|
36
|
-
class Resolver
|
37
|
-
# Wait for up to 2 seconds for a response. Override with `options[:timeout]`
|
38
|
-
DEFAULT_TIMEOUT = 5.0
|
39
|
-
|
40
|
-
# 10ms wait between making requests. Override with `options[:delay]`
|
41
|
-
DEFAULT_DELAY = 0.01
|
42
|
-
|
43
|
-
# Try a given request 5 times before failing. Override with `options[:retries]`.
|
44
|
-
DEFAULT_RETRIES = 10
|
45
|
-
|
46
|
-
include Celluloid::IO
|
47
|
-
|
48
|
-
# Servers are specified in the same manor as options[:listen], e.g.
|
49
|
-
# [:tcp/:udp, address, port]
|
50
|
-
# In the case of multiple servers, they will be checked in sequence.
|
51
|
-
def initialize(servers, options = {})
|
52
|
-
@servers = servers
|
53
|
-
|
54
|
-
@options = options
|
55
|
-
|
56
|
-
@origin = options[:origin] || nil
|
57
|
-
|
58
|
-
@logger = options[:logger] || Celluloid.logger
|
59
|
-
end
|
60
|
-
|
61
|
-
attr_accessor :origin
|
62
|
-
|
63
|
-
def fully_qualified_name(name)
|
64
|
-
# If we are passed an existing deconstructed name:
|
65
|
-
if Resolv::DNS::Name === name
|
66
|
-
if name.absolute?
|
67
|
-
return name
|
68
|
-
else
|
69
|
-
return name.with_origin(@origin)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
# ..else if we have a string, we need to do some basic processing:
|
74
|
-
if name.end_with? '.'
|
75
|
-
return Resolv::DNS::Name.create(name)
|
76
|
-
else
|
77
|
-
return Resolv::DNS::Name.create(name).with_origin(@origin)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
# Provides the next sequence identification number which is used to keep track of DNS messages.
|
82
|
-
def next_id!
|
83
|
-
# 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.
|
84
|
-
SecureRandom.random_number(2**16)
|
85
|
-
end
|
86
|
-
|
87
|
-
# Look up a named resource of the given resource_class.
|
88
|
-
def query(name, resource_class = Resolv::DNS::Resource::IN::A)
|
89
|
-
message = Resolv::DNS::Message.new(next_id!)
|
90
|
-
message.rd = 1
|
91
|
-
message.add_question fully_qualified_name(name), resource_class
|
92
|
-
|
93
|
-
dispatch_request(message)
|
94
|
-
end
|
95
|
-
|
96
|
-
# 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
|
-
def addresses_for(name, resource_class = Resolv::DNS::Resource::IN::A, options = {})
|
98
|
-
name = fully_qualified_name(name)
|
99
|
-
|
100
|
-
cache = options.fetch(:cache, {})
|
101
|
-
retries = options.fetch(:retries, DEFAULT_RETRIES)
|
102
|
-
delay = options.fetch(:delay, DEFAULT_DELAY)
|
103
|
-
|
104
|
-
records = lookup(name, resource_class, cache) do |name, resource_class|
|
105
|
-
response = nil
|
106
|
-
|
107
|
-
retries.times do |i|
|
108
|
-
# Wait 10ms before trying again:
|
109
|
-
sleep delay if delay and i > 0
|
110
|
-
|
111
|
-
response = query(name, resource_class)
|
112
|
-
|
113
|
-
break if response
|
114
|
-
end
|
115
|
-
|
116
|
-
response or abort ResolutionFailure.new("Could not resolve #{name} after #{retries} attempt(s).")
|
117
|
-
end
|
118
|
-
|
119
|
-
addresses = []
|
120
|
-
|
121
|
-
if records
|
122
|
-
records.each do |record|
|
123
|
-
if record.respond_to? :address
|
124
|
-
addresses << record.address
|
125
|
-
else
|
126
|
-
# 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.
|
127
|
-
addresses += addresses_for(record.name, record.class, options.merge(cache: cache))
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
if addresses.size > 0
|
133
|
-
return addresses
|
134
|
-
else
|
135
|
-
abort ResolutionFailure.new("Could not find any addresses for #{name}.")
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
def request_timeout
|
140
|
-
@options[:timeout] || DEFAULT_TIMEOUT
|
141
|
-
end
|
142
|
-
|
143
|
-
# 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
|
-
def dispatch_request(message)
|
145
|
-
request = Request.new(message, @servers)
|
146
|
-
|
147
|
-
request.each do |server|
|
148
|
-
@logger.debug "[#{message.id}] Sending request #{message.question.inspect} to server #{server.inspect}" if @logger
|
149
|
-
|
150
|
-
begin
|
151
|
-
response = nil
|
152
|
-
|
153
|
-
# This may be causing a problem, perhaps try:
|
154
|
-
# after(timeout) { socket.close }
|
155
|
-
# https://github.com/celluloid/celluloid-io/issues/121
|
156
|
-
timeout(request_timeout) do
|
157
|
-
response = try_server(request, server)
|
158
|
-
end
|
159
|
-
|
160
|
-
if valid_response(message, response)
|
161
|
-
return response
|
162
|
-
end
|
163
|
-
rescue Task::TimeoutError
|
164
|
-
@logger.debug "[#{message.id}] Request timed out!" if @logger
|
165
|
-
rescue InvalidResponseError
|
166
|
-
@logger.warn "[#{message.id}] Invalid response from network: #{$!}!" if @logger
|
167
|
-
rescue DecodeError
|
168
|
-
@logger.warn "[#{message.id}] Error while decoding data from network: #{$!}!" if @logger
|
169
|
-
rescue IOError
|
170
|
-
@logger.warn "[#{message.id}] Error while reading from network: #{$!}!" if @logger
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
return nil
|
175
|
-
end
|
176
|
-
|
177
|
-
private
|
178
|
-
|
179
|
-
# Lookup a name/resource_class record but use the records cache if possible reather than making a new request if possible.
|
180
|
-
def lookup(name, resource_class = Resolv::DNS::Resource::IN::A, records = {})
|
181
|
-
records.fetch(name) do
|
182
|
-
response = yield(name, resource_class)
|
183
|
-
|
184
|
-
if response
|
185
|
-
response.answer.each do |name, ttl, record|
|
186
|
-
(records[name] ||= []) << record
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
records[name]
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
def try_server(request, server)
|
195
|
-
case server[0]
|
196
|
-
when :udp
|
197
|
-
try_udp_server(request, server[1], server[2])
|
198
|
-
when :tcp
|
199
|
-
try_tcp_server(request, server[1], server[2])
|
200
|
-
else
|
201
|
-
raise InvalidProtocolError.new(server)
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
def valid_response(message, response)
|
206
|
-
if response.tc != 0
|
207
|
-
@logger.warn "[#{message.id}] Received truncated response!" if @logger
|
208
|
-
elsif response.id != message.id
|
209
|
-
@logger.warn "[#{message.id}] Received response with incorrect message id: #{response.id}!" if @logger
|
210
|
-
else
|
211
|
-
@logger.debug "[#{message.id}] Received valid response with #{response.answer.count} answer(s)." if @logger
|
212
|
-
|
213
|
-
return true
|
214
|
-
end
|
215
|
-
|
216
|
-
return false
|
217
|
-
end
|
218
|
-
|
219
|
-
def try_udp_server(request, host, port)
|
220
|
-
family = RubyDNS::address_family(host)
|
221
|
-
socket = UDPSocket.new(family)
|
222
|
-
|
223
|
-
socket.send(request.packet, 0, host, port)
|
224
|
-
|
225
|
-
data, (_, remote_port) = socket.recvfrom(UDP_TRUNCATION_SIZE, 0)
|
226
|
-
# Need to check host, otherwise security issue.
|
227
|
-
|
228
|
-
# May indicate some kind of spoofing attack:
|
229
|
-
if port != remote_port
|
230
|
-
raise InvalidResponseError.new("Data was not received from correct remote port (#{port} != #{remote_port})")
|
231
|
-
end
|
232
|
-
|
233
|
-
message = RubyDNS::decode_message(data)
|
234
|
-
ensure
|
235
|
-
socket.close if socket
|
236
|
-
end
|
237
|
-
|
238
|
-
def try_tcp_server(request, host, port)
|
239
|
-
begin
|
240
|
-
socket = TCPSocket.new(host, port)
|
241
|
-
rescue Errno::EALREADY
|
242
|
-
# This is a hack to work around faulty behaviour in celluloid-io:
|
243
|
-
# https://github.com/celluloid/celluloid/issues/436
|
244
|
-
raise IOError.new("Could not connect to remote host!")
|
245
|
-
end
|
246
|
-
|
247
|
-
StreamTransport.write_chunk(socket, request.packet)
|
248
|
-
|
249
|
-
input_data = StreamTransport.read_chunk(socket)
|
250
|
-
|
251
|
-
message = RubyDNS::decode_message(input_data)
|
252
|
-
rescue Errno::ECONNREFUSED => error
|
253
|
-
raise IOError.new(error.message)
|
254
|
-
rescue Errno::EPIPE => error
|
255
|
-
raise IOError.new(error.message)
|
256
|
-
rescue Errno::ECONNRESET => error
|
257
|
-
raise IOError.new(error.message)
|
258
|
-
ensure
|
259
|
-
socket.close if socket
|
260
|
-
end
|
261
|
-
|
262
|
-
# Manages a single DNS question message across one or more servers.
|
263
|
-
class Request
|
264
|
-
def initialize(message, servers)
|
265
|
-
@message = message
|
266
|
-
@packet = message.encode
|
267
|
-
|
268
|
-
@servers = servers.dup
|
269
|
-
|
270
|
-
# We select the protocol based on the size of the data:
|
271
|
-
if @packet.bytesize > UDP_TRUNCATION_SIZE
|
272
|
-
@servers.delete_if{|server| server[0] == :udp}
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
attr :message
|
277
|
-
attr :packet
|
278
|
-
attr :logger
|
279
|
-
|
280
|
-
def each(&block)
|
281
|
-
@servers.each do |server|
|
282
|
-
next if @packet.bytesize > UDP_TRUNCATION_SIZE
|
283
|
-
|
284
|
-
yield server
|
285
|
-
end
|
286
|
-
end
|
287
|
-
|
288
|
-
def update_id!(id)
|
289
|
-
@message.id = id
|
290
|
-
@packet = @message.encode
|
291
|
-
end
|
292
|
-
end
|
293
|
-
end
|
294
|
-
end
|
data/lib/rubydns/system.rb
DELETED
@@ -1,146 +0,0 @@
|
|
1
|
-
# Copyright, 2009, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
-
#
|
3
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
-
# of this software and associated documentation files (the "Software"), to deal
|
5
|
-
# in the Software without restriction, including without limitation the rights
|
6
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
-
# copies of the Software, and to permit persons to whom the Software is
|
8
|
-
# furnished to do so, subject to the following conditions:
|
9
|
-
#
|
10
|
-
# The above copyright notice and this permission notice shall be included in
|
11
|
-
# all copies or substantial portions of the Software.
|
12
|
-
#
|
13
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
-
# THE SOFTWARE.
|
20
|
-
|
21
|
-
begin
|
22
|
-
require 'win32/resolv'
|
23
|
-
rescue LoadError
|
24
|
-
# Ignore this - we aren't running on windows.
|
25
|
-
end
|
26
|
-
|
27
|
-
module RubyDNS
|
28
|
-
# This module encapsulates system dependent name lookup functionality.
|
29
|
-
module System
|
30
|
-
RESOLV_CONF = "/etc/resolv.conf"
|
31
|
-
HOSTS = "/etc/hosts"
|
32
|
-
|
33
|
-
def self.hosts_path
|
34
|
-
if RUBY_PLATFORM =~ /mswin32|mingw|bccwin/
|
35
|
-
Win32::Resolv.get_hosts_path
|
36
|
-
else
|
37
|
-
HOSTS
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# This code is very experimental
|
42
|
-
class Hosts
|
43
|
-
def initialize
|
44
|
-
@addresses = {}
|
45
|
-
@names = {}
|
46
|
-
end
|
47
|
-
|
48
|
-
attr :addresses
|
49
|
-
attr :names
|
50
|
-
|
51
|
-
# This is used to match names against the list of known hosts:
|
52
|
-
def call(name)
|
53
|
-
@names.include?(name)
|
54
|
-
end
|
55
|
-
|
56
|
-
def lookup(name)
|
57
|
-
addresses = @names[name]
|
58
|
-
|
59
|
-
if addresses
|
60
|
-
addresses.last
|
61
|
-
else
|
62
|
-
nil
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
alias [] lookup
|
67
|
-
|
68
|
-
def add(address, names)
|
69
|
-
@addresses[address] ||= []
|
70
|
-
@addresses[address] += names
|
71
|
-
|
72
|
-
names.each do |name|
|
73
|
-
@names[name] ||= []
|
74
|
-
@names[name] << address
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def parse_hosts(io)
|
79
|
-
io.each do |line|
|
80
|
-
line.sub!(/#.*/, '')
|
81
|
-
address, hostname, *aliases = line.split(/\s+/)
|
82
|
-
|
83
|
-
add(address, [hostname] + aliases)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def self.local
|
88
|
-
hosts = self.new
|
89
|
-
|
90
|
-
path = System::hosts_path
|
91
|
-
|
92
|
-
if path and File.exist?(path)
|
93
|
-
File.open(path) do |file|
|
94
|
-
hosts.parse_hosts(file)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
return hosts
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def self.parse_resolv_configuration(path)
|
103
|
-
nameservers = []
|
104
|
-
File.open(path) do |file|
|
105
|
-
file.each do |line|
|
106
|
-
# Remove any comments:
|
107
|
-
line.sub!(/[#;].*/, '')
|
108
|
-
|
109
|
-
# Extract resolv.conf command:
|
110
|
-
keyword, *args = line.split(/\s+/)
|
111
|
-
|
112
|
-
case keyword
|
113
|
-
when 'nameserver'
|
114
|
-
nameservers += args
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
return nameservers
|
120
|
-
end
|
121
|
-
|
122
|
-
def self.standard_connections(nameservers)
|
123
|
-
connections = []
|
124
|
-
|
125
|
-
nameservers.each do |host|
|
126
|
-
connections << [:udp, host, 53]
|
127
|
-
connections << [:tcp, host, 53]
|
128
|
-
end
|
129
|
-
|
130
|
-
return connections
|
131
|
-
end
|
132
|
-
|
133
|
-
# Get a list of standard nameserver connections which can be used for querying any standard servers that the system has been configured with. There is no equivalent facility to use the `hosts` file at present.
|
134
|
-
def self.nameservers
|
135
|
-
nameservers = []
|
136
|
-
|
137
|
-
if File.exist? RESOLV_CONF
|
138
|
-
nameservers = parse_resolv_configuration(RESOLV_CONF)
|
139
|
-
elsif defined?(Win32::Resolv) and RUBY_PLATFORM =~ /mswin32|cygwin|mingw|bccwin/
|
140
|
-
search, nameservers = Win32::Resolv.get_resolv_info
|
141
|
-
end
|
142
|
-
|
143
|
-
return standard_connections(nameservers)
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
data/lib/rubydns/transaction.rb
DELETED
@@ -1,204 +0,0 @@
|
|
1
|
-
# Copyright, 2009, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
-
#
|
3
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
-
# of this software and associated documentation files (the "Software"), to deal
|
5
|
-
# in the Software without restriction, including without limitation the rights
|
6
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
-
# copies of the Software, and to permit persons to whom the Software is
|
8
|
-
# furnished to do so, subject to the following conditions:
|
9
|
-
#
|
10
|
-
# The above copyright notice and this permission notice shall be included in
|
11
|
-
# all copies or substantial portions of the Software.
|
12
|
-
#
|
13
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
-
# THE SOFTWARE.
|
20
|
-
|
21
|
-
module RubyDNS
|
22
|
-
|
23
|
-
# This class provides all details of a single DNS question and response. This is used by the DSL to provide DNS related functionality.
|
24
|
-
#
|
25
|
-
# The main functions to complete the transaction 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).
|
26
|
-
class Transaction
|
27
|
-
# The default time used for responses (24 hours).
|
28
|
-
DEFAULT_TTL = 86400
|
29
|
-
|
30
|
-
def initialize(server, query, question, resource_class, response, options = {})
|
31
|
-
@server = server
|
32
|
-
@query = query
|
33
|
-
@question = question
|
34
|
-
@resource_class = resource_class
|
35
|
-
@response = response
|
36
|
-
|
37
|
-
@options = options
|
38
|
-
end
|
39
|
-
|
40
|
-
# The resource_class that was requested. This is typically used to generate a response.
|
41
|
-
attr :resource_class
|
42
|
-
|
43
|
-
# The incoming query which is a set of questions.
|
44
|
-
attr :query
|
45
|
-
|
46
|
-
# The question that this transaction represents.
|
47
|
-
attr :question
|
48
|
-
|
49
|
-
# The current full response to the incoming query.
|
50
|
-
attr :response
|
51
|
-
|
52
|
-
# Any options or configuration associated with the given transaction.
|
53
|
-
attr :options
|
54
|
-
|
55
|
-
def [] key
|
56
|
-
@options[key]
|
57
|
-
end
|
58
|
-
|
59
|
-
# The name of the question, which is typically the requested hostname.
|
60
|
-
def name
|
61
|
-
@question.to_s
|
62
|
-
end
|
63
|
-
|
64
|
-
# Shows the question name and resource class. Suitable for debugging purposes.
|
65
|
-
def to_s
|
66
|
-
"#{name} #{@resource_class.name}"
|
67
|
-
end
|
68
|
-
|
69
|
-
# 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 `response`.
|
70
|
-
def append!(name, resource_class = nil, options = {})
|
71
|
-
Transaction.new(@server, @query, name, resource_class || @resource_class, @response, options).process
|
72
|
-
end
|
73
|
-
|
74
|
-
# Use the given resolver to respond to the question. Uses `passthrough` to do the lookup and merges the result.
|
75
|
-
#
|
76
|
-
# If a block is supplied, this function yields with the `response` message if successful. This could be used, for example, to update a cache or modify the reply.
|
77
|
-
#
|
78
|
-
# If recursion is not requested, the result is `fail!(:Refused)`. This check is ignored if an explicit `options[:name]` or `options[:force]` is given.
|
79
|
-
#
|
80
|
-
# If the resolver can't reach upstream servers, `fail!(:ServFail)` is invoked.
|
81
|
-
def passthrough!(resolver, options = {}, &block)
|
82
|
-
if @query.rd || options[:force] || options[:name]
|
83
|
-
response = passthrough(resolver, options)
|
84
|
-
|
85
|
-
if response
|
86
|
-
yield response if block_given?
|
87
|
-
|
88
|
-
# Recursion is available and is being used:
|
89
|
-
# See issue #26 for more details.
|
90
|
-
@response.ra = 1
|
91
|
-
@response.merge!(response)
|
92
|
-
else
|
93
|
-
fail!(:ServFail)
|
94
|
-
end
|
95
|
-
else
|
96
|
-
fail!(:Refused)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
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.
|
103
|
-
#
|
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]`.
|
105
|
-
def passthrough(resolver, options = {})
|
106
|
-
query_name = options[:name] || name
|
107
|
-
query_resource_class = options[:resource_class] || resource_class
|
108
|
-
|
109
|
-
resolver.query(query_name, query_resource_class)
|
110
|
-
end
|
111
|
-
|
112
|
-
# 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!}.
|
113
|
-
#
|
114
|
-
# e.g. For A records: `respond!("1.2.3.4")`, For MX records: `respond!(10, Name.create("mail.blah.com"))`
|
115
|
-
|
116
|
-
# 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!}.
|
117
|
-
#
|
118
|
-
# See `Resolv::DNS::Resource` for more information about the various `resource_classes` available (http://www.ruby-doc.org/stdlib/libdoc/resolv/rdoc/index.html).
|
119
|
-
def respond!(*args)
|
120
|
-
append_question!
|
121
|
-
|
122
|
-
options = args.last.kind_of?(Hash) ? args.pop : {}
|
123
|
-
resource_class = options[:resource_class] || @resource_class
|
124
|
-
|
125
|
-
if resource_class == nil
|
126
|
-
raise ArgumentError.new("Could not instantiate resource #{resource_class}!")
|
127
|
-
end
|
128
|
-
|
129
|
-
@server.logger.info "Resource class: #{resource_class.inspect}"
|
130
|
-
resource = resource_class.new(*args)
|
131
|
-
@server.logger.info "Resource: #{resource.inspect}"
|
132
|
-
|
133
|
-
add([resource], options)
|
134
|
-
end
|
135
|
-
|
136
|
-
# Append a list of resources.
|
137
|
-
#
|
138
|
-
# By default resources are appended to the `answers` section, but this can be changed by setting `options[:section]` to either `:authority` or `:additional`.
|
139
|
-
#
|
140
|
-
# The time-to-live (TTL) of the resources can be specified using `options[:ttl]` and defaults to `DEFAULT_TTL`.
|
141
|
-
def add(resources, options = {})
|
142
|
-
# Use the default options if provided:
|
143
|
-
options = options.merge(@options)
|
144
|
-
|
145
|
-
ttl = options[:ttl] || DEFAULT_TTL
|
146
|
-
name = options[:name] || @question.to_s + "."
|
147
|
-
|
148
|
-
section = (options[:section] || 'answer').to_sym
|
149
|
-
method = "add_#{section}".to_sym
|
150
|
-
|
151
|
-
resources.each do |resource|
|
152
|
-
@server.logger.debug "#{method}: #{resource.inspect} #{resource.class::TypeValue} #{resource.class::ClassValue}"
|
153
|
-
|
154
|
-
@response.send(method, name, ttl, resource)
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
# 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}.
|
159
|
-
#
|
160
|
-
# The easiest way to use this function it to simply supply a symbol. Here is a list of the most commonly used ones:
|
161
|
-
#
|
162
|
-
# - `:NoError`: No error occurred.
|
163
|
-
# - `:FormErr`: The incoming data was not formatted correctly.
|
164
|
-
# - `:ServFail`: The operation caused a server failure (internal error, etc).
|
165
|
-
# - `:NXDomain`: Non-eXistant Domain (domain record does not exist).
|
166
|
-
# - `:NotImp`: The operation requested is not implemented.
|
167
|
-
# - `:Refused`: The operation was refused by the server.
|
168
|
-
# - `:NotAuth`: The server is not authoritive for the zone.
|
169
|
-
#
|
170
|
-
# See [RFC2929](http://www.rfc-editor.org/rfc/rfc2929.txt) for more information about DNS error codes (specifically, page 3).
|
171
|
-
#
|
172
|
-
# **This function will complete deferred transactions.**
|
173
|
-
def fail!(rcode)
|
174
|
-
append_question!
|
175
|
-
|
176
|
-
if rcode.kind_of? Symbol
|
177
|
-
@response.rcode = Resolv::DNS::RCode.const_get(rcode)
|
178
|
-
else
|
179
|
-
@response.rcode = rcode.to_i
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
# @deprecated
|
184
|
-
def failure!(*args)
|
185
|
-
@server.logger.warn "failure! is deprecated, use fail! instead"
|
186
|
-
|
187
|
-
fail!(*args)
|
188
|
-
end
|
189
|
-
|
190
|
-
# A helper method to process the transaction on the given server. Unless the transaction is deferred, it will {#succeed} on completion.
|
191
|
-
def process
|
192
|
-
@server.process(name, @resource_class, self)
|
193
|
-
end
|
194
|
-
|
195
|
-
protected
|
196
|
-
|
197
|
-
# A typical response to a DNS request includes both the question and response. This helper appends the question unless it looks like the user is already managing that aspect of the response.
|
198
|
-
def append_question!
|
199
|
-
if @response.question.size == 0
|
200
|
-
@response.add_question(@question, @resource_class)
|
201
|
-
end
|
202
|
-
end
|
203
|
-
end
|
204
|
-
end
|