rubydns 0.3.4 → 0.4.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/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