rubydns 0.6.7 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -4
- data/.yardopts +1 -0
- data/README.md +98 -92
- data/lib/rubydns.rb +5 -47
- data/lib/rubydns/{extensions/string-1.9.3.rb → binary_string.rb} +2 -0
- data/lib/rubydns/extensions/logger.rb +1 -1
- data/lib/rubydns/extensions/resolv.rb +1 -5
- data/lib/rubydns/extensions/string.rb +1 -0
- data/lib/rubydns/handler.rb +7 -2
- data/lib/rubydns/message.rb +17 -4
- data/lib/rubydns/resolver.rb +3 -0
- data/lib/rubydns/server.rb +182 -93
- data/lib/rubydns/transaction.rb +110 -149
- data/lib/rubydns/version.rb +1 -1
- data/rubydns.gemspec +1 -1
- data/test/examples/dropping-dns.rb +2 -2
- data/test/examples/fortune-dns.rb +2 -2
- data/test/examples/soa-dns.rb +1 -1
- data/test/examples/wikipedia-dns.rb +2 -2
- data/test/test_daemon.rb +3 -3
- data/test/test_message.rb +41 -0
- data/test/test_passthrough.rb +5 -1
- data/test/test_rules.rb +3 -3
- data/test/test_slow_server.rb +8 -6
- data/test/test_truncation.rb +3 -3
- metadata +10 -9
- data/lib/rubydns/extensions/hexdump.rb +0 -38
- data/lib/rubydns/extensions/string-1.8.rb +0 -35
- data/lib/rubydns/extensions/string-1.9.2.rb +0 -29
data/lib/rubydns/resolver.rb
CHANGED
@@ -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
|
|
data/lib/rubydns/server.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
91
|
-
#
|
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
|
-
#
|
113
|
-
#
|
114
|
-
#
|
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
|
-
#
|
122
|
-
#
|
123
|
-
#
|
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
|
-
#
|
142
|
-
#
|
143
|
-
#
|
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
|
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
|
178
|
-
|
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
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
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
|
-
|
236
|
-
|
237
|
-
|
238
|
-
chain.last.call
|
325
|
+
|
326
|
+
yield answer
|
327
|
+
end.resume
|
239
328
|
end
|
240
329
|
end
|
241
330
|
end
|
data/lib/rubydns/transaction.rb
CHANGED
@@ -21,11 +21,16 @@
|
|
21
21
|
require 'eventmachine'
|
22
22
|
|
23
23
|
module RubyDNS
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
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
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
-
|
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.
|
109
|
-
#
|
110
|
-
#
|
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
|
-
#
|
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
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
132
|
-
|
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
|
-
|
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
|
-
#
|
160
|
-
|
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
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
options =
|
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
|
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(*
|
144
|
+
resource = resource_class.new(*args)
|
175
145
|
@server.logger.info "Resource: #{resource.inspect}"
|
176
146
|
|
177
|
-
|
147
|
+
add([resource], options)
|
178
148
|
end
|
179
|
-
|
180
|
-
# Append a
|
181
|
-
#
|
182
|
-
#
|
183
|
-
#
|
184
|
-
#
|
185
|
-
|
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
|
-
|
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,
|
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
|
-
#
|
227
|
-
#
|
228
|
-
#
|
229
|
-
#
|
230
|
-
#
|
231
|
-
#
|
232
|
-
#
|
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
|
-
#
|
236
|
-
|
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
|