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 +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
|