rubydns 0.8.5 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 579892157e10443cf4e2959ff42da5f6d312a59b
4
- data.tar.gz: eadc76b6e5c1bbc199aaecc6beae5ea6836ece29
3
+ metadata.gz: 1835f3164db6b9d4a9bf06f1b484be87abfee77b
4
+ data.tar.gz: 53f3a898b8cf9c5b4ecda1e09e061caeb48ee4c0
5
5
  SHA512:
6
- metadata.gz: 2dab4ccffd9bfc4d2ee23be3cee574f1925e152999e754ded886d80b9b45c427815ae7e02bafed735a454706ea76436f91f44f604fe2162198e2ea7a34e5dfbe
7
- data.tar.gz: d4d47c5ed9316a1177d42a8cc403d690031c343e10bf0ab0a5b387d02ed8bcfaa91e5bb78c620134d65a18d7bf10233bff9e4269bd4be9dac53f0434b1ef3210
6
+ metadata.gz: 7436649377952b0e4bcbe7e8edb0108b4f6cde35341c3f5f269baa0874e2c5c6eeeb13000ebde28b8035d40c3d6b62819bf3aa591c86b043528b2249b6bdc58f
7
+ data.tar.gz: db91f39c37441efb5092502e49a9ec3851f356dd0d2f89710ab6eb2464760cfa5aa73d47617b11541b69a8c0bd393377632cef534189099623a02fdd4957f441
data/.travis.yml CHANGED
@@ -6,3 +6,7 @@ rvm:
6
6
  - 1.9
7
7
  - 2.0
8
8
  - 2.1
9
+ - rbx-2
10
+ matrix:
11
+ allow_failures:
12
+ - rvm: rbx-2
data/Gemfile CHANGED
@@ -2,7 +2,3 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in trenni.gemspec
4
4
  gemspec
5
-
6
- group :test do
7
- gem "minitest"
8
- end
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` however `bind9` supports multiple CPUs and thus can scale more easily than RubyDNS using MRI. Some basic benchmarks resolving 1000 names concurrently, repeated 5 times, using `RubyDNS::Resolver` gives the following:
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 1.040000 0.320000 1.360000 ( 6.469213)
93
- Bind9 1.940000 0.120000 2.060000 ( 2.062983)
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.280679)
104
- Resolv::DNS 0.030000 0.010000 0.040000 ( 2.801773)
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 "rake/testtask"
2
+ require "rspec/core/rake_task"
3
3
 
4
- Rake::TestTask.new do |t|
5
- t.libs << 'test'
6
- end
4
+ RSpec::Core::RakeTask.new(:spec)
7
5
 
8
- desc "Run tests"
9
- task :default => :test
6
+ task :default => :spec
7
+
8
+ require 'celluloid'
9
+ Celluloid.logger.level = Logger::ERROR
File without changes
@@ -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 'message'
22
- require_relative 'binary_string'
21
+ require_relative 'transport'
23
22
 
24
23
  module RubyDNS
25
- module Peername
26
- # Available for both UDP and TCP connections, returns `[address, port]`.
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
- # Process a packet of data with the given server. If an exception is thrown, a failure message will be sent back.
41
- def self.process(server, data, options = {}, &block)
42
- server.logger.debug {"Receiving incoming query (#{data.bytesize} bytes)..."}
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, &block)
49
- rescue => error
50
- server.logger.error "Error processing request!"
51
- server.logger.error "#{error.class}: #{error.message}"
52
-
53
- error.backtrace.each { |at| server.logger.error at }
54
-
55
- # Encoding may fail, so we need to handle this particular case:
56
- server_failure = Resolv::DNS::Message::new(query ? query.id : 0)
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 peername
71
- Socket.unpack_sockaddr_in(self.get_peername)
80
+ def run
81
+ loop { handle_connection }
72
82
  end
73
83
 
74
- def receive_data(data)
75
- options = {:connection => self}
84
+ def respond(input_data, remote_host, remote_port)
85
+ options = {peer: remote_host}
76
86
 
77
- UDPHandler.process(@server, data, options) do |answer|
78
- data = answer.encode
79
-
80
- @server.logger.debug {"Writing response to client (#{data.bytesize} bytes) via UDP..."}
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
- if data.bytesize > UDP_TRUNCATION_SIZE
83
- @server.logger.warn {"Response via UDP was larger than #{UDP_TRUNCATION_SIZE}!"}
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
- # We explicitly use the ip and port given, because we found that send_data was unreliable in a callback.
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
- module TCPHandler
100
- include Peername
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
- def initialize(server)
103
- @server = server
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
- @buffer = BinaryStringIO.new
144
+ @socket = socket
106
145
 
107
- @length = nil
108
- @processed = 0
146
+ async.run
109
147
  end
110
148
 
111
- # Receive the data via a TCP connection, process messages when we receive the indicated amount of data.
112
- def receive_data(data)
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
- # Check that all data received was processed.
148
- def unbind
149
- if @processed != @buffer.size
150
- @server.logger.debug "Unprocessed data remaining (#{@buffer.size - @processed} bytes unprocessed) on incoming TCP connection."
151
- end
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
- end
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
@@ -18,7 +18,6 @@
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 'eventmachine'
22
21
  require 'stringio'
23
22
  require 'resolv'
24
23