rubydns 0.4.1 → 0.5.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.
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ # A sample Gemfile
2
+ source "https://rubygems.org"
3
+
4
+ gem "rexec", "~> 1.5.1"
5
+ gem "rake"
6
+ gem "eventmachine"
7
+
data/README.md CHANGED
@@ -4,6 +4,7 @@ RubyDNS
4
4
  * Author: Samuel G. D. Williams (<http://www.oriontransfer.co.nz>)
5
5
  * Copyright (C) 2009, 2011 Samuel G. D. Williams.
6
6
  * Released under the MIT license.
7
+ * [![Build Status](https://secure.travis-ci.org/ioquatix/rubydns.png)](http://travis-ci.org/ioquatix/rubydns)
7
8
 
8
9
  RubyDNS is a simple programmatic DSL (domain specific language) for configuring and running a DNS server. RubyDNS provides a daemon that runs a DNS server which can process DNS requests depending on specific policy. Rule selection is based on pattern matching, and results can be hard-coded, computed, fetched from a remote DNS server, fetched from a local cache, etc.
9
10
 
data/lib/rubydns.rb CHANGED
@@ -23,12 +23,17 @@ require 'rubydns/version'
23
23
  if RUBY_VERSION < "1.9"
24
24
  require 'rubydns/extensions/resolv-1.8'
25
25
  require 'rubydns/extensions/string-1.8'
26
+ elsif RUBY_VERSION < "1.9.3"
27
+ require 'rubydns/extensions/resolv-1.9'
28
+ require 'rubydns/extensions/string-1.9.2'
26
29
  else
27
30
  require 'rubydns/extensions/resolv-1.9'
28
- require 'rubydns/extensions/string-1.9'
31
+ require 'rubydns/extensions/string-1.9.3'
29
32
  end
30
33
 
31
34
  require 'rubydns/server'
35
+ require 'rubydns/resolver'
36
+ require 'rubydns/handler'
32
37
 
33
38
  require 'logger'
34
39
 
@@ -0,0 +1,34 @@
1
+ # Copyright (c) 2009, 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module RubyDNS
22
+ # Produces an array of arrays of binary data with each sub-array a maximum of chunk_size bytes.
23
+ def self.chunked(string, chunk_size = 255)
24
+ chunks = []
25
+
26
+ offset = 0
27
+ while offset < string.bytesize
28
+ chunks << string.byteslice(offset, chunk_size)
29
+ offset += chunk_size
30
+ end
31
+
32
+ return chunks
33
+ end
34
+ end
@@ -18,6 +18,8 @@
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 'stringio'
22
+
21
23
  class String
22
24
  def bytesize
23
25
  size
@@ -26,16 +28,8 @@ class String
26
28
  def byteslice(*args)
27
29
  self[*args]
28
30
  end
29
-
30
- def chunked(chunk_size = 255)
31
- chunks = []
32
-
33
- offset = 0
34
- while offset < bytesize
35
- chunks << byteslice(offset, chunk_size)
36
- offset += chunk_size
37
- end
38
-
39
- return chunks
40
- end
31
+ end
32
+
33
+ module RubyDNS
34
+ BinaryStringIO = StringIO
41
35
  end
@@ -0,0 +1,37 @@
1
+ # Copyright (c) 2009, 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'stringio'
22
+
23
+ class String
24
+ def byteslice(*args)
25
+ self.dup.force_encoding("binary").slice(*args)
26
+ end
27
+ end
28
+
29
+ module RubyDNS
30
+ class BinaryStringIO < StringIO
31
+ def initialize
32
+ super
33
+
34
+ set_encoding("binary")
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,33 @@
1
+ # Copyright (c) 2009, 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'stringio'
22
+
23
+ module RubyDNS
24
+ class BinaryStringIO < StringIO
25
+ def initialize
26
+ super
27
+
28
+ set_encoding("BINARY")
29
+ end
30
+ end
31
+
32
+
33
+ end
@@ -18,16 +18,10 @@
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 'rubydns/chunked'
22
+
21
23
  class String
22
24
  def chunked(chunk_size = 255)
23
- chunks = []
24
-
25
- offset = 0
26
- while offset < bytesize
27
- chunks << byteslice(offset, chunk_size)
28
- offset += chunk_size
29
- end
30
-
31
- return chunks
25
+ RubyDNS::chunked(self)
32
26
  end
33
27
  end
@@ -18,14 +18,10 @@
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
- require 'stringio'
21
+ require 'rubydns/message'
23
22
 
24
23
  module RubyDNS
25
-
26
24
  module UDPHandler
27
- UDP_TRUNCATION_SIZE = 512
28
-
29
25
  def initialize(server)
30
26
  @server = server
31
27
  end
@@ -35,7 +31,7 @@ module RubyDNS
35
31
  query = nil
36
32
 
37
33
  begin
38
- query = Resolv::DNS::Message::decode(data)
34
+ query = RubyDNS::decode_message(data)
39
35
 
40
36
  return server.process_query(query, &block)
41
37
  rescue
@@ -69,8 +65,10 @@ module RubyDNS
69
65
  @server.logger.warn "Response via UDP was larger than #{UDP_TRUNCATION_SIZE}!"
70
66
 
71
67
  # Reencode data with truncation flag marked as true:
72
- answer.tc = 1
73
- data = answer.encode.byteslice(0,UDP_TRUNCATION_SIZE)
68
+ truncation_error = Resolv::DNS::Message.new(answer.id)
69
+ truncation_error.tc = 1
70
+
71
+ data = truncation_error.encode
74
72
  end
75
73
 
76
74
  self.send_data(data)
@@ -84,14 +82,15 @@ module RubyDNS
84
82
  module TCPHandler
85
83
  def initialize(server)
86
84
  @server = server
87
- @buffer = nil
85
+
86
+ @buffer = BinaryStringIO.new
87
+
88
88
  @length = nil
89
89
  @processed = 0
90
90
  end
91
91
 
92
92
  def receive_data(data)
93
93
  # We buffer data until we've received the entire packet:
94
- @buffer ||= StringIO.new
95
94
  @buffer.write(data)
96
95
 
97
96
  # Message includes a 16-bit length field.. we need to see if we have received it yet:
@@ -0,0 +1,38 @@
1
+ # Copyright (c) 2009, 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'eventmachine'
22
+ require 'stringio'
23
+ require 'resolv'
24
+
25
+ module RubyDNS
26
+ UDP_TRUNCATION_SIZE = 512
27
+
28
+ # The DNS message container.
29
+ Message = Resolv::DNS::Message
30
+
31
+ def self.decode_message(data)
32
+ if data.respond_to? :force_encoding
33
+ data.force_encoding("BINARY")
34
+ end
35
+
36
+ Message.decode(data)
37
+ end
38
+ end
@@ -0,0 +1,219 @@
1
+ # Copyright (c) 2012 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'rubydns/message'
22
+
23
+ module RubyDNS
24
+ class InvalidProtocolError < StandardError
25
+ end
26
+
27
+ class ResolutionFailure < StandardError
28
+ end
29
+
30
+ class Resolver
31
+ # Servers are specified in the same manor as options[:listen], e.g.
32
+ # [:tcp/:udp, address, port]
33
+ # In the case of multiple servers, they will be checked in sequence.
34
+ def initialize(servers, options = {})
35
+ @servers = servers
36
+ @sequence = 0
37
+
38
+ @options = options
39
+ end
40
+
41
+ # Provides the next sequence identification number which is used to keep track of DNS messages.
42
+ def next_id!
43
+ return (@sequence += 1)
44
+ end
45
+
46
+ # Look up a named resource of the given resource_class.
47
+ def query(name, resource_class = Resolv::DNS::Resource::IN::A, &block)
48
+ message = Resolv::DNS::Message.new(next_id!)
49
+ message.rd = 1
50
+ message.add_question name, resource_class
51
+
52
+ Request.fetch(message, @servers, @options, &block)
53
+ end
54
+
55
+ # Manages a single DNS question message across one or more servers.
56
+ class Request
57
+ include EventMachine::Deferrable
58
+
59
+ def self.fetch(*args)
60
+ request = self.new(*args)
61
+
62
+ request.callback do |message|
63
+ yield message
64
+ end
65
+
66
+ request.errback do |error|
67
+ yield error
68
+ end
69
+
70
+ request.run!
71
+ end
72
+
73
+ def initialize(message, servers, options = {}, &block)
74
+ @message = message
75
+ @packet = message.encode
76
+
77
+ @servers = servers.dup
78
+
79
+ # We select the protocol based on the size of the data:
80
+ if @packet.bytesize > UDP_TRUNCATION_SIZE
81
+ @servers.delete_if{|server| server[0] == :udp}
82
+ end
83
+
84
+ # Measured in seconds:
85
+ @timeout = options[:timeout] || 5
86
+
87
+ @logger = options[:logger]
88
+ end
89
+
90
+ attr :message
91
+ attr :packet
92
+ attr :logger
93
+
94
+ def run!
95
+ try_next_server!
96
+ end
97
+
98
+ def process_response!(response)
99
+ if response.tc != 0
100
+ @logger.warn "[#{@message.id}] Received truncated response!" if @logger
101
+ # We hardcode this behaviour for now.
102
+ try_next_server!
103
+ else
104
+ @logger.warn "[#{@message.id}] Received valid response #{response.inspect}" if @logger
105
+ succeed response
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ def try_next_server!
112
+ if @request
113
+ @request.close_connection
114
+ @request = nil
115
+ end
116
+
117
+ if @servers.size > 0
118
+ @server = @servers.shift
119
+
120
+ @logger.debug "[#{@message.id}] Sending request to server #{@server.inspect}" if @logger
121
+
122
+ # We make requests one at a time to the given server, naturally the servers are ordered in terms of priority.
123
+ case @server[0]
124
+ when :udp
125
+ @request = UDPRequestHandler.open(@server[1], @server[2], self)
126
+ when :tcp
127
+ @request = TCPRequestHandler.open(@server[1], @server[2], self)
128
+ else
129
+ raise InvalidProtocolError.new(@server)
130
+ end
131
+
132
+ # Setting up the timeout...
133
+ EventMachine::Timer.new(@timeout) do
134
+ @logger.debug "[#{@message.id}] Request timed out!" if @logger
135
+
136
+ try_next_server!
137
+ end
138
+ else
139
+ fail ResolutionFailure.new("No available servers responded to the request.")
140
+ end
141
+ end
142
+
143
+ module UDPRequestHandler
144
+ def self.open(host, port, request)
145
+ # Open a datagram socket... EventMachine doesn't support connected datagram sockets, so we have to cheat a bit:
146
+ EventMachine::open_datagram_socket('', 0, self, request, host, port)
147
+ end
148
+
149
+ def initialize(request, host, port)
150
+ @request = request
151
+ @host = host
152
+ @port = port
153
+ end
154
+
155
+ def post_init
156
+ # Sending question to remote DNS server...
157
+ send_datagram(@request.packet, @host, @port)
158
+ end
159
+
160
+ def receive_data(data)
161
+ # Receiving response from remote DNS server...
162
+ message = RubyDNS::decode_message(data)
163
+
164
+ # The message id must match, and it can't be truncated:
165
+ if message.id == @request.message.id
166
+ @request.process_response!(message)
167
+ else
168
+ @request.logger.warn "[#{@request.message.id}] Received response with incorrect message id: #{message.id}" if @request.logger
169
+ end
170
+ end
171
+ end
172
+
173
+ module TCPRequestHandler
174
+ def self.open(host, port, request)
175
+ EventMachine::connect(host, port, TCPRequestHandler, request)
176
+ end
177
+
178
+ def initialize(request)
179
+ @request = request
180
+ @buffer = nil
181
+ @length = nil
182
+ end
183
+
184
+ def post_init
185
+ data = @request.packet
186
+
187
+ send_data([data.bytesize].pack('n'))
188
+ send_data data
189
+ end
190
+
191
+ def receive_data(data)
192
+ # We buffer data until we've received the entire packet:
193
+ @buffer ||= BinaryStringIO.new
194
+ @buffer.write(data)
195
+
196
+ if @length == nil
197
+ if @buffer.size > 2
198
+ @length = @buffer.string.byteslice(0, 2).unpack('n')[0]
199
+ end
200
+ end
201
+
202
+ if @buffer.size == (@length + 2)
203
+ data = @buffer.string.byteslice(2, @length)
204
+
205
+ message = RubyDNS::decode_message(data)
206
+
207
+ if message.id == @request.message.id
208
+ @request.process_response!(message)
209
+ else
210
+ @request.logger.warn "[#{@request.message.id}] Received response with incorrect message id: #{message.id}" if @request.logger
211
+ end
212
+ elsif @buffer.size > (@length + 2)
213
+ @request.try_next_server!
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end