rubydns 0.5.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +60 -7
- data/lib/rubydns.rb +1 -3
- data/lib/rubydns/extensions/{resolv-1.8.rb → resolv.rb} +0 -25
- data/lib/rubydns/handler.rb +13 -4
- data/lib/rubydns/message.rb +2 -0
- data/lib/rubydns/server.rb +78 -56
- data/lib/rubydns/transaction.rb +33 -9
- data/lib/rubydns/version.rb +1 -1
- data/test/examples/GeoLiteCountry.dat +0 -0
- data/test/{daemon1.rb → examples/dropping-dns.rb} +10 -10
- data/test/{fortune-dns.rb → examples/fortune-dns.rb} +0 -2
- data/test/examples/geoip-dns.rb +86 -0
- data/test/examples/log/DroppingDaemon.log +107 -0
- data/test/examples/log/FortuneDNS.log +5 -0
- data/test/examples/log/GeoIPDNSDaemon.log +0 -0
- data/test/examples/log/TestDaemon.log +340 -0
- data/test/examples/run/DroppingDaemon.pid +1 -0
- data/test/examples/run/GeoIPDNSDaemon.pid +1 -0
- data/test/{soa_example1.rb → examples/soa-dns.rb} +5 -3
- data/test/{example1.rb → examples/test-dns-1.rb} +4 -3
- data/test/{daemon2.rb → examples/test-dns-2.rb} +4 -4
- data/test/log/BasicTestServer.log +64 -1453
- data/test/log/SlowServer.log +48 -0
- data/test/log/TestPassthroughServer.log +24 -790
- data/test/log/TruncatedServer.log +52 -959
- data/test/test_daemon.rb +5 -3
- data/test/test_passthrough.rb +4 -2
- data/test/test_rules.rb +53 -0
- data/test/test_slow_server.rb +77 -0
- data/test/test_truncation.rb +4 -2
- metadata +39 -31
- data/lib/rubydns/extensions/resolv-1.9.rb +0 -111
- data/test/TruncatedServer/log/daemon.log +0 -445
- data/test/log/FortuneDNS.log +0 -265
data/README.md
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
RubyDNS
|
2
2
|
=======
|
3
3
|
|
4
|
-
* Author: Samuel G. D. Williams (<http://www.oriontransfer.co.nz>)
|
5
|
-
* Copyright (C) 2009, 2011 Samuel G. D. Williams.
|
6
4
|
* Released under the MIT license.
|
5
|
+
* Copyright (C) 2009, 2011 [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams/).
|
7
6
|
* [](http://travis-ci.org/ioquatix/rubydns)
|
8
7
|
|
9
8
|
RubyDNS is a simple programmatic DSL (domain specific language) for configuring and running a DNS server. RubyDNS provides a daemon that runs a DNS server which can process DNS requests depending on specific policy. Rule selection is based on pattern matching, and results can be hard-coded, computed, fetched from a remote DNS server, fetched from a local cache, etc.
|
@@ -63,8 +62,6 @@ After starting this server you can test it using dig:
|
|
63
62
|
Compatibility
|
64
63
|
-------------
|
65
64
|
|
66
|
-
From RubyDNS version `0.4.0`, the recommended minimum Ruby version is `1.9.3` for complete support. Some features may not work as expected on Ruby version `1.8.x` and it is not tested significantly.
|
67
|
-
|
68
65
|
### Migrating from RubyDNS 0.3.x to 0.4.x ###
|
69
66
|
|
70
67
|
Due to changes in `resolv.rb`, superficial parts of RubyDNS have changed. Rather than using `:A` to specify A-records, one must now use the class name.
|
@@ -76,12 +73,68 @@ becomes
|
|
76
73
|
IN = Resolv::DNS::Resource::IN
|
77
74
|
match(..., IN::A)
|
78
75
|
|
76
|
+
### Migrating from RubyDNS 0.4.x to 0.5.x ###
|
77
|
+
|
78
|
+
The system standard resolver was synchronous, and this could stall the server when making upstream requests to other DNS servers. A new resolver `RubyDNS::Resolver` now provides an asynchronous interface and the `Transaction::passthrough` makes exclusive use of this to provide high performance asynchonous resolution.
|
79
|
+
|
80
|
+
Here is a basic example of how to use the new resolver in full. It is important to provide both `:udp` and `:tcp` connection specifications, so that large requests will be handled correctly:
|
81
|
+
|
82
|
+
resolver = RubyDNS::Resolver.new([[:udp, "8.8.8.8", 53], [:tcp, "8.8.8.8", 53]])
|
83
|
+
|
84
|
+
EventMachine::run do
|
85
|
+
resolver.query('google.com', IN::A) do |response|
|
86
|
+
case response
|
87
|
+
when RubyDNS::Message
|
88
|
+
puts "Got response: #{response.answers.first}"
|
89
|
+
else
|
90
|
+
# Response is of class RubyDNS::ResolutionFailure
|
91
|
+
puts "Failed: #{response.message}"
|
92
|
+
end
|
93
|
+
|
94
|
+
EventMachine::stop
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
Existing code that uses `Resolv::DNS` as a resolver will need to be updated:
|
99
|
+
|
100
|
+
# 1/ Add this at the top of your file; Host specific system information:
|
101
|
+
require 'rubydns/system'
|
102
|
+
|
103
|
+
# 2/ Change from R = Resolv::DNS.new to:
|
104
|
+
R = RubyDNS::Resolver.new(RubyDNS::System::nameservers)
|
105
|
+
|
106
|
+
Everything else in the server can remain the same. You can see a complete example in `test/test_resolver.rb`.
|
107
|
+
|
108
|
+
#### Deferred Transactions ####
|
109
|
+
|
110
|
+
The implementation of the above depends on a new feature which was added in 0.5.0:
|
111
|
+
|
112
|
+
transaction.defer!
|
113
|
+
|
114
|
+
Once you call this, the transaction won't complete until you call either `transaction.succeed` or `transaction.fail`.
|
115
|
+
|
116
|
+
RubyDNS::run_server(:listen => SERVER_PORTS) do
|
117
|
+
match(/\.*.com/, IN::A) do |match, transaction|
|
118
|
+
transaction.defer!
|
119
|
+
|
120
|
+
# No domain exists, after 5 seconds:
|
121
|
+
EventMachine::Timer.new(5) do
|
122
|
+
transaction.failure!(:NXDomain)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
otherwise do
|
127
|
+
transaction.failure!(:NXDomain)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
You can see a complete example in `test/test_slow_server.rb`.
|
132
|
+
|
79
133
|
Todo
|
80
134
|
----
|
81
135
|
|
82
|
-
* Support for more features of DNS such as zone transfer
|
83
|
-
* Support reverse records more easily
|
84
|
-
* Better support for deferred requests/concurrency.
|
136
|
+
* Support for more features of DNS such as zone transfer.
|
137
|
+
* Support reverse records more easily.
|
85
138
|
|
86
139
|
License
|
87
140
|
-------
|
data/lib/rubydns.rb
CHANGED
@@ -21,16 +21,14 @@
|
|
21
21
|
require 'rubydns/version'
|
22
22
|
|
23
23
|
if RUBY_VERSION < "1.9"
|
24
|
-
require 'rubydns/extensions/resolv-1.8'
|
25
24
|
require 'rubydns/extensions/string-1.8'
|
26
25
|
elsif RUBY_VERSION < "1.9.3"
|
27
|
-
require 'rubydns/extensions/resolv-1.9'
|
28
26
|
require 'rubydns/extensions/string-1.9.2'
|
29
27
|
else
|
30
|
-
require 'rubydns/extensions/resolv-1.9'
|
31
28
|
require 'rubydns/extensions/string-1.9.3'
|
32
29
|
end
|
33
30
|
|
31
|
+
require 'rubydns/message'
|
34
32
|
require 'rubydns/server'
|
35
33
|
require 'rubydns/resolver'
|
36
34
|
require 'rubydns/handler'
|
@@ -22,31 +22,6 @@ require 'resolv'
|
|
22
22
|
|
23
23
|
class Resolv
|
24
24
|
class DNS
|
25
|
-
# Queries the given DNS server and returns its response in its entirety.
|
26
|
-
# This allows such responses to be passed upstream with little or no
|
27
|
-
# modification/reinterpretation.
|
28
|
-
def query(name, typeclass)
|
29
|
-
lazy_initialize
|
30
|
-
requester = make_requester
|
31
|
-
senders = {}
|
32
|
-
begin
|
33
|
-
@config.resolv(name) {|candidate, tout, nameserver|
|
34
|
-
msg = Message.new
|
35
|
-
msg.rd = 1
|
36
|
-
msg.add_question(candidate, typeclass)
|
37
|
-
unless sender = senders[[candidate, nameserver]]
|
38
|
-
sender = senders[[candidate, nameserver]] =
|
39
|
-
requester.sender(msg, candidate, nameserver)
|
40
|
-
end
|
41
|
-
reply, reply_name = requester.request(sender, tout)
|
42
|
-
|
43
|
-
return reply, reply_name
|
44
|
-
}
|
45
|
-
ensure
|
46
|
-
requester.close
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
25
|
class Message
|
51
26
|
# Merge the given message with this message. A number of heuristics are
|
52
27
|
# applied in order to ensure that the result makes sense. For example,
|
data/lib/rubydns/handler.rb
CHANGED
@@ -21,19 +21,24 @@
|
|
21
21
|
require 'rubydns/message'
|
22
22
|
|
23
23
|
module RubyDNS
|
24
|
+
|
25
|
+
def self.get_peer_details(connection)
|
26
|
+
Socket.unpack_sockaddr_in(connection.get_peername)[1]
|
27
|
+
end
|
28
|
+
|
24
29
|
module UDPHandler
|
25
30
|
def initialize(server)
|
26
31
|
@server = server
|
27
32
|
end
|
28
33
|
|
29
|
-
def self.process(server, data, &block)
|
34
|
+
def self.process(server, data, options = {}, &block)
|
30
35
|
server.logger.debug "Receiving incoming query (#{data.bytesize} bytes)..."
|
31
36
|
query = nil
|
32
37
|
|
33
38
|
begin
|
34
39
|
query = RubyDNS::decode_message(data)
|
35
40
|
|
36
|
-
return server.process_query(query, &block)
|
41
|
+
return server.process_query(query, options, &block)
|
37
42
|
rescue
|
38
43
|
server.logger.error "Error processing request!"
|
39
44
|
server.logger.error "#{$!.class}: #{$!.message}"
|
@@ -56,7 +61,9 @@ module RubyDNS
|
|
56
61
|
end
|
57
62
|
|
58
63
|
def receive_data(data)
|
59
|
-
|
64
|
+
options = {:peer => RubyDNS::get_peer_details(self)}
|
65
|
+
|
66
|
+
UDPHandler.process(@server, data, options) do |answer|
|
60
67
|
data = answer.encode
|
61
68
|
|
62
69
|
@server.logger.debug "Writing response to client (#{data.bytesize} bytes) via UDP..."
|
@@ -107,7 +114,9 @@ module RubyDNS
|
|
107
114
|
if (@buffer.size - @processed) >= @length
|
108
115
|
data = @buffer.string.byteslice(@processed, @length)
|
109
116
|
|
110
|
-
|
117
|
+
options = {:peer => RubyDNS::get_peer_details(self)}
|
118
|
+
|
119
|
+
UDPHandler.process(@server, data, options) do |answer|
|
111
120
|
data = answer.encode
|
112
121
|
|
113
122
|
@server.logger.debug "Writing response to client (#{data.bytesize} bytes) via TCP..."
|
data/lib/rubydns/message.rb
CHANGED
data/lib/rubydns/server.rb
CHANGED
@@ -26,6 +26,61 @@ module RubyDNS
|
|
26
26
|
# are used to match against incoming DNS questions. These rules are used to
|
27
27
|
# generate responses which are either DNS resource records or failures.
|
28
28
|
class Server
|
29
|
+
class Rule
|
30
|
+
def initialize(pattern, callback)
|
31
|
+
@pattern = pattern
|
32
|
+
@callback = callback
|
33
|
+
end
|
34
|
+
|
35
|
+
def match(name, resource_class)
|
36
|
+
# If the pattern doesn't specify any resource classes, we implicitly pass this test:
|
37
|
+
return true if @pattern.size < 2
|
38
|
+
|
39
|
+
# Otherwise, we try to match against some specific resource classes:
|
40
|
+
if Class === @pattern[1]
|
41
|
+
@pattern[1] == resource_class
|
42
|
+
else
|
43
|
+
@pattern[1].include?(resource_class) rescue false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def call(server, name, resource_class, *args)
|
48
|
+
unless match(name, resource_class)
|
49
|
+
server.logger.debug "Resource class #{resource_class} failed to match #{@pattern[1].inspect}!"
|
50
|
+
|
51
|
+
return false
|
52
|
+
end
|
53
|
+
|
54
|
+
# Match succeeded against name?
|
55
|
+
case @pattern[0]
|
56
|
+
when Regexp
|
57
|
+
match_data = @pattern[0].match(name)
|
58
|
+
if match_data
|
59
|
+
server.logger.debug "Regexp pattern matched with #{match_data.inspect}."
|
60
|
+
return @callback[match_data, *args]
|
61
|
+
end
|
62
|
+
when String
|
63
|
+
if @pattern[0] == name
|
64
|
+
server.logger.debug "String pattern matched."
|
65
|
+
return @callback[*args]
|
66
|
+
end
|
67
|
+
else
|
68
|
+
if (@pattern[0].call(name, resource_class) rescue false)
|
69
|
+
server.logger.debug "Callable pattern matched."
|
70
|
+
return @callback[*args]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
server.logger.debug "No pattern matched."
|
75
|
+
# We failed to match the pattern.
|
76
|
+
return false
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_s
|
80
|
+
@pattern.inspect
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
29
84
|
# Instantiate a server with a block
|
30
85
|
#
|
31
86
|
# server = Server.new do
|
@@ -58,7 +113,7 @@ module RubyDNS
|
|
58
113
|
# match(/g?mail.(com|org|net)/, [IN::MX, IN::A])
|
59
114
|
#
|
60
115
|
def match(*pattern, &block)
|
61
|
-
@rules <<
|
116
|
+
@rules << Rule.new(pattern, block)
|
62
117
|
end
|
63
118
|
|
64
119
|
# Register a named event which may be invoked later using #fire
|
@@ -66,7 +121,7 @@ module RubyDNS
|
|
66
121
|
# RExec.change_user(RUN_AS)
|
67
122
|
# end
|
68
123
|
def on(event_name, &block)
|
69
|
-
@events[event_name] =
|
124
|
+
@events[event_name] = block
|
70
125
|
end
|
71
126
|
|
72
127
|
# Fire the named event, which must have been registered using on.
|
@@ -87,7 +142,11 @@ module RubyDNS
|
|
87
142
|
# end
|
88
143
|
#
|
89
144
|
def otherwise(&block)
|
90
|
-
@otherwise =
|
145
|
+
@otherwise = block
|
146
|
+
end
|
147
|
+
|
148
|
+
def next!
|
149
|
+
throw :next
|
91
150
|
end
|
92
151
|
|
93
152
|
# Give a name and a record type, try to match a rule and use it for
|
@@ -99,55 +158,11 @@ module RubyDNS
|
|
99
158
|
@logger.debug "Searching for #{name} #{resource_class.name}"
|
100
159
|
|
101
160
|
@rules.each do |rule|
|
102
|
-
@logger.debug "Checking rule #{rule
|
103
|
-
|
104
|
-
pattern = rule[0]
|
105
|
-
|
106
|
-
# Match failed against resource_class?
|
107
|
-
case pattern[1]
|
108
|
-
when Class
|
109
|
-
next unless pattern[1] == resource_class
|
110
|
-
@logger.debug "Resource class #{resource_class.name} matched"
|
111
|
-
when Array
|
112
|
-
next unless pattern[1].include?(resource_class)
|
113
|
-
@logger.debug "Resource class #{resource_class} matched #{pattern[1].inspect}"
|
114
|
-
end
|
161
|
+
@logger.debug "Checking rule #{rule}..."
|
115
162
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
match_data = pattern[0].match(name)
|
120
|
-
if match_data
|
121
|
-
@logger.debug "Query #{name} matched #{pattern[0].to_s} with result #{match_data.inspect}"
|
122
|
-
if rule[1].call(match_data, *args)
|
123
|
-
@logger.debug "Rule returned successfully"
|
124
|
-
return
|
125
|
-
end
|
126
|
-
else
|
127
|
-
@logger.debug "Query #{name} failed to match against #{pattern[0].to_s}"
|
128
|
-
end
|
129
|
-
when String
|
130
|
-
if pattern[0] == name
|
131
|
-
@logger.debug "Query #{name} matched #{pattern[0]}"
|
132
|
-
if rule[1].call(*args)
|
133
|
-
@logger.debug "Rule returned successfully"
|
134
|
-
return
|
135
|
-
end
|
136
|
-
else
|
137
|
-
@logger.debug "Query #{name} failed to match against #{pattern[0]}"
|
138
|
-
end
|
139
|
-
else
|
140
|
-
if pattern[0].respond_to? :call
|
141
|
-
if pattern[0].call(name)
|
142
|
-
@logger.debug "Query #{name} matched #{pattern[0]}"
|
143
|
-
if rule[1].call(*args)
|
144
|
-
@logger.debug "Rule returned successfully"
|
145
|
-
return
|
146
|
-
end
|
147
|
-
else
|
148
|
-
@logger.debug "Query #{name} failed to match against #{pattern[0]}"
|
149
|
-
end
|
150
|
-
end
|
163
|
+
catch (:next) do
|
164
|
+
# If the rule returns true, we assume that it was successful and no further rules need to be evaluated.
|
165
|
+
return true if rule.call(self, name, resource_class, *args)
|
151
166
|
end
|
152
167
|
end
|
153
168
|
|
@@ -160,7 +175,7 @@ module RubyDNS
|
|
160
175
|
|
161
176
|
# Process an incoming DNS message. Returns a serialized message to be
|
162
177
|
# sent back to the client.
|
163
|
-
def process_query(query, &block)
|
178
|
+
def process_query(query, options = {}, &block)
|
164
179
|
# Setup answer
|
165
180
|
answer = Resolv::DNS::Message::new(query.id)
|
166
181
|
answer.qr = 1 # 0 = Query, 1 = Response
|
@@ -186,7 +201,7 @@ module RubyDNS
|
|
186
201
|
chain << lambda do
|
187
202
|
@logger.debug "Processing question #{question} #{resource_class}..."
|
188
203
|
|
189
|
-
transaction = Transaction.new(self, query, question, resource_class, answer)
|
204
|
+
transaction = Transaction.new(self, query, question, resource_class, answer, options)
|
190
205
|
|
191
206
|
# Call the next link in the chain:
|
192
207
|
transaction.callback do
|
@@ -196,9 +211,16 @@ module RubyDNS
|
|
196
211
|
|
197
212
|
# If there was an error, log it and fail:
|
198
213
|
transaction.errback do |response|
|
199
|
-
|
200
|
-
|
201
|
-
|
214
|
+
if Exception === response
|
215
|
+
@logger.error "Exception thrown while processing #{transaction}!"
|
216
|
+
@logger.error "#{response.class}: #{response.message}"
|
217
|
+
if response.backtrace
|
218
|
+
Array(response.backtrace).each { |at| @logger.error at }
|
219
|
+
end
|
220
|
+
else
|
221
|
+
@logger.error "Failure while processing #{transaction}!"
|
222
|
+
@logger.error "#{response.inspect}"
|
223
|
+
end
|
202
224
|
|
203
225
|
answer.rcode = Resolv::DNS::RCode::ServFail
|
204
226
|
|
data/lib/rubydns/transaction.rb
CHANGED
@@ -27,13 +27,15 @@ module RubyDNS
|
|
27
27
|
class Transaction
|
28
28
|
include EventMachine::Deferrable
|
29
29
|
|
30
|
-
def initialize(server, query, question, resource_class, answer)
|
30
|
+
def initialize(server, query, question, resource_class, answer, options = {})
|
31
31
|
@server = server
|
32
32
|
@query = query
|
33
33
|
@question = question
|
34
34
|
@resource_class = resource_class
|
35
35
|
@answer = answer
|
36
36
|
|
37
|
+
@options = options
|
38
|
+
|
37
39
|
@deferred = false
|
38
40
|
@question_appended = false
|
39
41
|
end
|
@@ -51,6 +53,9 @@ module RubyDNS
|
|
51
53
|
# The current full answer to the incoming query.
|
52
54
|
attr :answer
|
53
55
|
|
56
|
+
# Any options or configuration associated with the given transaction.
|
57
|
+
attr :options
|
58
|
+
|
54
59
|
# Return the name of the question, which is typically the requested hostname.
|
55
60
|
def name
|
56
61
|
@question.to_s
|
@@ -63,8 +68,8 @@ module RubyDNS
|
|
63
68
|
|
64
69
|
# Run a new query through the rules with the given name and resource type. The
|
65
70
|
# results of this query are appended to the current transactions <tt>answer</tt>.
|
66
|
-
def append_query!(name, resource_class = nil)
|
67
|
-
Transaction.new(@server, @query, name, resource_class || @resource_class, @answer).process
|
71
|
+
def append_query!(name, resource_class = nil, options = {})
|
72
|
+
Transaction.new(@server, @query, name, resource_class || @resource_class, @answer, options).process
|
68
73
|
end
|
69
74
|
|
70
75
|
def process(&finished)
|
@@ -93,6 +98,8 @@ module RubyDNS
|
|
93
98
|
end
|
94
99
|
|
95
100
|
@answer.merge!(response)
|
101
|
+
|
102
|
+
succeed if @deferred
|
96
103
|
end
|
97
104
|
|
98
105
|
true
|
@@ -117,8 +124,10 @@ module RubyDNS
|
|
117
124
|
case response
|
118
125
|
when RubyDNS::Message
|
119
126
|
yield response
|
120
|
-
|
127
|
+
when RubyDNS::ResolverFailure
|
128
|
+
failure!(:ServFail)
|
121
129
|
else
|
130
|
+
# This shouldn't ever happen, but if it does for some reason we shouldn't hang.
|
122
131
|
fail(response)
|
123
132
|
end
|
124
133
|
end
|
@@ -166,6 +175,8 @@ module RubyDNS
|
|
166
175
|
#
|
167
176
|
# <tt>options[:ttl]</tt>:: Specify the TTL for the resource
|
168
177
|
# <tt>options[:name]</tt>:: Override the name (question) of the response.
|
178
|
+
# <tt>options[:section]</tt>:: Specify whether the response should go in the `:answer`
|
179
|
+
# `:authority` or `:additional` section.
|
169
180
|
#
|
170
181
|
# This function can be used to supply multiple responses to a given question.
|
171
182
|
# For example, each argument is expected to be an instantiated resource from
|
@@ -173,17 +184,27 @@ module RubyDNS
|
|
173
184
|
def append! (*resources)
|
174
185
|
append_question!
|
175
186
|
|
176
|
-
|
187
|
+
if resources.last.kind_of?(Hash)
|
188
|
+
options = resources.pop
|
189
|
+
else
|
190
|
+
options = {}
|
191
|
+
end
|
192
|
+
|
193
|
+
# Use the default options if provided:
|
194
|
+
options = options.merge(@options)
|
195
|
+
|
177
196
|
options[:ttl] ||= 16000
|
178
197
|
options[:name] ||= @question.to_s + "."
|
198
|
+
|
199
|
+
method = ("add_" + (options[:section] || 'answer').to_s).to_sym
|
179
200
|
|
180
201
|
resources.each do |resource|
|
181
|
-
@server.logger.debug "
|
182
|
-
|
202
|
+
@server.logger.debug "#{method}: #{resource.inspect} #{resource.class::TypeValue} #{resource.class::ClassValue}"
|
203
|
+
|
204
|
+
@answer.send(method, options[:name], options[:ttl], resource)
|
183
205
|
end
|
184
206
|
|
185
|
-
|
186
|
-
@answer.encode
|
207
|
+
succeed if @deferred
|
187
208
|
|
188
209
|
true
|
189
210
|
end
|
@@ -214,6 +235,9 @@ module RubyDNS
|
|
214
235
|
@answer.rcode = rcode.to_i
|
215
236
|
end
|
216
237
|
|
238
|
+
# The transaction itself has completed, but contains a failure:
|
239
|
+
succeed(rcode) if @deferred
|
240
|
+
|
217
241
|
true
|
218
242
|
end
|
219
243
|
|