rubydns 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -26,12 +26,15 @@ This is copied from `test/example1.rb`. It has been simplified slightly.
26
26
  $R = Resolv::DNS.new
27
27
 
28
28
  RubyDNS::run_server do
29
+ Name = Resolv::DNS::Name
30
+ IN = Resolv::DNS::Resource::IN
31
+
29
32
  # For this exact address record, return an IP address
30
- match("dev.mydomain.org", :A) do |transaction|
33
+ match("dev.mydomain.org", IN::A) do |transaction|
31
34
  transaction.respond!("10.0.0.80")
32
35
  end
33
36
 
34
- match(/^test([0-9]+).mydomain.org$/, :A) do |match_data, transaction|
37
+ match(/^test([0-9]+).mydomain.org$/, IN::A) do |match_data, transaction|
35
38
  offset = match_data[1].to_i
36
39
 
37
40
  if offset > 0 && offset < 10
@@ -56,6 +59,22 @@ After starting this server you can test it using dig:
56
59
  dig @localhost dev.mydomain.org
57
60
  dig @localhost google.com
58
61
 
62
+ Compatibility
63
+ -------------
64
+
65
+ From RubyDNS version `0.4.0`, the recommended minimum Ruby version is `1.9.3` for complete support. Some features may not work as expected on Ruby version `1.8.x` and it is not tested significantly.
66
+
67
+ ### Migrating from RubyDNS 0.3.x to 0.4.x ###
68
+
69
+ Due to changes in `resolv.rb`, superficial parts of RubyDNS have changed. Rather than using `:A` to specify A-records, one must now use the class name.
70
+
71
+ match(..., :A)
72
+
73
+ becomes
74
+
75
+ IN = Resolv::DNS::Resource::IN
76
+ match(..., IN::A)
77
+
59
78
  Todo
60
79
  ----
61
80
 
data/lib/rubydns.rb CHANGED
@@ -19,7 +19,15 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require 'rubydns/version'
22
- require 'rubydns/resolv'
22
+
23
+ if RUBY_VERSION < "1.9"
24
+ require 'rubydns/extensions/resolv-1.8'
25
+ require 'rubydns/extensions/string-1.8'
26
+ else
27
+ require 'rubydns/extensions/resolv-1.9'
28
+ require 'rubydns/extensions/string-1.9'
29
+ end
30
+
23
31
  require 'rubydns/server'
24
32
 
25
33
  require 'logger'
@@ -25,7 +25,7 @@ class String
25
25
  i = 1
26
26
  out = StringIO.new
27
27
 
28
- out.puts "Size: #{self.size}"
28
+ out.puts "Size: #{self.bytesize}"
29
29
  while (self.length > 16*(i-1))
30
30
  a = self.slice(16*(i-1)..(16*i)-1)
31
31
  out.printf("%06x: %4.4x %4.4x %4.4x %4.4x %4.4x %4.4x %4.4x %4.4x ", (i-1)*16, *a.unpack("n16"))
@@ -59,10 +59,10 @@ class Resolv
59
59
  # Authoritive Answer
60
60
  @aa = @aa && other.aa
61
61
 
62
- @additional += other.additional
62
+ @question += other.question
63
63
  @answer += other.answer
64
64
  @authority += other.authority
65
- @question += other.question
65
+ @additional += other.additional
66
66
 
67
67
  # Recursion Available
68
68
  @ra = @ra || other.ra
@@ -0,0 +1,111 @@
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 'resolv'
22
+
23
+ class Resolv
24
+ class DNS
25
+ # This issue was not fixed in 1.9.3 but the proposed patch works as expected:
26
+ # https://bugs.ruby-lang.org/issues/4788
27
+ def fetch_resource(name, typeclass)
28
+ lazy_initialize
29
+ requester = make_udp_requester
30
+ senders = {}
31
+ begin
32
+ @config.resolv(name) {|candidate, tout, nameserver, port|
33
+ msg = Message.new
34
+ msg.rd = 1
35
+ msg.add_question(candidate, typeclass)
36
+ unless sender = senders[[candidate, nameserver, port]]
37
+ sender = senders[[candidate, nameserver, port]] =
38
+ requester.sender(msg, candidate, nameserver, port)
39
+ end
40
+ reply, reply_name = requester.request(sender, tout)
41
+ case reply.rcode
42
+ when RCode::NoError
43
+ if reply.tc == 1 and not Requester::TCP === requester
44
+ requester.close
45
+ # Retry via TCP:
46
+ requester = make_tcp_requester(nameserver, port)
47
+ senders = {}
48
+ # This will use TCP for all remaining candidates (assuming the
49
+ # current candidate does not already respond successfully via
50
+ # TCP). This makes sense because we already know the full
51
+ # response will not fit in an untruncated UDP packet.
52
+ redo
53
+ else
54
+ yield(reply, reply_name)
55
+ end
56
+ return
57
+ when RCode::NXDomain
58
+ raise Config::NXDomain.new(reply_name.to_s)
59
+ else
60
+ raise Config::OtherResolvError.new(reply_name.to_s)
61
+ end
62
+ }
63
+ ensure
64
+ requester.close
65
+ end
66
+ end
67
+
68
+ def each_resource(name, typeclass, &proc)
69
+ fetch_resource(name, typeclass) do |reply, reply_name|
70
+ extract_resources(reply, reply_name, typeclass, &proc)
71
+ end
72
+ end
73
+
74
+ # Queries the given DNS server and returns its response in its entirety.
75
+ # This allows such responses to be passed upstream with little or no
76
+ # modification/reinterpretation.
77
+ def query(name, typeclass)
78
+ fetch_resource(name, typeclass) do |reply, reply_name|
79
+ return reply, reply_name
80
+ end
81
+ end
82
+
83
+ class Message
84
+ # Merge the given message with this message. A number of heuristics are
85
+ # applied in order to ensure that the result makes sense. For example,
86
+ # If the current message is not recursive but is being merged with a
87
+ # message that was recursive, this bit is maintained. If either message
88
+ # is authoritive, then the result is also authoritive.
89
+ #
90
+ # Modifies the current message in place.
91
+ def merge! (other)
92
+ # Authoritive Answer
93
+ @aa = @aa && other.aa
94
+
95
+ @question += other.question
96
+ @answer += other.answer
97
+ @authority += other.authority
98
+ @additional += other.additional
99
+
100
+ # Recursion Available
101
+ @ra = @ra || other.ra
102
+
103
+ # Result Code (Error Code)
104
+ @rcode = other.rcode unless other.rcode == 0
105
+
106
+ # Recursion Desired
107
+ @rd = @rd || other.rd
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,41 @@
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
+ class String
22
+ def bytesize
23
+ size
24
+ end
25
+
26
+ def byteslice(*args)
27
+ self[*args]
28
+ 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
41
+ 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
+ class String
22
+ 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
32
+ end
33
+ end
@@ -23,23 +23,39 @@ require 'stringio'
23
23
 
24
24
  module RubyDNS
25
25
 
26
- UDP_TRUNCATION_SIZE = 512
27
-
28
26
  module UDPHandler
27
+ UDP_TRUNCATION_SIZE = 512
28
+
29
29
  def initialize(server)
30
30
  @server = server
31
31
  end
32
-
32
+
33
33
  def self.process(server, data, &block)
34
- server.logger.debug "Receiving incoming query (#{data.size} bytes)..."
35
-
34
+ server.logger.debug "Receiving incoming query (#{data.bytesize} bytes)..."
35
+ query = nil
36
+
36
37
  begin
37
- server.receive_data(data, &block)
38
+ query = Resolv::DNS::Message::decode(data)
39
+
40
+ return server.process_query(query, &block)
38
41
  rescue
39
42
  server.logger.error "Error processing request!"
40
43
  server.logger.error "#{$!.class}: #{$!.message}"
41
44
 
42
45
  $!.backtrace.each { |at| server.logger.error at }
46
+
47
+ # Encoding may fail, so we need to handle this particular case:
48
+ server_failure = Resolv::DNS::Message::new(query ? query.id : 0)
49
+ server_failure.qr = 1
50
+ server_failure.opcode = query ? query.opcode : 0
51
+ server_failure.aa = 1
52
+ server_failure.rd = 0
53
+ server_failure.ra = 0
54
+
55
+ server_failure.rcode = Resolv::DNS::RCode::ServFail
56
+
57
+ # We can't do anything at this point...
58
+ yield server_failure
43
59
  end
44
60
  end
45
61
 
@@ -47,13 +63,14 @@ module RubyDNS
47
63
  UDPHandler.process(@server, data) do |answer|
48
64
  data = answer.encode
49
65
 
50
- @server.logger.debug "Writing response to client (#{data.size} bytes)"
66
+ @server.logger.debug "Writing response to client (#{data.bytesize} bytes) via UDP..."
51
67
 
52
- if (data.size > UDP_TRUNCATION_SIZE)
68
+ if data.bytesize > UDP_TRUNCATION_SIZE
53
69
  @server.logger.warn "Response via UDP was larger than #{UDP_TRUNCATION_SIZE}!"
54
70
 
71
+ # Reencode data with truncation flag marked as true:
55
72
  answer.tc = 1
56
- data = answer.encode[0,UDP_TRUNCATION_SIZE]
73
+ data = answer.encode.byteslice(0,UDP_TRUNCATION_SIZE)
57
74
  end
58
75
 
59
76
  self.send_data(data)
@@ -73,29 +90,30 @@ module RubyDNS
73
90
  end
74
91
 
75
92
  def receive_data(data)
93
+ # We buffer data until we've received the entire packet:
76
94
  @buffer ||= StringIO.new
77
95
  @buffer.write(data)
78
96
 
79
- # Message includes a 16-bit length field
97
+ # Message includes a 16-bit length field.. we need to see if we have received it yet:
80
98
  if @length == nil
81
99
  if (@buffer.size - @processed) < 2
82
100
  raise LengthError.new("Malformed message smaller than two bytes received")
83
101
  end
84
102
 
85
- @length = @buffer.string[@processed, 2].unpack('n')[0]
103
+ # Grab the length field:
104
+ @length = @buffer.string.byteslice(@processed, 2).unpack('n')[0]
86
105
  @processed += 2
87
106
  end
88
107
 
89
-
90
108
  if (@buffer.size - @processed) >= @length
91
- data = @buffer.string[@processed, @length]
109
+ data = @buffer.string.byteslice(@processed, @length)
92
110
 
93
111
  UDPHandler.process(@server, data) do |answer|
94
112
  data = answer.encode
95
113
 
96
- @server.logger.debug "Writing response to client (#{data.size} bytes)"
114
+ @server.logger.debug "Writing response to client (#{data.bytesize} bytes) via TCP..."
97
115
 
98
- self.send_data([data.size].pack('n'))
116
+ self.send_data([data.bytesize].pack('n'))
99
117
  self.send_data(data)
100
118
  end
101
119
 
@@ -26,11 +26,10 @@ module RubyDNS
26
26
  # are used to match against incoming DNS questions. These rules are used to
27
27
  # generate responses which are either DNS resource records or failures.
28
28
  class Server
29
-
30
29
  # Instantiate a server with a block
31
30
  #
32
31
  # server = Server.new do
33
- # match(/server.mydomain.com/, :A) do |transaction|
32
+ # match(/server.mydomain.com/, IN::A) do |transaction|
34
33
  # transaction.respond!("1.2.3.4")
35
34
  # end
36
35
  # end
@@ -55,22 +54,10 @@ module RubyDNS
55
54
  # types which the rule matches against.
56
55
  #
57
56
  # match("www.google.com")
58
- # match("gmail.com", :MX)
59
- # match(/g?mail.(com|org|net)/, [:MX, :A])
57
+ # match("gmail.com", IN::MX)
58
+ # match(/g?mail.(com|org|net)/, [IN::MX, IN::A])
60
59
  #
61
- def match (*pattern, &block)
62
- # Normalize pattern
63
- case pattern[1]
64
- when nil
65
- # Do nothing
66
- when String
67
- pattern[1] = pattern[1].upcase
68
- when Symbol
69
- pattern[1] = pattern[1].to_s.upcase
70
- when Array
71
- pattern[1] = pattern[1].collect { |v| v.to_s.upcase }
72
- end
73
-
60
+ def match(*pattern, &block)
74
61
  @rules << [pattern, Proc.new(&block)]
75
62
  end
76
63
 
@@ -108,22 +95,22 @@ module RubyDNS
108
95
  #
109
96
  # If a rule returns false, it is considered that the rule failed and
110
97
  # futher matching is carried out.
111
- def process(name, record_type, *args)
112
- @logger.debug "Searching for #{name} #{record_type}"
98
+ def process(name, resource_class, *args)
99
+ @logger.debug "Searching for #{name} #{resource_class.name}"
113
100
 
114
101
  @rules.each do |rule|
115
102
  @logger.debug "Checking rule #{rule[0].inspect}..."
116
103
 
117
104
  pattern = rule[0]
118
105
 
119
- # Match failed against record_type?
106
+ # Match failed against resource_class?
120
107
  case pattern[1]
121
- when String
122
- next unless pattern[1] == record_type
123
- @logger.debug "Resource type #{record_type} matched"
108
+ when Class
109
+ next unless pattern[1] == resource_class
110
+ @logger.debug "Resource class #{resource_class.name} matched"
124
111
  when Array
125
- next unless pattern[1].include?(record_type)
126
- @logger.debug "Resource type #{record_type} matched #{pattern[1].inspect}"
112
+ next unless pattern[1].include?(resource_class)
113
+ @logger.debug "Resource class #{resource_class} matched #{pattern[1].inspect}"
127
114
  end
128
115
 
129
116
  # Match succeeded against name?
@@ -167,15 +154,13 @@ module RubyDNS
167
154
  if @otherwise
168
155
  @otherwise.call(*args)
169
156
  else
170
- @logger.warn "Failed to handle #{name} #{record_type}!"
157
+ @logger.warn "Failed to handle #{name} #{resource_class.name}!"
171
158
  end
172
159
  end
173
160
 
174
161
  # Process an incoming DNS message. Returns a serialized message to be
175
162
  # sent back to the client.
176
- def receive_data(data, &block)
177
- query = Resolv::DNS::Message::decode(data)
178
-
163
+ def process_query(query, &block)
179
164
  # Setup answer
180
165
  answer = Resolv::DNS::Message::new(query.id)
181
166
  answer.qr = 1 # 0 = Query, 1 = Response
@@ -202,7 +187,7 @@ module RubyDNS
202
187
  if block_given?
203
188
  yield answer
204
189
  else
205
- answer.encode
190
+ return answer
206
191
  end
207
192
  end
208
193
  end