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.
- checksums.yaml +4 -4
- data/.travis.yml +4 -0
- data/Gemfile +0 -4
- data/README.md +16 -8
- data/Rakefile +6 -6
- data/{test/examples → examples}/dropping-dns.rb +0 -0
- data/lib/rubydns/handler.rb +133 -102
- data/lib/rubydns/message.rb +0 -1
- data/lib/rubydns/resolver.rb +135 -187
- data/lib/rubydns/server.rb +92 -86
- data/lib/rubydns/transaction.rb +24 -48
- data/lib/rubydns/{binary_string.rb → transport.rb} +38 -0
- data/lib/rubydns/version.rb +1 -1
- data/lib/rubydns.rb +15 -8
- data/rubydns.gemspec +5 -2
- data/{test/test_daemon.rb → spec/rubydns/daemon_spec.rb} +36 -48
- data/{test → spec/rubydns}/hosts.txt +0 -0
- data/{test/test_rules.rb → spec/rubydns/message_spec.rb} +26 -44
- data/spec/rubydns/passthrough_spec.rb +78 -0
- data/spec/rubydns/resolver_performance_spec.rb +110 -0
- data/spec/rubydns/resolver_spec.rb +144 -0
- data/spec/rubydns/rules_spec.rb +74 -0
- data/{test/performance → spec/rubydns/server}/benchmark.rb +0 -0
- data/{test/performance → spec/rubydns/server}/bind9/generate-local.rb +0 -0
- data/{test/performance → spec/rubydns/server}/bind9/local.zone +0 -0
- data/{test/performance → spec/rubydns/server}/bind9/named.conf +0 -0
- data/spec/rubydns/server/bind9/named.run +0 -0
- data/{test/performance → spec/rubydns/server}/million.rb +1 -3
- data/spec/rubydns/server/rubydns.stackprof +0 -0
- data/spec/rubydns/server_performance_spec.rb +136 -0
- data/spec/rubydns/slow_server_spec.rb +89 -0
- data/spec/rubydns/socket_spec.rb +77 -0
- data/{test/test_system.rb → spec/rubydns/system_spec.rb} +28 -22
- data/spec/rubydns/transaction_spec.rb +64 -0
- data/{test/test_truncation.rb → spec/rubydns/truncation_spec.rb} +22 -48
- metadata +91 -54
- data/test/examples/fortune-dns.rb +0 -107
- data/test/examples/geoip-dns.rb +0 -76
- data/test/examples/soa-dns.rb +0 -82
- data/test/examples/test-dns-1.rb +0 -77
- data/test/examples/test-dns-2.rb +0 -83
- data/test/examples/wikipedia-dns.rb +0 -112
- data/test/test_message.rb +0 -65
- data/test/test_passthrough.rb +0 -120
- data/test/test_resolver.rb +0 -106
- data/test/test_resolver_performance.rb +0 -123
- data/test/test_server_performance.rb +0 -134
- data/test/test_slow_server.rb +0 -125
data/lib/rubydns/resolver.rb
CHANGED
@@ -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 '
|
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
|
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
|
-
|
62
|
+
dispatch_request(message)
|
56
63
|
end
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
82
|
-
request
|
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
|
-
|
89
|
-
|
95
|
+
begin
|
96
|
+
response = nil
|
97
|
+
|
98
|
+
timeout(request_timeout) do
|
99
|
+
response = try_server(request, server)
|
100
|
+
end
|
90
101
|
|
91
|
-
|
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
|
-
|
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
|
119
|
-
|
120
|
-
|
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
|
-
|
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
|
-
|
249
|
-
|
250
|
-
|
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
|
data/lib/rubydns/server.rb
CHANGED
@@ -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 '
|
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
|
-
@
|
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
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
query.
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
96
|
-
|
97
|
-
|
98
|
-
@logger
|
99
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
147
|
+
@logger.info "<> Attaching to pre-existing TCP socket #{ip}:#{port}"
|
148
|
+
link TCPSocketHandler.new(self, TCPServerWrapper.new(spec))
|
138
149
|
else
|
139
|
-
|
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
|
-
|
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,
|
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[
|
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[
|
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[
|
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,
|
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,
|
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(
|
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
|