rubydns 0.8.5 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -0
  3. data/Gemfile +0 -4
  4. data/README.md +16 -8
  5. data/Rakefile +6 -6
  6. data/{test/examples → examples}/dropping-dns.rb +0 -0
  7. data/lib/rubydns/handler.rb +133 -102
  8. data/lib/rubydns/message.rb +0 -1
  9. data/lib/rubydns/resolver.rb +135 -187
  10. data/lib/rubydns/server.rb +92 -86
  11. data/lib/rubydns/transaction.rb +24 -48
  12. data/lib/rubydns/{binary_string.rb → transport.rb} +38 -0
  13. data/lib/rubydns/version.rb +1 -1
  14. data/lib/rubydns.rb +15 -8
  15. data/rubydns.gemspec +5 -2
  16. data/{test/test_daemon.rb → spec/rubydns/daemon_spec.rb} +36 -48
  17. data/{test → spec/rubydns}/hosts.txt +0 -0
  18. data/{test/test_rules.rb → spec/rubydns/message_spec.rb} +26 -44
  19. data/spec/rubydns/passthrough_spec.rb +78 -0
  20. data/spec/rubydns/resolver_performance_spec.rb +110 -0
  21. data/spec/rubydns/resolver_spec.rb +144 -0
  22. data/spec/rubydns/rules_spec.rb +74 -0
  23. data/{test/performance → spec/rubydns/server}/benchmark.rb +0 -0
  24. data/{test/performance → spec/rubydns/server}/bind9/generate-local.rb +0 -0
  25. data/{test/performance → spec/rubydns/server}/bind9/local.zone +0 -0
  26. data/{test/performance → spec/rubydns/server}/bind9/named.conf +0 -0
  27. data/spec/rubydns/server/bind9/named.run +0 -0
  28. data/{test/performance → spec/rubydns/server}/million.rb +1 -3
  29. data/spec/rubydns/server/rubydns.stackprof +0 -0
  30. data/spec/rubydns/server_performance_spec.rb +136 -0
  31. data/spec/rubydns/slow_server_spec.rb +89 -0
  32. data/spec/rubydns/socket_spec.rb +77 -0
  33. data/{test/test_system.rb → spec/rubydns/system_spec.rb} +28 -22
  34. data/spec/rubydns/transaction_spec.rb +64 -0
  35. data/{test/test_truncation.rb → spec/rubydns/truncation_spec.rb} +22 -48
  36. metadata +91 -54
  37. data/test/examples/fortune-dns.rb +0 -107
  38. data/test/examples/geoip-dns.rb +0 -76
  39. data/test/examples/soa-dns.rb +0 -82
  40. data/test/examples/test-dns-1.rb +0 -77
  41. data/test/examples/test-dns-2.rb +0 -83
  42. data/test/examples/wikipedia-dns.rb +0 -112
  43. data/test/test_message.rb +0 -65
  44. data/test/test_passthrough.rb +0 -120
  45. data/test/test_resolver.rb +0 -106
  46. data/test/test_resolver_performance.rb +0 -123
  47. data/test/test_server_performance.rb +0 -134
  48. data/test/test_slow_server.rb +0 -125
@@ -18,19 +18,24 @@
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_relative 'message'
22
- require_relative 'binary_string'
21
+ require_relative 'handler'
23
22
 
24
23
  require 'securerandom'
24
+ require 'celluloid/io'
25
25
 
26
26
  module RubyDNS
27
27
  class InvalidProtocolError < StandardError
28
28
  end
29
29
 
30
+ class InvalidResponseError < StandardError
31
+ end
32
+
30
33
  class ResolutionFailure < StandardError
31
34
  end
32
35
 
33
36
  class Resolver
37
+ include Celluloid::IO
38
+
34
39
  # Servers are specified in the same manor as options[:listen], e.g.
35
40
  # [:tcp/:udp, address, port]
36
41
  # In the case of multiple servers, they will be checked in sequence.
@@ -38,6 +43,8 @@ module RubyDNS
38
43
  @servers = servers
39
44
 
40
45
  @options = options
46
+
47
+ @logger = options[:logger] || Celluloid.logger
41
48
  end
42
49
 
43
50
  # Provides the next sequence identification number which is used to keep track of DNS messages.
@@ -47,54 +54,138 @@ module RubyDNS
47
54
  end
48
55
 
49
56
  # Look up a named resource of the given resource_class.
50
- def query(name, resource_class = Resolv::DNS::Resource::IN::A, &block)
57
+ def query(name, resource_class = Resolv::DNS::Resource::IN::A)
51
58
  message = Resolv::DNS::Message.new(next_id!)
52
59
  message.rd = 1
53
60
  message.add_question name, resource_class
54
61
 
55
- send_message(message, &block)
62
+ dispatch_request(message)
56
63
  end
57
-
58
- def send_message(message, &block)
59
- Request.fetch(message, @servers, @options, &block)
60
- end
61
-
62
- # Yields a list of `Resolv::IPv4` and `Resolv::IPv6` addresses for the given `name` and `resource_class`.
63
- def addresses_for(name, resource_class = Resolv::DNS::Resource::IN::A, &block)
64
- query(name, resource_class) do |response|
65
- # Resolv::DNS::Name doesn't retain the trailing dot.
66
- name = name.sub(/\.$/, '')
67
-
68
- case response
69
- when Message
70
- yield response.answer.select{|record| record[0].to_s == name}.collect{|record| record[2].address}
71
- else
72
- yield []
64
+
65
+ # Yields a list of `Resolv::IPv4` and `Resolv::IPv6` addresses for the given `name` and `resource_class`. Raises a ResolutionFailure if no severs respond.
66
+ def addresses_for(name, resource_class = Resolv::DNS::Resource::IN::A, options = {})
67
+ (options[:retries] || 5).times do
68
+ response = query(name, resource_class)
69
+
70
+ if response
71
+ # Resolv::DNS::Name doesn't retain the trailing dot.
72
+ name = name.sub(/\.$/, '')
73
+
74
+ return response.answer.select{|record| record[0].to_s == name}.collect{|record| record[2].address}
73
75
  end
76
+
77
+ # Wait 10ms before trying again:
78
+ sleep 0.01
74
79
  end
80
+
81
+ abort ResolutionFailure.new("No server replied.")
75
82
  end
76
-
77
- # Manages a single DNS question message across one or more servers.
78
- class Request
79
- include EventMachine::Deferrable
83
+
84
+ def request_timeout
85
+ @options[:timeout] || 1
86
+ end
87
+
88
+ # 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.
89
+ def dispatch_request(message)
90
+ request = Request.new(message, @servers)
80
91
 
81
- def self.fetch(*args)
82
- request = self.new(*args)
83
-
84
- request.callback do |message|
85
- yield message
86
- end
92
+ request.each do |server|
93
+ @logger.debug "[#{message.id}] Sending request to server #{server.inspect}" if @logger
87
94
 
88
- request.errback do |error|
89
- # In the case of a timeout, error will be nil, so we make one up.
95
+ begin
96
+ response = nil
97
+
98
+ timeout(request_timeout) do
99
+ response = try_server(request, server)
100
+ end
90
101
 
91
- yield error
102
+ if valid_response(message, response)
103
+ return response
104
+ end
105
+ rescue Task::TimeoutError
106
+ @logger.debug "[#{message.id}] Request timed out!" if @logger
107
+ rescue InvalidResponseError
108
+ @logger.warn "[#{message.id}] Invalid response from network: #{$!}!" if @logger
109
+ rescue DecodeError
110
+ @logger.warn "[#{message.id}] Error while decoding data from network: #{$!}!" if @logger
111
+ rescue IOError
112
+ @logger.warn "[#{message.id}] Error while reading from network: #{$!}!" if @logger
92
113
  end
93
-
94
- request.run!
95
114
  end
96
115
 
97
- def initialize(message, servers, options = {}, &block)
116
+ return nil
117
+ end
118
+
119
+ private
120
+
121
+ def try_server(request, server)
122
+ case server[0]
123
+ when :udp
124
+ try_udp_server(request, server[1], server[2])
125
+ when :tcp
126
+ try_tcp_server(request, server[1], server[2])
127
+ else
128
+ raise InvalidProtocolError.new(server)
129
+ end
130
+ end
131
+
132
+ def valid_response(message, response)
133
+ if response.tc != 0
134
+ @logger.warn "[#{message.id}] Received truncated response!" if @logger
135
+ elsif response.id != message.id
136
+ @logger.warn "[#{message.id}] Received response with incorrect message id: #{response.id}!" if @logger
137
+ else
138
+ @logger.debug "[#{message.id}] Received valid response with #{response.answer.count} answer(s)." if @logger
139
+
140
+ return true
141
+ end
142
+
143
+ return false
144
+ end
145
+
146
+ def try_udp_server(request, host, port)
147
+ socket = UDPSocket.new
148
+
149
+ socket.send(request.packet, 0, host, port)
150
+
151
+ data, (_, remote_port) = socket.recvfrom(UDP_TRUNCATION_SIZE)
152
+ # Need to check host, otherwise security issue.
153
+
154
+ # May indicate some kind of spoofing attack:
155
+ if port != remote_port
156
+ raise InvalidResponseError.new("Data was not received from correct remote port (#{port} != #{remote_port})")
157
+ end
158
+
159
+ message = RubyDNS::decode_message(data)
160
+ ensure
161
+ socket.close if socket
162
+ end
163
+
164
+ def try_tcp_server(request, host, port)
165
+ begin
166
+ socket = TCPSocket.new(host, port)
167
+ rescue Errno::EALREADY
168
+ raise IOError.new("Could not connect to remote host!")
169
+ end
170
+
171
+ StreamTransport.write_chunk(socket, request.packet)
172
+
173
+ input_data = StreamTransport.read_chunk(socket)
174
+
175
+ message = RubyDNS::decode_message(input_data)
176
+ rescue Errno::ECONNREFUSED => error
177
+ raise IOError.new(error.message)
178
+ rescue Errno::EPIPE => error
179
+ raise IOError.new(error.message)
180
+ rescue Errno::ECONNRESET => error
181
+ raise IOError.new(error.message)
182
+ ensure
183
+ socket.close if socket
184
+ end
185
+
186
+ # Manages a single DNS question message across one or more servers.
187
+ class Request
188
+ def initialize(message, servers)
98
189
  @message = message
99
190
  @packet = message.encode
100
191
 
@@ -104,167 +195,24 @@ module RubyDNS
104
195
  if @packet.bytesize > UDP_TRUNCATION_SIZE
105
196
  @servers.delete_if{|server| server[0] == :udp}
106
197
  end
107
-
108
- # Measured in seconds:
109
- @timeout = options[:timeout] || 1
110
-
111
- @logger = options[:logger]
112
198
  end
113
199
 
114
200
  attr :message
115
201
  attr :packet
116
202
  attr :logger
117
203
 
118
- def run!
119
- try_next_server!
120
- end
121
-
122
- # Once either an exception or message is received, we update the status of this request.
123
- def process_response!(response)
124
- finish_request!
125
-
126
- if Exception === response
127
- @logger.warn "[#{@message.id}] Failure while processing response #{response}!" if @logger
128
- RubyDNS.log_exception(@logger, response) if @logger
129
-
130
- try_next_server!
131
- elsif response.tc != 0
132
- @logger.warn "[#{@message.id}] Received truncated response!" if @logger
204
+ def each(&block)
205
+ @servers.each do |server|
206
+ next if @packet.bytesize > UDP_TRUNCATION_SIZE
133
207
 
134
- try_next_server!
135
- elsif response.id != @message.id
136
- @logger.warn "[#{@message.id}] Received response with incorrect message id: #{response.id}" if @logger
137
-
138
- try_next_server!
139
- else
140
- @logger.debug "[#{@message.id}] Received valid response #{response.inspect}" if @logger
141
-
142
- succeed response
143
- end
144
- end
145
-
146
- private
147
-
148
- # Closes any connections and cancels any timeout.
149
- def finish_request!
150
- cancel_timeout
151
-
152
- # Cancel an existing request if it is in flight:
153
- if @request
154
- @request.close_connection
155
- @request = nil
156
- end
157
- end
158
-
159
- def try_next_server!
160
- if @servers.size > 0
161
- @server = @servers.shift
162
-
163
- @logger.debug "[#{@message.id}] Sending request to server #{@server.inspect}" if @logger
164
-
165
- # We make requests one at a time to the given server, naturally the servers are ordered in terms of priority.
166
- case @server[0]
167
- when :udp
168
- @request = UDPRequestHandler.open(@server[1], @server[2], self)
169
- when :tcp
170
- @request = TCPRequestHandler.open(@server[1], @server[2], self)
171
- else
172
- raise InvalidProtocolError.new(@server)
173
- end
174
-
175
- # Setting up the timeout...
176
- timeout(@timeout)
177
- else
178
- fail ResolutionFailure.new("No available servers responded to the request.")
179
- end
180
- end
181
-
182
- def timeout seconds
183
- cancel_timeout
184
-
185
- @deferred_timeout = EventMachine::Timer.new(seconds) do
186
- @logger.debug "[#{@message.id}] Request timed out!" if @logger
187
-
188
- finish_request!
189
-
190
- try_next_server!
191
- end
192
- end
193
-
194
- module UDPRequestHandler
195
- def self.open(host, port, request)
196
- # Open a datagram socket... a random socket chosen by the OS by specifying 0 for the port:
197
- EventMachine::open_datagram_socket('', 0, self, request, host, port)
198
- end
199
-
200
- def initialize(request, host, port)
201
- @request = request
202
- @host = host
203
- @port = port
204
- end
205
-
206
- def post_init
207
- # Sending question to remote DNS server...
208
- send_datagram(@request.packet, @host, @port)
209
- end
210
-
211
- def receive_data(data)
212
- # local_port, local_ip = Socket.unpack_sockaddr_in(get_sockname)
213
- # puts "Socket name: #{local_ip}:#{local_port}"
214
-
215
- # Receiving response from remote DNS server...
216
- message = RubyDNS::decode_message(data)
217
-
218
- # The message id must match, and it can't be truncated:
219
- @request.process_response!(message)
220
- rescue Resolv::DNS::DecodeError => error
221
- @request.process_response!(error)
208
+ yield server
222
209
  end
223
210
  end
224
-
225
- module TCPRequestHandler
226
- def self.open(host, port, request)
227
- EventMachine::connect(host, port, TCPRequestHandler, request)
228
- end
229
-
230
- def initialize(request)
231
- @request = request
232
- @buffer = nil
233
- @length = nil
234
- end
235
-
236
- def post_init
237
- data = @request.packet
238
-
239
- send_data([data.bytesize].pack('n'))
240
- send_data data
241
- end
242
-
243
- def receive_data(data)
244
- # We buffer data until we've received the entire packet:
245
- @buffer ||= BinaryStringIO.new
246
- @buffer.write(data)
247
211
 
248
- # If we've received enough data and we haven't figured out the length yet...
249
- if @length == nil and @buffer.size > 2
250
- # Extract the length from the buffer:
251
- @length = @buffer.string.byteslice(0, 2).unpack('n')[0]
252
- end
253
-
254
- # If we know what the length is, and we've got that much data, we can decode the message:
255
- if @length != nil and @buffer.size >= (@length + 2)
256
- data = @buffer.string.byteslice(2, @length)
257
-
258
- message = RubyDNS::decode_message(data)
259
-
260
- @request.process_response!(message)
261
- end
262
-
263
- # If we have received more data than expected, should this be an error?
264
- rescue Resolv::DNS::DecodeError => error
265
- @request.process_response!(error)
266
- end
212
+ def update_id!(id)
213
+ @message.id = id
214
+ @packet = @message.encode
267
215
  end
268
216
  end
269
217
  end
270
- end
218
+ end
@@ -18,14 +18,29 @@
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'
21
+ require 'celluloid/io'
22
22
 
23
23
  require_relative 'transaction'
24
24
  require_relative 'logger'
25
25
 
26
26
  module RubyDNS
27
+ class UDPSocketWrapper < Celluloid::IO::UDPSocket
28
+ def initialize(socket)
29
+ @socket = socket
30
+ end
31
+ end
32
+
33
+ class TCPServerWrapper < Celluloid::IO::TCPServer
34
+ def initialize(server)
35
+ @server = server
36
+ end
37
+ end
27
38
 
28
39
  class Server
40
+ include Celluloid::IO
41
+
42
+ finalizer :shutdown
43
+
29
44
  # The default server interfaces
30
45
  DEFAULT_INTERFACES = [[:udp, "0.0.0.0", 53], [:tcp, "0.0.0.0", 53]]
31
46
 
@@ -37,8 +52,11 @@ module RubyDNS
37
52
  # end
38
53
  # end
39
54
  #
40
- def initialize(options)
41
- @logger = options[:logger] || Logger.new($stderr)
55
+ def initialize(options = {})
56
+ @handlers = []
57
+
58
+ @logger = options[:logger] || Celluloid.logger
59
+ @interfaces = options[:listen] || DEFAULT_INTERFACES
42
60
  end
43
61
 
44
62
  attr_accessor :logger
@@ -47,56 +65,51 @@ module RubyDNS
47
65
  def fire(event_name)
48
66
  end
49
67
 
68
+ def shutdown
69
+ fire(:stop)
70
+ end
71
+
50
72
  # Give a name and a record type, try to match a rule and use it for processing the given arguments.
51
73
  def process(name, resource_class, transaction)
52
74
  raise NotImplementedError.new
53
75
  end
54
76
 
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
77
  # Process an incoming DNS message. Returns a serialized message to be sent back to the client.
65
78
  def process_query(query, options = {}, &block)
66
79
  start_time = Time.now
67
80
 
68
- # Setup answer
69
- answer = Resolv::DNS::Message::new(query.id)
70
- answer.qr = 1 # 0 = Query, 1 = Response
71
- answer.opcode = query.opcode # Type of Query; copy from query
72
- answer.aa = 1 # Is this an authoritative response: 0 = No, 1 = Yes
73
- answer.rd = query.rd # Is Recursion Desired, copied from query
74
- answer.ra = 0 # Does name server support recursion: 0 = No, 1 = Yes
75
- answer.rcode = 0 # Response code: 0 = No errors
81
+ # Setup response
82
+ response = Resolv::DNS::Message::new(query.id)
83
+ response.qr = 1 # 0 = Query, 1 = Response
84
+ response.opcode = query.opcode # Type of Query; copy from query
85
+ response.aa = 1 # Is this an authoritative response: 0 = No, 1 = Yes
86
+ response.rd = query.rd # Is Recursion Desired, copied from query
87
+ response.ra = 0 # Does name server support recursion: 0 = No, 1 = Yes
88
+ response.rcode = 0 # Response code: 0 = No errors
76
89
 
77
- Fiber.new do
78
- transaction = nil
79
-
80
- begin
81
- query.question.each do |question, resource_class|
82
- @logger.debug {"[#{query.id}] Processing question #{question} #{resource_class}..."}
83
-
84
- transaction = Transaction.new(self, query, question, resource_class, answer, options)
85
-
86
- transaction.process
87
- end
88
- rescue
89
- @logger.error {"[#{query.id}] Exception thrown while processing #{transaction}!"}
90
- RubyDNS.log_exception(@logger, $!)
91
-
92
- answer.rcode = Resolv::DNS::RCode::ServFail
90
+ transaction = nil
91
+
92
+ begin
93
+ query.question.each do |question, resource_class|
94
+ @logger.debug {"<#{query.id}> Processing question #{question} #{resource_class}..."}
95
+
96
+ transaction = Transaction.new(self, query, question, resource_class, response, options)
97
+
98
+ transaction.process
93
99
  end
94
-
95
- yield answer
96
-
97
- end_time = Time.now
98
- @logger.debug {"[#{query.id}] Time to process request: #{end_time - start_time}s"}
99
- end.resume
100
+ rescue Celluloid::ResumableError
101
+ raise
102
+ rescue StandardError => error
103
+ @logger.error "<#{query.id}> Exception thrown while processing #{transaction}!"
104
+ RubyDNS.log_exception(@logger, error)
105
+
106
+ response.rcode = Resolv::DNS::RCode::ServFail
107
+ end
108
+
109
+ end_time = Time.now
110
+ @logger.debug {"<#{query.id}> Time to process request: #{end_time - start_time}s"}
111
+
112
+ return response
100
113
  end
101
114
 
102
115
  #
@@ -113,41 +126,40 @@ module RubyDNS
113
126
  # Process::Sys.setuid(server_uid)
114
127
  # INTERFACES = [socket]
115
128
  #
116
- def run(options = {})
129
+ def run
117
130
  @logger.info "Starting RubyDNS server (v#{RubyDNS::VERSION})..."
118
-
119
- interfaces = options[:listen] || DEFAULT_INTERFACES
120
-
131
+
121
132
  fire(:setup)
122
-
133
+
123
134
  # Setup server sockets
124
- interfaces.each do |spec|
135
+ @interfaces.each do |spec|
125
136
  if spec.is_a?(BasicSocket)
126
137
  spec.do_not_reverse_lookup
127
- optval = spec.getsockopt(Socket::SOL_SOCKET, Socket::SO_TYPE)
128
- protocol = optval.unpack("i")[0]
138
+ protocol = spec.getsockopt(Socket::SOL_SOCKET, Socket::SO_TYPE).unpack("i")[0]
129
139
  ip = spec.local_address.ip_address
130
140
  port = spec.local_address.ip_port
141
+
131
142
  case protocol
132
143
  when Socket::SOCK_DGRAM
133
- @logger.info "Attaching to pre-existing UDP socket #{ip}:#{port}"
134
- EventMachine.attach(spec, UDPHandler, self)
144
+ @logger.info "<> Attaching to pre-existing UDP socket #{ip}:#{port}"
145
+ link UDPSocketHandler.new(self, UDPSocketWrapper.new(spec))
135
146
  when Socket::SOCK_STREAM
136
- @logger.info "Attaching to pre-existing TCP socket #{ip}:#{port}"
137
- EventMachine.attach(spec, TCPHandler, self)
147
+ @logger.info "<> Attaching to pre-existing TCP socket #{ip}:#{port}"
148
+ link TCPSocketHandler.new(self, TCPServerWrapper.new(spec))
138
149
  else
139
- @logger.error "Ignoring unknown socket protocol: #{protocol}"
150
+ raise ArgumentError.new("Unknown socket protocol: #{protocol}")
140
151
  end
152
+ elsif spec[0] == :udp
153
+ @logger.info "<> Listening on #{spec.join(':')}"
154
+ link UDPHandler.new(self, spec[1], spec[2])
155
+ elsif spec[0] == :tcp
156
+ @logger.info "<> Listening on #{spec.join(':')}"
157
+ link TCPHandler.new(self, spec[1], spec[2])
141
158
  else
142
- @logger.info "Listening on #{spec.join(':')}"
143
- if spec[0] == :udp
144
- EventMachine.open_datagram_socket(spec[1], spec[2], UDPHandler, self)
145
- elsif spec[0] == :tcp
146
- EventMachine.start_server(spec[1], spec[2], TCPHandler, self)
147
- end
159
+ raise ArgumentError.new("Invalid connection specification: #{spec.inspect}")
148
160
  end
149
161
  end
150
-
162
+
151
163
  fire(:start)
152
164
  end
153
165
  end
@@ -175,9 +187,9 @@ module RubyDNS
175
187
  end
176
188
 
177
189
  # Invoke the rule, if it matches the incoming request, it is evaluated and returns `true`, otherwise returns `false`.
178
- def call(server, name, resource_class, *args)
190
+ def call(server, name, resource_class, transaction)
179
191
  unless match(name, resource_class)
180
- server.logger.debug "Resource class #{resource_class} failed to match #{@pattern[1].inspect}!"
192
+ server.logger.debug "<#{transaction.query.id}> Resource class #{resource_class} failed to match #{@pattern[1].inspect}!"
181
193
 
182
194
  return false
183
195
  end
@@ -188,31 +200,31 @@ module RubyDNS
188
200
  match_data = @pattern[0].match(name)
189
201
 
190
202
  if match_data
191
- server.logger.debug "Regexp pattern matched with #{match_data.inspect}."
203
+ server.logger.debug "<#{transaction.query.id}> Regexp pattern matched with #{match_data.inspect}."
192
204
 
193
- @callback[*args, match_data]
205
+ @callback[transaction, match_data]
194
206
 
195
207
  return true
196
208
  end
197
209
  when String
198
210
  if @pattern[0] == name
199
- server.logger.debug "String pattern matched."
211
+ server.logger.debug "<#{transaction.query.id}> String pattern matched."
200
212
 
201
- @callback[*args]
213
+ @callback[transaction]
202
214
 
203
215
  return true
204
216
  end
205
217
  else
206
218
  if (@pattern[0].call(name, resource_class) rescue false)
207
- server.logger.debug "Callable pattern matched."
219
+ server.logger.debug "<#{transaction.query.id}> Callable pattern matched."
208
220
 
209
- @callback[*args]
221
+ @callback[transaction]
210
222
 
211
223
  return true
212
224
  end
213
225
  end
214
226
 
215
- server.logger.debug "No pattern matched."
227
+ server.logger.debug "<#{transaction.query.id}> No pattern matched."
216
228
 
217
229
  # We failed to match the pattern.
218
230
  return false
@@ -223,6 +235,9 @@ module RubyDNS
223
235
  end
224
236
  end
225
237
 
238
+ # Don't wrap the block going into initialize.
239
+ execute_block_on_receiver :initialize
240
+
226
241
  # Instantiate a server with a block
227
242
  #
228
243
  # server = Server.new do
@@ -289,32 +304,23 @@ module RubyDNS
289
304
  end
290
305
 
291
306
  # Give a name and a record type, try to match a rule and use it for processing the given arguments.
292
- def process(name, resource_class, *args)
293
- @logger.debug {"Searching for #{name} #{resource_class.name}"}
307
+ def process(name, resource_class, transaction)
308
+ @logger.debug {"<#{transaction.query.id}> Searching for #{name} #{resource_class.name}"}
294
309
 
295
310
  @rules.each do |rule|
296
- @logger.debug {"Checking rule #{rule}..."}
311
+ @logger.debug {"<#{transaction.query.id}> Checking rule #{rule}..."}
297
312
 
298
313
  catch (:next) do
299
314
  # If the rule returns true, we assume that it was successful and no further rules need to be evaluated.
300
- return if rule.call(self, name, resource_class, *args)
315
+ return if rule.call(self, name, resource_class, transaction)
301
316
  end
302
317
  end
303
318
 
304
319
  if @otherwise
305
- @otherwise.call(*args)
320
+ @otherwise.call(transaction)
306
321
  else
307
- @logger.warn "Failed to handle #{name} #{resource_class.name}!"
322
+ @logger.warn "<#{transaction.query.id}> Failed to handle #{name} #{resource_class.name}!"
308
323
  end
309
324
  end
310
-
311
- # 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.
312
- def defer(&block)
313
- fiber = Fiber.current
314
-
315
- yield(fiber)
316
-
317
- Fiber.yield
318
- end
319
325
  end
320
326
  end