rubydns 1.0.3 → 2.0.0.pre.rc1

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 (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