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 +21 -2
- data/lib/rubydns.rb +9 -1
- data/lib/rubydns/extensions/hexdump.rb +1 -1
- data/lib/rubydns/{resolv.rb → extensions/resolv-1.8.rb} +2 -2
- data/lib/rubydns/extensions/resolv-1.9.rb +111 -0
- data/lib/rubydns/extensions/string-1.8.rb +41 -0
- data/lib/rubydns/extensions/string-1.9.rb +33 -0
- data/lib/rubydns/handler.rb +33 -15
- data/lib/rubydns/server.rb +15 -30
- data/lib/rubydns/transaction.rb +4 -63
- data/lib/rubydns/version.rb +2 -2
- data/test/daemon1.rb +7 -3
- data/test/daemon2.rb +4 -3
- data/test/example1.rb +5 -4
- data/test/fortune-dns.rb +32 -12
- data/test/log/FortuneDNS/stderr.log +6880 -4467
- data/test/log/FortuneDNS/stdout.log +24 -0
- data/test/soa_example1.rb +8 -7
- metadata +55 -60
- data/test/run/FortuneDNS/FortuneDNS.pid +0 -1
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",
|
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$/,
|
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
|
-
|
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.
|
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
|
-
@
|
62
|
+
@question += other.question
|
63
63
|
@answer += other.answer
|
64
64
|
@authority += other.authority
|
65
|
-
@
|
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
|
data/lib/rubydns/handler.rb
CHANGED
@@ -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.
|
35
|
-
|
34
|
+
server.logger.debug "Receiving incoming query (#{data.bytesize} bytes)..."
|
35
|
+
query = nil
|
36
|
+
|
36
37
|
begin
|
37
|
-
|
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.
|
66
|
+
@server.logger.debug "Writing response to client (#{data.bytesize} bytes) via UDP..."
|
51
67
|
|
52
|
-
if
|
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
|
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
|
-
|
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
|
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.
|
114
|
+
@server.logger.debug "Writing response to client (#{data.bytesize} bytes) via TCP..."
|
97
115
|
|
98
|
-
self.send_data([data.
|
116
|
+
self.send_data([data.bytesize].pack('n'))
|
99
117
|
self.send_data(data)
|
100
118
|
end
|
101
119
|
|
data/lib/rubydns/server.rb
CHANGED
@@ -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/,
|
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",
|
59
|
-
# match(/g?mail.(com|org|net)/, [
|
57
|
+
# match("gmail.com", IN::MX)
|
58
|
+
# match(/g?mail.(com|org|net)/, [IN::MX, IN::A])
|
60
59
|
#
|
61
|
-
def match
|
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,
|
112
|
-
@logger.debug "Searching for #{name} #{
|
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
|
106
|
+
# Match failed against resource_class?
|
120
107
|
case pattern[1]
|
121
|
-
when
|
122
|
-
next unless pattern[1] ==
|
123
|
-
@logger.debug "Resource
|
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?(
|
126
|
-
@logger.debug "Resource
|
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} #{
|
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
|
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
|
190
|
+
return answer
|
206
191
|
end
|
207
192
|
end
|
208
193
|
end
|