rubydns 1.0.3 → 2.0.0.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +4 -0
  3. data/.travis.yml +9 -12
  4. data/Gemfile +4 -1
  5. data/README.md +49 -151
  6. data/Rakefile +2 -7
  7. data/examples/basic-dns.rb +24 -0
  8. data/examples/cname.rb +25 -0
  9. data/examples/flakey-dns.rb +2 -2
  10. data/examples/simple.rb +25 -0
  11. data/examples/soa-dns.rb +82 -0
  12. data/examples/test-dns-1.rb +83 -0
  13. data/examples/test-dns-2.rb +83 -0
  14. data/examples/wikipedia-dns.rb +4 -18
  15. data/lib/rubydns.rb +9 -23
  16. data/lib/rubydns/{server.rb → rule_based_server.rb} +2 -160
  17. data/lib/rubydns/version.rb +1 -1
  18. data/rubydns.gemspec +3 -6
  19. data/spec/rubydns/daemon_spec.rb +26 -22
  20. data/spec/rubydns/injected_supervisor_spec.rb +10 -7
  21. data/spec/rubydns/passthrough_spec.rb +31 -24
  22. data/spec/spec_helper.rb +43 -0
  23. metadata +21 -100
  24. data/lib/rubydns/chunked.rb +0 -34
  25. data/lib/rubydns/extensions/resolv.rb +0 -85
  26. data/lib/rubydns/extensions/string.rb +0 -28
  27. data/lib/rubydns/handler.rb +0 -188
  28. data/lib/rubydns/logger.rb +0 -31
  29. data/lib/rubydns/message.rb +0 -76
  30. data/lib/rubydns/resolver.rb +0 -294
  31. data/lib/rubydns/system.rb +0 -146
  32. data/lib/rubydns/transaction.rb +0 -204
  33. data/lib/rubydns/transport.rb +0 -75
  34. data/spec/rubydns/celluloid_bug_spec.rb +0 -92
  35. data/spec/rubydns/ipv6_spec.rb +0 -70
  36. data/spec/rubydns/message_spec.rb +0 -56
  37. data/spec/rubydns/origin_spec.rb +0 -106
  38. data/spec/rubydns/resolver_performance_spec.rb +0 -110
  39. data/spec/rubydns/resolver_spec.rb +0 -152
  40. data/spec/rubydns/server/bind9/generate-local.rb +0 -25
  41. data/spec/rubydns/server/bind9/local.zone +0 -5014
  42. data/spec/rubydns/server/bind9/named.conf +0 -14
  43. data/spec/rubydns/server/bind9/named.run +0 -0
  44. data/spec/rubydns/server/million.rb +0 -85
  45. data/spec/rubydns/server/rubydns.stackprof +0 -0
  46. data/spec/rubydns/server_performance_spec.rb +0 -136
  47. data/spec/rubydns/slow_server_spec.rb +0 -89
  48. data/spec/rubydns/socket_spec.rb +0 -77
  49. data/spec/rubydns/system_spec.rb +0 -60
  50. data/spec/rubydns/transaction_spec.rb +0 -138
  51. data/spec/rubydns/truncation_spec.rb +0 -59
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright, 2009, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'rubygems'
24
+ require 'rubydns'
25
+ require 'rubydns/system'
26
+
27
+ # You can specify other DNS servers easily
28
+ # $R = Resolv::DNS.new(:nameserver => ["xx.xx.1.1", "xx.xx.2.2"])
29
+
30
+ R = RubyDNS::Resolver.new(RubyDNS::System.nameservers)
31
+ Name = Resolv::DNS::Name
32
+ IN = Resolv::DNS::Resource::IN
33
+ INTERFACES = [
34
+ [:udp, '0.0.0.0', 5300],
35
+ [:tcp, '0.0.0.0', 5300],
36
+ # [:udp, '::0', 5300],
37
+ # [:tcp, '::0', 5300],
38
+ ]
39
+
40
+ RubyDNS.run_server(listen: INTERFACES) do
41
+ # % dig +nocmd +noall +answer @localhost ANY dev.mydomain.org
42
+ # dev.mydomain.org. 16000 IN A 10.0.0.80
43
+ # dev.mydomain.org. 16000 IN MX 10 mail.mydomain.org.
44
+ match(/dev.mydomain.org/, IN::ANY) do |transaction|
45
+ transaction.append_question!
46
+
47
+ [IN::A, IN::CNAME, IN::MX].each do |resource_class|
48
+ logger.debug "Appending query for #{resource_class}..."
49
+ transaction.append!(transaction.name, resource_class)
50
+ end
51
+ end
52
+
53
+ # For this exact address record, return an IP address
54
+ match('dev.mydomain.org', IN::A) do |transaction|
55
+ transaction.respond!('10.0.0.80')
56
+ end
57
+
58
+ match('80.0.0.10.in-addr.arpa', IN::PTR) do |transaction|
59
+ transaction.respond!(Name.create('dev.mydomain.org.'))
60
+ end
61
+
62
+ match('dev.mydomain.org', IN::MX) do |transaction|
63
+ transaction.respond!(10, Name.create('mail.mydomain.org.'))
64
+ end
65
+
66
+ match(/^test([0-9]+).mydomain.org$/, IN::A) do |transaction, match_data|
67
+ offset = match_data[1].to_i
68
+
69
+ if offset > 0 && offset < 10
70
+ logger.info "Responding with address #{'10.0.0.' + (90 + offset).to_s}..."
71
+ transaction.respond!('10.0.0.' + (90 + offset).to_s)
72
+ else
73
+ logger.info "Address out of range: #{offset}!"
74
+ false
75
+ end
76
+ end
77
+
78
+ # Default DNS handler
79
+ otherwise do |transaction|
80
+ logger.info 'Passing DNS request upstream...'
81
+ transaction.passthrough!(R)
82
+ end
83
+ end
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright, 2009, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'rubydns'
24
+ require 'process-daemon'
25
+
26
+ # To run this command, use the standard daemon syntax as root
27
+ # ./daemon2.rb start
28
+
29
+ # You should be able to see that the server has dropped priviledges
30
+ # # ps aux | grep daemon2.rb
31
+ # daemon 16555 0.4 0.0 81392 2024 ?? S 3:35am 0:00.28 ruby ../test/daemon2.rb start
32
+
33
+ # Test using the following command
34
+ # dig @localhost test.mydomain.org
35
+ # dig +tcp @localhost test.mydomain.org
36
+
37
+ # You might need to change the user name "daemon". This can be a user name or a user id.
38
+ RUN_AS = 'daemon'
39
+
40
+ INTERFACES = [
41
+ [:udp, '0.0.0.0', 53],
42
+ [:tcp, '0.0.0.0', 53]
43
+ ]
44
+
45
+ # We need to be root in order to bind to privileged port
46
+ if RExec.current_user != 'root'
47
+ $stderr.puts 'Sorry, this command needs to be run as root!'
48
+ exit 1
49
+ end
50
+
51
+ # The Daemon itself
52
+ class Server < Process::Daemon
53
+ Name = Resolv::DNS::Name
54
+ IN = Resolv::DNS::Resource::IN
55
+
56
+ # Use upstream DNS for name resolution.
57
+ UPSTREAM = RubyDNS::Resolver.new([[:udp, '8.8.8.8', 53], [:tcp, '8.8.8.8', 53]])
58
+
59
+ def startup
60
+ # Don't buffer output (for debug purposes)
61
+ $stderr.sync = true
62
+
63
+ # Start the RubyDNS server
64
+ RubyDNS.run_server(listen: INTERFACES) do
65
+ on(:start) do
66
+ RExec.change_user(RUN_AS)
67
+ end
68
+
69
+ match('test.mydomain.org', IN::A) do |transaction|
70
+ transaction.respond!('10.0.0.80')
71
+ end
72
+
73
+ # Default DNS handler
74
+ otherwise do |transaction|
75
+ logger.info "Passthrough: #{transaction}"
76
+ transaction.passthrough!(UPSTREAM)
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ # RExec daemon runner
83
+ Server.daemonize
@@ -22,7 +22,6 @@
22
22
  # THE SOFTWARE.
23
23
 
24
24
  require 'rubydns'
25
- require 'rubydns/extensions/string'
26
25
 
27
26
  require 'process/daemon'
28
27
  require 'process/daemon/privileges'
@@ -33,6 +32,8 @@ require 'json'
33
32
 
34
33
  require 'digest/md5'
35
34
 
35
+ require 'http'
36
+
36
37
  # You might need to change the user name "daemon". This can be a user name
37
38
  # or a user id.
38
39
  RUN_AS = 'daemon'
@@ -42,19 +43,6 @@ if Process::Daemon::Privileges.current_user != 'root'
42
43
  exit 1
43
44
  end
44
45
 
45
- require 'http'
46
-
47
- # Celluloid::IO fetcher to retrieve URLs.
48
- class HttpFetcher
49
- include Celluloid::IO
50
-
51
- def get(url)
52
- # Note: For SSL support specify:
53
- # ssl_socket_class: Celluloid::IO::SSLSocket
54
- HTTP.get(url, socket_class: Celluloid::IO::TCPSocket)
55
- end
56
- end
57
-
58
46
  # Encapsulates the logic for fetching information from Wikipedia.
59
47
  module Wikipedia
60
48
  def self.summary_url(title)
@@ -81,9 +69,6 @@ class WikipediaDNS < Process::Daemon
81
69
 
82
70
  stats = { requested: 0 }
83
71
 
84
- # Use a Celluloid supervisor so the system recovers if the actor dies
85
- fetcher = HttpFetcher.supervise
86
-
87
72
  # Start the RubyDNS server
88
73
  RubyDNS.run_server do
89
74
  on(:start) do
@@ -103,7 +88,8 @@ class WikipediaDNS < Process::Daemon
103
88
  title = match_data[1]
104
89
  stats[:requested] += 1
105
90
 
106
- response = fetcher.actors.first.get(Wikipedia.summary_url(title))
91
+ url = Wikipedia.summary_url(title)
92
+ response = HTTP.get(url) # socket_class: ... is not yet supported.
107
93
 
108
94
  summary =
109
95
  Wikipedia.extract_summary(response).force_encoding('ASCII-8BIT')
@@ -18,33 +18,19 @@
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 'rubydns/version'
21
+ require 'async/dns'
22
22
 
23
- require_relative 'rubydns/message'
24
- require_relative 'rubydns/server'
25
- require_relative 'rubydns/resolver'
26
- require_relative 'rubydns/handler'
27
- require_relative 'rubydns/logger'
23
+ require_relative 'rubydns/version'
24
+ require_relative 'rubydns/rule_based_server'
28
25
 
29
26
  module RubyDNS
27
+ # Backwards compatibility:
28
+ Resolver = Async::DNS::Resolver
29
+
30
30
  # Run a server with the given rules.
31
- def self.run_server (options = {}, &block)
32
- server_class = options[:server_class] || RuleBasedServer
33
-
34
- supervisor = server_class.supervise(options, &block)
31
+ def self.run_server (server_class: RuleBasedServer, **options, &block)
32
+ server = server_class.new(**options, &block)
35
33
 
36
- supervisor.actors.first.run
37
- if options[:asynchronous]
38
- return supervisor
39
- else
40
- read, write = IO.pipe
41
-
42
- trap(:INT) {
43
- write.puts
44
- }
45
-
46
- IO.select([read])
47
- supervisor.terminate
48
- end
34
+ server.run
49
35
  end
50
36
  end
@@ -18,166 +18,11 @@
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 'celluloid/io'
22
-
23
- require_relative 'transaction'
24
- require_relative 'logger'
21
+ require 'async/dns/server'
25
22
 
26
23
  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
38
-
39
- class Server
40
- include Celluloid::IO
41
-
42
- finalizer :shutdown
43
-
44
- # The default server interfaces
45
- DEFAULT_INTERFACES = [[:udp, "0.0.0.0", 53], [:tcp, "0.0.0.0", 53]]
46
-
47
- # Instantiate a server with a block
48
- #
49
- # server = Server.new do
50
- # match(/server.mydomain.com/, IN::A) do |transaction|
51
- # transaction.respond!("1.2.3.4")
52
- # end
53
- # end
54
- #
55
- def initialize(options = {})
56
- @handlers = []
57
-
58
- @logger = options[:logger] || Celluloid.logger
59
- @interfaces = options[:listen] || DEFAULT_INTERFACES
60
-
61
- @origin = options[:origin] || '.'
62
- end
63
-
64
- # Records are relative to this origin:
65
- attr_accessor :origin
66
-
67
- attr_accessor :logger
68
-
69
- # Fire the named event as part of running the server.
70
- def fire(event_name)
71
- end
72
-
73
- def shutdown
74
- fire(:stop)
75
- end
76
-
77
- # Give a name and a record type, try to match a rule and use it for processing the given arguments.
78
- def process(name, resource_class, transaction)
79
- raise NotImplementedError.new
80
- end
81
-
82
- # Process an incoming DNS message. Returns a serialized message to be sent back to the client.
83
- def process_query(query, options = {}, &block)
84
- start_time = Time.now
85
-
86
- # Setup response
87
- response = Resolv::DNS::Message::new(query.id)
88
- response.qr = 1 # 0 = Query, 1 = Response
89
- response.opcode = query.opcode # Type of Query; copy from query
90
- response.aa = 1 # Is this an authoritative response: 0 = No, 1 = Yes
91
- response.rd = query.rd # Is Recursion Desired, copied from query
92
- response.ra = 0 # Does name server support recursion: 0 = No, 1 = Yes
93
- response.rcode = 0 # Response code: 0 = No errors
94
-
95
- transaction = nil
96
-
97
- begin
98
- query.question.each do |question, resource_class|
99
- begin
100
- question = question.without_origin(@origin)
101
-
102
- @logger.debug {"<#{query.id}> Processing question #{question} #{resource_class}..."}
103
-
104
- transaction = Transaction.new(self, query, question, resource_class, response, options)
105
-
106
- transaction.process
107
- rescue Resolv::DNS::OriginError
108
- # This is triggered if the question is not part of the specified @origin:
109
- @logger.debug {"<#{query.id}> Skipping question #{question} #{resource_class} because #{$!}"}
110
- end
111
- end
112
- rescue Celluloid::ResumableError
113
- raise
114
- rescue StandardError => error
115
- @logger.error "<#{query.id}> Exception thrown while processing #{transaction}!"
116
- RubyDNS.log_exception(@logger, error)
117
-
118
- response.rcode = Resolv::DNS::RCode::ServFail
119
- end
120
-
121
- end_time = Time.now
122
- @logger.debug {"<#{query.id}> Time to process request: #{end_time - start_time}s"}
123
-
124
- return response
125
- end
126
-
127
- #
128
- # By default the server runs on port 53, both TCP and UDP, which is usually a priviledged port and requires root access to bind. You can change this by specifying `options[:listen]` which should contain an array of `[protocol, interface address, port]` specifications.
129
- #
130
- # INTERFACES = [[:udp, "0.0.0.0", 5300]]
131
- # RubyDNS::run_server(:listen => INTERFACES) do
132
- # ...
133
- # end
134
- #
135
- # You can specify already connected sockets if need be:
136
- #
137
- # socket = UDPSocket.new; socket.bind("0.0.0.0", 53)
138
- # Process::Sys.setuid(server_uid)
139
- # INTERFACES = [socket]
140
- #
141
- def run
142
- @logger.info "Starting RubyDNS server (v#{RubyDNS::VERSION})..."
143
-
144
- fire(:setup)
145
-
146
- # Setup server sockets
147
- @interfaces.each do |spec|
148
- if spec.is_a?(BasicSocket)
149
- spec.do_not_reverse_lookup
150
- protocol = spec.getsockopt(Socket::SOL_SOCKET, Socket::SO_TYPE).unpack("i")[0]
151
- ip = spec.local_address.ip_address
152
- port = spec.local_address.ip_port
153
-
154
- case protocol
155
- when Socket::SOCK_DGRAM
156
- @logger.info "<> Attaching to pre-existing UDP socket #{ip}:#{port}"
157
- link UDPSocketHandler.new(self, UDPSocketWrapper.new(spec))
158
- when Socket::SOCK_STREAM
159
- @logger.info "<> Attaching to pre-existing TCP socket #{ip}:#{port}"
160
- link TCPSocketHandler.new(self, TCPServerWrapper.new(spec))
161
- else
162
- raise ArgumentError.new("Unknown socket protocol: #{protocol}")
163
- end
164
- elsif spec[0] == :udp
165
- @logger.info "<> Listening on #{spec.join(':')}"
166
- link UDPHandler.new(self, spec[1], spec[2])
167
- elsif spec[0] == :tcp
168
- @logger.info "<> Listening on #{spec.join(':')}"
169
- link TCPHandler.new(self, spec[1], spec[2])
170
- else
171
- raise ArgumentError.new("Invalid connection specification: #{spec.inspect}")
172
- end
173
- end
174
-
175
- fire(:start)
176
- end
177
- end
178
-
179
24
  # Provides the core of the RubyDNS domain-specific language (DSL). It contains a list of rules which are used to match against incoming DNS questions. These rules are used to generate responses which are either DNS resource records or failures.
180
- class RuleBasedServer < Server
25
+ class RuleBasedServer < Async::DNS::Server
181
26
  # Represents a single rule in the server.
182
27
  class Rule
183
28
  def initialize(pattern, callback)
@@ -247,9 +92,6 @@ module RubyDNS
247
92
  end
248
93
  end
249
94
 
250
- # Don't wrap the block going into initialize.
251
- execute_block_on_receiver :initialize
252
-
253
95
  # Instantiate a server with a block
254
96
  #
255
97
  # server = Server.new do
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module RubyDNS
22
- VERSION = '1.0.3'
22
+ VERSION = '2.0.0-rc1'
23
23
  end