rubydns 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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