rubydns 0.8.5 → 0.9.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 -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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1835f3164db6b9d4a9bf06f1b484be87abfee77b
|
4
|
+
data.tar.gz: 53f3a898b8cf9c5b4ecda1e09e061caeb48ee4c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7436649377952b0e4bcbe7e8edb0108b4f6cde35341c3f5f269baa0874e2c5c6eeeb13000ebde28b8035d40c3d6b62819bf3aa591c86b043528b2249b6bdc58f
|
7
|
+
data.tar.gz: db91f39c37441efb5092502e49a9ec3851f356dd0d2f89710ab6eb2464760cfa5aa73d47617b11541b69a8c0bd393377632cef534189099623a02fdd4957f441
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -81,16 +81,15 @@ This is the best way to integrate with other projects.
|
|
81
81
|
|
82
82
|
## Performance
|
83
83
|
|
84
|
-
We welcome additional benchmarks and feedback regarding RubyDNS performance.
|
84
|
+
We welcome additional benchmarks and feedback regarding RubyDNS performance. To check the current performance results, consult the [travis build job output](https://travis-ci.org/ioquatix/rubydns).
|
85
85
|
|
86
86
|
### Server
|
87
87
|
|
88
|
-
The performance is on the same magnitude as `bind9
|
88
|
+
The performance is on the same magnitude as `bind9`. Some basic benchmarks resolving 1000 names concurrently, repeated 5 times, using `RubyDNS::Resolver` gives the following:
|
89
89
|
|
90
|
-
Testing server performance...
|
91
90
|
user system total real
|
92
|
-
RubyDNS::Server
|
93
|
-
Bind9
|
91
|
+
RubyDNS::Server 4.280000 0.450000 4.730000 ( 4.854862)
|
92
|
+
Bind9 4.970000 0.520000 5.490000 ( 5.541213)
|
94
93
|
|
95
94
|
These benchmarks are included in the unit tests. To test bind9 performance, it must be installed and `which named` must return the executable.
|
96
95
|
|
@@ -98,15 +97,24 @@ These benchmarks are included in the unit tests. To test bind9 performance, it m
|
|
98
97
|
|
99
98
|
The `RubyDNS::Resolver` is highly concurrent and can resolve individual names as fast as the built in `Resolv::DNS` resolver. Because the resolver is asynchronous, when dealing with multiple names, it can work more efficiently:
|
100
99
|
|
101
|
-
Comparing resolvers...
|
102
100
|
user system total real
|
103
|
-
RubyDNS::Resolver 0.020000 0.010000 0.030000 ( 0.
|
104
|
-
Resolv::DNS 0.
|
101
|
+
RubyDNS::Resolver 0.020000 0.010000 0.030000 ( 0.030507)
|
102
|
+
Resolv::DNS 0.070000 0.010000 0.080000 ( 1.465975)
|
105
103
|
|
106
104
|
These benchmarks are included in the unit tests.
|
107
105
|
|
108
106
|
## Compatibility
|
109
107
|
|
108
|
+
### Migrating from RubyDNS 0.8.x 0.9.x
|
109
|
+
|
110
|
+
RubyDNS 0.9.0 is based on a branch which replaced EventMachine with Celluloid. This reduces the complexity in writing concurrent systems hugely, but it is also a largely untested code path. RubyDNS 0.8.x using EventMachine has been tested over 4 years now by many projects.
|
111
|
+
|
112
|
+
The reason for the change is simply that EventMachine is now a dead project and no longer being maintained/supported. The ramifications of this are: no IPv6 support, crashes/segfaults in certain workloads with no workable solution going forward, and lack of integration with external libraries.
|
113
|
+
|
114
|
+
The difference for authors integrating RubyDNS in a daemon should be minimal. For users integrating RubyDNS into an existing system, you need to be aware of the contracts imposed by Celluloid, namely, whether it affects other parts of your system. Some areas of Celluloid are well developed, others are still needing attention (e.g. how it handles forking child processes). We expect the 0.8 branch should remain stable for a long time, but 0.9 branch will eventually become the 1.0 release.
|
115
|
+
|
116
|
+
The benefits of using Celluloid include: fault tolerance, high performance, scalability across multiple hardware threads (when using Rubinius or JRuby), simpler integration with 3rd party data (e.g. `defer` has now been removed since it isn't necessary with celluloid).
|
117
|
+
|
110
118
|
### Migrating from RubyDNS 0.7.x to 0.8.x
|
111
119
|
|
112
120
|
The primary change is the removal of the dependency on `RExec` which was used for daemons and the addition of the testing dependency `process-daemon`. In order to create and run your own daemon, you may use `process-daemon` or another tool of your choice.
|
data/Rakefile
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
-
require "
|
2
|
+
require "rspec/core/rake_task"
|
3
3
|
|
4
|
-
|
5
|
-
t.libs << 'test'
|
6
|
-
end
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
5
|
|
8
|
-
|
9
|
-
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
require 'celluloid'
|
9
|
+
Celluloid.logger.level = Logger::ERROR
|
File without changes
|
data/lib/rubydns/handler.rb
CHANGED
@@ -18,138 +18,169 @@
|
|
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 'transport'
|
23
22
|
|
24
23
|
module RubyDNS
|
25
|
-
|
26
|
-
|
27
|
-
def peername
|
28
|
-
Socket.unpack_sockaddr_in(self.get_peername).reverse
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Handling incoming UDP requests, which are single data packets, and pass them on to the given server.
|
33
|
-
module UDPHandler
|
34
|
-
include Peername
|
24
|
+
class GenericHandler
|
25
|
+
include Celluloid::IO
|
35
26
|
|
36
27
|
def initialize(server)
|
37
28
|
@server = server
|
29
|
+
@logger = @server.logger || Celluloid.logger
|
38
30
|
end
|
39
31
|
|
40
|
-
|
41
|
-
|
42
|
-
|
32
|
+
def error_response(query = nil, code = Resolv::DNS::RCode::ServFail)
|
33
|
+
# Encoding may fail, so we need to handle this particular case:
|
34
|
+
server_failure = Resolv::DNS::Message::new(query ? query.id : 0)
|
35
|
+
|
36
|
+
server_failure.qr = 1
|
37
|
+
server_failure.opcode = query ? query.opcode : 0
|
38
|
+
server_failure.aa = 1
|
39
|
+
server_failure.rd = 0
|
40
|
+
server_failure.ra = 0
|
41
|
+
|
42
|
+
server_failure.rcode = code
|
43
|
+
|
44
|
+
# We can't do anything at this point...
|
45
|
+
return server_failure
|
46
|
+
end
|
47
|
+
|
48
|
+
def process_query(data, options)
|
49
|
+
@logger.debug "<> Receiving incoming query (#{data.bytesize} bytes) to #{self.class.name}..."
|
43
50
|
query = nil
|
44
51
|
|
45
52
|
begin
|
46
53
|
query = RubyDNS::decode_message(data)
|
47
|
-
|
48
|
-
return server.process_query(query, options
|
49
|
-
rescue
|
50
|
-
server.
|
51
|
-
|
52
|
-
|
53
|
-
error
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
server_failure.qr = 1
|
58
|
-
server_failure.opcode = query ? query.opcode : 0
|
59
|
-
server_failure.aa = 1
|
60
|
-
server_failure.rd = 0
|
61
|
-
server_failure.ra = 0
|
62
|
-
|
63
|
-
server_failure.rcode = Resolv::DNS::RCode::ServFail
|
64
|
-
|
65
|
-
# We can't do anything at this point...
|
66
|
-
yield server_failure
|
54
|
+
|
55
|
+
return @server.process_query(query, options)
|
56
|
+
rescue Celluloid::ResumableError
|
57
|
+
# Celluloid terminates tasks, we may be stuck in a task when the server is terminated. We don't want to reply to the client in this case, because the server is being terminated. It might be an option to return a server failure
|
58
|
+
raise
|
59
|
+
rescue StandardError => error
|
60
|
+
@logger.error "<> Error processing request: #{error.inspect}!"
|
61
|
+
RubyDNS::log_exception(@logger, error)
|
62
|
+
|
63
|
+
return error_response(query)
|
67
64
|
end
|
68
65
|
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Handling incoming UDP requests, which are single data packets, and pass them on to the given server.
|
69
|
+
class UDPSocketHandler < GenericHandler
|
70
|
+
include Celluloid::IO
|
71
|
+
|
72
|
+
def initialize(server, socket)
|
73
|
+
super(server)
|
74
|
+
|
75
|
+
@socket = socket
|
76
|
+
|
77
|
+
async.run
|
78
|
+
end
|
69
79
|
|
70
|
-
def
|
71
|
-
|
80
|
+
def run
|
81
|
+
loop { handle_connection }
|
72
82
|
end
|
73
83
|
|
74
|
-
def
|
75
|
-
options = {:
|
84
|
+
def respond(input_data, remote_host, remote_port)
|
85
|
+
options = {peer: remote_host}
|
76
86
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
87
|
+
response = process_query(input_data, options)
|
88
|
+
|
89
|
+
output_data = response.encode
|
90
|
+
|
91
|
+
@logger.debug "<#{response.id}> Writing #{output_data.bytesize} bytes response to client via UDP..."
|
92
|
+
|
93
|
+
if output_data.bytesize > UDP_TRUNCATION_SIZE
|
94
|
+
@logger.warn "<#{response.id}>Response via UDP was larger than #{UDP_TRUNCATION_SIZE}!"
|
81
95
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
# Reencode data with truncation flag marked as true:
|
86
|
-
truncation_error = Resolv::DNS::Message.new(answer.id)
|
87
|
-
truncation_error.tc = 1
|
88
|
-
|
89
|
-
data = truncation_error.encode
|
90
|
-
end
|
96
|
+
# Reencode data with truncation flag marked as true:
|
97
|
+
truncation_error = Resolv::DNS::Message.new(response.id)
|
98
|
+
truncation_error.tc = 1
|
91
99
|
|
92
|
-
|
93
|
-
# self.send_datagram(data, peer_ip, peer_port)
|
94
|
-
self.send_data(data)
|
100
|
+
output_data = truncation_error.encode
|
95
101
|
end
|
102
|
+
|
103
|
+
@socket.send(output_data, 0, remote_host, remote_port)
|
104
|
+
rescue IOError => error
|
105
|
+
@logger.warn "<> UDP response failed: #{error.inspect}!"
|
106
|
+
rescue EOFError => error
|
107
|
+
@logger.warn "<> UDP session ended prematurely: #{error.inspect}!"
|
108
|
+
rescue DecodeError
|
109
|
+
@logger.warn "<> Could not decode incoming UDP data!"
|
110
|
+
end
|
111
|
+
|
112
|
+
def handle_connection
|
113
|
+
# @logger.debug "Waiting for incoming UDP packet #{@socket.inspect}..."
|
114
|
+
|
115
|
+
input_data, (_, remote_port, remote_host) = @socket.recvfrom(UDP_TRUNCATION_SIZE)
|
116
|
+
|
117
|
+
async.respond(input_data, remote_host, remote_port)
|
118
|
+
rescue IOError => error
|
119
|
+
@logger.warn "<> UDP connection failed: #{error.inspect}!"
|
120
|
+
rescue EOFError => error
|
121
|
+
@logger.warn "<> UDP session ended prematurely!"
|
96
122
|
end
|
97
123
|
end
|
98
124
|
|
99
|
-
|
100
|
-
|
125
|
+
class UDPHandler < UDPSocketHandler
|
126
|
+
def initialize(server, host, port)
|
127
|
+
socket = UDPSocket.new
|
128
|
+
socket.bind(host, port)
|
129
|
+
|
130
|
+
super(server, socket)
|
131
|
+
end
|
101
132
|
|
102
|
-
|
103
|
-
|
133
|
+
finalizer :finalize
|
134
|
+
|
135
|
+
def finalize
|
136
|
+
@socket.close if @socket
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class TCPSocketHandler < GenericHandler
|
141
|
+
def initialize(server, socket)
|
142
|
+
super(server)
|
104
143
|
|
105
|
-
@
|
144
|
+
@socket = socket
|
106
145
|
|
107
|
-
|
108
|
-
@processed = 0
|
146
|
+
async.run
|
109
147
|
end
|
110
148
|
|
111
|
-
|
112
|
-
|
113
|
-
# We buffer data until we've received the entire packet:
|
114
|
-
@buffer.write(data)
|
115
|
-
|
116
|
-
# Message includes a 16-bit length field.. we need to see if we have received it yet:
|
117
|
-
if @length == nil
|
118
|
-
# Not enough data received yet...
|
119
|
-
if (@buffer.size - @processed) < 2
|
120
|
-
return
|
121
|
-
end
|
122
|
-
|
123
|
-
# Grab the length field:
|
124
|
-
@length = @buffer.string.byteslice(@processed, 2).unpack('n')[0]
|
125
|
-
@processed += 2
|
126
|
-
end
|
127
|
-
|
128
|
-
if (@buffer.size - @processed) >= @length
|
129
|
-
data = @buffer.string.byteslice(@processed, @length)
|
130
|
-
|
131
|
-
options = {:connection => self}
|
132
|
-
|
133
|
-
UDPHandler.process(@server, data, options) do |answer|
|
134
|
-
data = answer.encode
|
135
|
-
|
136
|
-
@server.logger.debug "Writing response to client (#{data.bytesize} bytes) via TCP..."
|
137
|
-
|
138
|
-
self.send_data([data.bytesize].pack('n'))
|
139
|
-
self.send_data(data)
|
140
|
-
end
|
141
|
-
|
142
|
-
@processed += @length
|
143
|
-
@length = nil
|
144
|
-
end
|
149
|
+
def run
|
150
|
+
loop { async.handle_connection @socket.accept }
|
145
151
|
end
|
146
152
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
153
|
+
def handle_connection(socket)
|
154
|
+
_, remote_port, remote_host = socket.peeraddr
|
155
|
+
options = {peer: remote_host}
|
156
|
+
|
157
|
+
input_data = StreamTransport.read_chunk(socket)
|
158
|
+
|
159
|
+
response = process_query(input_data, options)
|
160
|
+
|
161
|
+
length = StreamTransport.write_message(socket, response)
|
162
|
+
|
163
|
+
@logger.debug "<#{response.id}> Wrote #{length} bytes via TCP..."
|
164
|
+
rescue EOFError => error
|
165
|
+
@logger.warn "<> TCP session ended prematurely!"
|
166
|
+
rescue DecodeError
|
167
|
+
@logger.warn "<> Could not decode incoming TCP data!"
|
168
|
+
ensure
|
169
|
+
socket.close
|
152
170
|
end
|
153
171
|
end
|
154
172
|
|
155
|
-
|
173
|
+
class TCPHandler < TCPSocketHandler
|
174
|
+
def initialize(server, host, port)
|
175
|
+
socket = TCPServer.new(host, port)
|
176
|
+
|
177
|
+
super(server, socket)
|
178
|
+
end
|
179
|
+
|
180
|
+
finalizer :finalize
|
181
|
+
|
182
|
+
def finalize
|
183
|
+
@socket.close if @socket
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|