gitlab-net-dns 0.9.1
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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.gitlab-ci.yml +20 -0
- data/.rspec +1 -0
- data/.rubocop.yml +3 -0
- data/.rubocop_defaults.yml +359 -0
- data/.rubocop_todo.yml +207 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +113 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +56 -0
- data/README.md +172 -0
- data/Rakefile +38 -0
- data/THANKS.rdoc +24 -0
- data/bin/console +14 -0
- data/demo/check_soa.rb +104 -0
- data/demo/threads.rb +18 -0
- data/gitlab-net-dns.gemspec +24 -0
- data/lib/net/dns.rb +104 -0
- data/lib/net/dns/header.rb +705 -0
- data/lib/net/dns/names.rb +120 -0
- data/lib/net/dns/packet.rb +560 -0
- data/lib/net/dns/question.rb +185 -0
- data/lib/net/dns/resolver.rb +1214 -0
- data/lib/net/dns/resolver/socks.rb +148 -0
- data/lib/net/dns/resolver/timeouts.rb +70 -0
- data/lib/net/dns/rr.rb +356 -0
- data/lib/net/dns/rr/a.rb +114 -0
- data/lib/net/dns/rr/aaaa.rb +94 -0
- data/lib/net/dns/rr/classes.rb +130 -0
- data/lib/net/dns/rr/cname.rb +74 -0
- data/lib/net/dns/rr/hinfo.rb +96 -0
- data/lib/net/dns/rr/mr.rb +70 -0
- data/lib/net/dns/rr/mx.rb +82 -0
- data/lib/net/dns/rr/ns.rb +70 -0
- data/lib/net/dns/rr/null.rb +50 -0
- data/lib/net/dns/rr/ptr.rb +77 -0
- data/lib/net/dns/rr/soa.rb +75 -0
- data/lib/net/dns/rr/srv.rb +41 -0
- data/lib/net/dns/rr/txt.rb +58 -0
- data/lib/net/dns/rr/types.rb +191 -0
- data/lib/net/dns/version.rb +8 -0
- data/spec/fixtures/resolv.conf +4 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/unit/resolver/dns_timeout_spec.rb +36 -0
- data/spec/unit/resolver/tcp_timeout_spec.rb +46 -0
- data/spec/unit/resolver/udp_timeout_spec.rb +46 -0
- data/test/test_helper.rb +13 -0
- data/test/unit/header_test.rb +164 -0
- data/test/unit/names_test.rb +21 -0
- data/test/unit/packet_test.rb +47 -0
- data/test/unit/question_test.rb +81 -0
- data/test/unit/resolver_test.rb +114 -0
- data/test/unit/rr/a_test.rb +106 -0
- data/test/unit/rr/aaaa_test.rb +102 -0
- data/test/unit/rr/classes_test.rb +83 -0
- data/test/unit/rr/cname_test.rb +90 -0
- data/test/unit/rr/hinfo_test.rb +111 -0
- data/test/unit/rr/mr_test.rb +99 -0
- data/test/unit/rr/mx_test.rb +106 -0
- data/test/unit/rr/ns_test.rb +80 -0
- data/test/unit/rr/types_test.rb +71 -0
- data/test/unit/rr_test.rb +127 -0
- metadata +172 -0
@@ -0,0 +1,120 @@
|
|
1
|
+
module Net # :nodoc:
|
2
|
+
module DNS
|
3
|
+
module Names
|
4
|
+
# Base error class.
|
5
|
+
class Error < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
# Generic Names Error.
|
9
|
+
class ExpandError < Error
|
10
|
+
end
|
11
|
+
|
12
|
+
INT16SZ = 2
|
13
|
+
|
14
|
+
# Expand a compressed name in a DNS Packet object. Please
|
15
|
+
# see RFC1035 for an explanation of how the compression
|
16
|
+
# in DNS packets works, how may it be useful and how should
|
17
|
+
# be handled.
|
18
|
+
#
|
19
|
+
# This method accept two parameters: a raw packet data and an
|
20
|
+
# offset, which indicates the point in the packet in which the
|
21
|
+
# parsing has arrived.
|
22
|
+
#
|
23
|
+
def dn_expand(packet, offset)
|
24
|
+
name = ""
|
25
|
+
packetlen = packet.size
|
26
|
+
loop do
|
27
|
+
raise ExpandError, "Offset is greater than packet length!" if packetlen < (offset + 1)
|
28
|
+
|
29
|
+
len = packet.unpack("@#{offset} C")[0]
|
30
|
+
|
31
|
+
if len == 0
|
32
|
+
offset += 1
|
33
|
+
break
|
34
|
+
elsif (len & 0xC0) == 0xC0
|
35
|
+
raise ExpandError, "Packet ended before offset expand" if packetlen < (offset + INT16SZ)
|
36
|
+
|
37
|
+
ptr = packet.unpack("@#{offset} n")[0]
|
38
|
+
ptr &= 0x3FFF
|
39
|
+
name2 = dn_expand(packet, ptr)[0]
|
40
|
+
raise ExpandError, "Packet is malformed!" if name2.nil?
|
41
|
+
|
42
|
+
name += name2
|
43
|
+
offset += INT16SZ
|
44
|
+
break
|
45
|
+
else
|
46
|
+
offset += 1
|
47
|
+
raise ExpandError, "No expansion found" if packetlen < (offset + len)
|
48
|
+
|
49
|
+
elem = packet[offset..offset + len - 1]
|
50
|
+
name += "#{elem}."
|
51
|
+
offset += len
|
52
|
+
end
|
53
|
+
end
|
54
|
+
[name, offset] # name.chomp(".") if trailing dot has to be omitted
|
55
|
+
end
|
56
|
+
|
57
|
+
def pack_name(name)
|
58
|
+
if name.size > 255
|
59
|
+
raise ArgumentError, "Name may not exceed 255 chars"
|
60
|
+
end
|
61
|
+
|
62
|
+
arr = name.split(".")
|
63
|
+
str = ""
|
64
|
+
arr.each do |elem|
|
65
|
+
if elem.size > 63
|
66
|
+
raise ArgumentError, "Label may not exceed 63 chars"
|
67
|
+
end
|
68
|
+
|
69
|
+
str += [elem.size, elem].pack("Ca*")
|
70
|
+
end
|
71
|
+
str += [0].pack("C")
|
72
|
+
str
|
73
|
+
end
|
74
|
+
|
75
|
+
def names_array(name)
|
76
|
+
arr = name.split(".")
|
77
|
+
ar = []
|
78
|
+
string = ""
|
79
|
+
arr.size.times do |i|
|
80
|
+
x = i + 1
|
81
|
+
elem = arr[-x]
|
82
|
+
len = elem.size
|
83
|
+
string = (string.reverse + [len, elem].pack("Ca*").reverse).reverse
|
84
|
+
ar.unshift(string)
|
85
|
+
end
|
86
|
+
ar
|
87
|
+
end
|
88
|
+
|
89
|
+
def dn_comp(name, offset, compnames)
|
90
|
+
names = {}
|
91
|
+
ptr = 0
|
92
|
+
str = ""
|
93
|
+
arr = names_array(name)
|
94
|
+
arr.each do |entry|
|
95
|
+
if compnames.key?(entry)
|
96
|
+
ptr = 0xC000 | compnames[entry]
|
97
|
+
str += [ptr].pack("n")
|
98
|
+
offset += INT16SZ
|
99
|
+
break
|
100
|
+
else
|
101
|
+
len = entry.unpack("C")[0]
|
102
|
+
elem = entry[1..len]
|
103
|
+
str += [len, elem].pack("Ca*")
|
104
|
+
names.update(entry.to_s => offset)
|
105
|
+
offset += len
|
106
|
+
end
|
107
|
+
end
|
108
|
+
[str, offset, names]
|
109
|
+
end
|
110
|
+
|
111
|
+
def valid?(name)
|
112
|
+
if name =~ /^([a-z0-9]([-a-z0-9]*[a-z0-9])?\.)+((a[cdefgilmnoqrstuwxz]|aero|arpa)|(b[abdefghijmnorstvwyz]|biz)|(c[acdfghiklmnorsuvxyz]|cat|com|coop)|d[ejkmoz]|(e[ceghrstu]|edu)|f[ijkmor]|(g[abdefghilmnpqrstuwy]|gov)|h[kmnrtu]|(i[delmnoqrst]|info|int)|(j[emop]|jobs)|k[eghimnprwyz]|l[abcikrstuvy]|(m[acdghklmnopqrstuvwxyz]|mil|mobi|museum)|(n[acefgilopruz]|name|net)|(om|org)|(p[aefghklmnrstwy]|pro)|qa|r[eouw]|s[abcdeghijklmnortvyz]|(t[cdfghjklmnoprtvwz]|travel)|u[agkmsyz]|v[aceginu]|w[fs]|y[etu]|z[amw])$/i
|
113
|
+
name
|
114
|
+
else
|
115
|
+
raise ArgumentError, "Invalid FQDN: #{name}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,560 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'net/dns/names'
|
3
|
+
require 'net/dns/header'
|
4
|
+
require 'net/dns/question'
|
5
|
+
require 'net/dns/rr'
|
6
|
+
|
7
|
+
module Net
|
8
|
+
module DNS
|
9
|
+
#
|
10
|
+
# = Net::DNS::Packet
|
11
|
+
#
|
12
|
+
# The Net::DNS::Packet class represents an entire DNS packet,
|
13
|
+
# divided in his main section:
|
14
|
+
#
|
15
|
+
# * Header (instance of Net::DNS::Header)
|
16
|
+
# * Question (array of Net::DNS::Question objects)
|
17
|
+
# * Answer, Authority, Additional (each formed by an array of Net::DNS::RR
|
18
|
+
# objects)
|
19
|
+
#
|
20
|
+
# You can use this class whenever you need to create a DNS packet, whether
|
21
|
+
# in an user application, in a resolver instance (have a look, for instance,
|
22
|
+
# at the <tt>Net::DNS::Resolver#send</tt> method) or for a nameserver.
|
23
|
+
#
|
24
|
+
# For example:
|
25
|
+
#
|
26
|
+
# # Create a packet
|
27
|
+
# packet = Net::DNS::Packet.new("www.example.com")
|
28
|
+
# mx = Net::DNS::Packet.new("example.com", Net::DNS::MX)
|
29
|
+
#
|
30
|
+
# # Getting packet binary data, suitable for network transmission
|
31
|
+
# data = packet.data
|
32
|
+
#
|
33
|
+
# A packet object can be created from binary data too, like an
|
34
|
+
# answer packet just received from a network stream:
|
35
|
+
#
|
36
|
+
# packet = Net::DNS::Packet::parse(data)
|
37
|
+
#
|
38
|
+
# Each part of a packet can be gotten by the right accessors:
|
39
|
+
#
|
40
|
+
# header = packet.header # Instance of Net::DNS::Header class
|
41
|
+
# question = packet.question # Instance of Net::DNS::Question class
|
42
|
+
#
|
43
|
+
# # Iterate over additional RRs
|
44
|
+
# packet.additional.each do |rr|
|
45
|
+
# puts "Got an #{rr.type} record"
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# Some iterators have been written to easy the access of those RRs,
|
49
|
+
# which are often the most important. So instead of doing:
|
50
|
+
#
|
51
|
+
# packet.answer.each do |rr|
|
52
|
+
# if rr.type == Net::DNS::RR::Types::A
|
53
|
+
# # do something with +rr.address+
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# we can do:
|
58
|
+
#
|
59
|
+
# packet.each_address do |ip|
|
60
|
+
# # do something with +ip+
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# Be sure you don't miss all the iterators in the class documentation.
|
64
|
+
#
|
65
|
+
# == Logging facility
|
66
|
+
#
|
67
|
+
# As Net::DNS::Resolver class, Net::DNS::Packet class has its own logging
|
68
|
+
# facility too. It work in the same way the other one do, so you can
|
69
|
+
# maybe want to override it or change the file descriptor.
|
70
|
+
#
|
71
|
+
# packet = Net::DNS::Packet.new("www.example.com")
|
72
|
+
# packet.logger = $stderr
|
73
|
+
#
|
74
|
+
# # or even
|
75
|
+
# packet.logger = Logger.new("/tmp/packet.log")
|
76
|
+
#
|
77
|
+
# If the <tt>Net::DNS::Packet</tt> class is directly instantiated by the <tt>Net::DNS::Resolver</tt>
|
78
|
+
# class, like the great majority of the time, it will use the same logger facility.
|
79
|
+
#
|
80
|
+
# Logger level will be set to <tt>Logger::Debug</tt> if <tt>$DEBUG</tt> variable is set.
|
81
|
+
#
|
82
|
+
class Packet
|
83
|
+
include Names
|
84
|
+
|
85
|
+
# Base error class.
|
86
|
+
class Error < StandardError
|
87
|
+
end
|
88
|
+
|
89
|
+
# Generic Packet Error.
|
90
|
+
class PacketError < Error
|
91
|
+
end
|
92
|
+
|
93
|
+
attr_reader :header, :question, :answer, :authority, :additional
|
94
|
+
attr_reader :answerfrom, :answersize
|
95
|
+
|
96
|
+
# Creates a new instance of <tt>Net::DNS::Packet</tt> class. Arguments are the
|
97
|
+
# canonical name of the resource, an optional type field and an optional
|
98
|
+
# class field. The record type and class can be omitted; they default
|
99
|
+
# to +A+ and +IN+.
|
100
|
+
#
|
101
|
+
# packet = Net::DNS::Packet.new("www.example.com")
|
102
|
+
# packet = Net::DNS::Packet.new("example.com", Net::DNS::MX)
|
103
|
+
# packet = Net::DNS::Packet.new("example.com", Net::DNS::TXT, Net::DNS::CH)
|
104
|
+
#
|
105
|
+
# This class no longer instantiate object from binary data coming from
|
106
|
+
# network streams. Please use <tt>Net::DNS::Packet.parse</tt> instead.
|
107
|
+
def initialize(name, type = Net::DNS::A, cls = Net::DNS::IN)
|
108
|
+
@header = Net::DNS::Header.new(qdCount: 1)
|
109
|
+
@question = [Net::DNS::Question.new(name, type, cls)]
|
110
|
+
@answer = []
|
111
|
+
@authority = []
|
112
|
+
@additional = []
|
113
|
+
@logger = Logger.new $stdout
|
114
|
+
@logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
|
115
|
+
end
|
116
|
+
|
117
|
+
# Checks if the packet is a QUERY packet
|
118
|
+
def query?
|
119
|
+
@header.opCode == Net::DNS::Header::QUERY
|
120
|
+
end
|
121
|
+
|
122
|
+
# Returns the packet object in binary data, suitable
|
123
|
+
# for sending across a network stream.
|
124
|
+
#
|
125
|
+
# packet_data = packet.data
|
126
|
+
# puts "Packet is #{packet_data.size} bytes long"
|
127
|
+
#
|
128
|
+
def data
|
129
|
+
qdcount = ancount = nscount = arcount = 0
|
130
|
+
data = @header.data
|
131
|
+
headerlength = data.length
|
132
|
+
|
133
|
+
@question.each do |question|
|
134
|
+
data += question.data
|
135
|
+
qdcount += 1
|
136
|
+
end
|
137
|
+
@answer.each do |rr|
|
138
|
+
data += rr.data # (data.length)
|
139
|
+
ancount += 1
|
140
|
+
end
|
141
|
+
@authority.each do |rr|
|
142
|
+
data += rr.data # (data.length)
|
143
|
+
nscount += 1
|
144
|
+
end
|
145
|
+
@additional.each do |rr|
|
146
|
+
data += rr.data # (data.length)
|
147
|
+
arcount += 1
|
148
|
+
end
|
149
|
+
|
150
|
+
@header.qdCount = qdcount
|
151
|
+
@header.anCount = ancount
|
152
|
+
@header.nsCount = nscount
|
153
|
+
@header.arCount = arcount
|
154
|
+
|
155
|
+
@header.data + data[Net::DNS::HFIXEDSZ..data.size]
|
156
|
+
end
|
157
|
+
|
158
|
+
# Same as <tt>Net::DNS::Packet#data</tt>, but implements name compression
|
159
|
+
# (see RFC1025) for a considerable save of bytes.
|
160
|
+
#
|
161
|
+
# packet = Net::DNS::Packet.new("www.example.com")
|
162
|
+
# puts "Size normal is #{packet.data.size} bytes"
|
163
|
+
# puts "Size compressed is #{packet.data_comp.size} bytes"
|
164
|
+
#
|
165
|
+
def data_comp
|
166
|
+
offset = 0
|
167
|
+
compnames = {}
|
168
|
+
qdcount = ancount = nscount = arcount = 0
|
169
|
+
data = @header.data
|
170
|
+
headerlength = data.length
|
171
|
+
|
172
|
+
@question.each do |question|
|
173
|
+
str, offset, names = question.data
|
174
|
+
data += str
|
175
|
+
compnames.update(names)
|
176
|
+
qdcount += 1
|
177
|
+
end
|
178
|
+
|
179
|
+
@answer.each do |rr|
|
180
|
+
str, offset, names = rr.data(offset, compnames)
|
181
|
+
data += str
|
182
|
+
compnames.update(names)
|
183
|
+
ancount += 1
|
184
|
+
end
|
185
|
+
|
186
|
+
@authority.each do |rr|
|
187
|
+
str, offset, names = rr.data(offset, compnames)
|
188
|
+
data += str
|
189
|
+
compnames.update(names)
|
190
|
+
nscount += 1
|
191
|
+
end
|
192
|
+
|
193
|
+
@additional.each do |rr|
|
194
|
+
str, offset, names = rr.data(offset, compnames)
|
195
|
+
data += str
|
196
|
+
compnames.update(names)
|
197
|
+
arcount += 1
|
198
|
+
end
|
199
|
+
|
200
|
+
@header.qdCount = qdcount
|
201
|
+
@header.anCount = ancount
|
202
|
+
@header.nsCount = nscount
|
203
|
+
@header.arCount = arcount
|
204
|
+
|
205
|
+
@header.data + data[Net::DNS::HFIXEDSZ..data.size]
|
206
|
+
end
|
207
|
+
|
208
|
+
# Returns a string containing a human-readable representation
|
209
|
+
# of this <tt>Net::DNS::Packet</tt> instance.
|
210
|
+
def inspect
|
211
|
+
retval = ""
|
212
|
+
if (@answerfrom != "0.0.0.0:0") && @answerfrom
|
213
|
+
retval += ";; Answer received from #{@answerfrom} (#{@answersize} bytes)\n;;\n"
|
214
|
+
end
|
215
|
+
|
216
|
+
retval += ";; HEADER SECTION\n"
|
217
|
+
retval += @header.inspect
|
218
|
+
|
219
|
+
retval += "\n"
|
220
|
+
section = @header.opCode == "UPDATE" ? "ZONE" : "QUESTION"
|
221
|
+
retval += ";; #{section} SECTION (#{@header.qdCount} record#{@header.qdCount == 1 ? '' : 's'}):\n"
|
222
|
+
@question.each do |qr|
|
223
|
+
retval += ";; " + qr.inspect + "\n"
|
224
|
+
end
|
225
|
+
|
226
|
+
unless @answer.empty?
|
227
|
+
retval += "\n"
|
228
|
+
section = @header.opCode == "UPDATE" ? "PREREQUISITE" : "ANSWER"
|
229
|
+
retval += ";; #{section} SECTION (#{@header.anCount} record#{@header.anCount == 1 ? '' : 's'}):\n"
|
230
|
+
@answer.each do |rr|
|
231
|
+
retval += rr.inspect + "\n"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
unless @authority.empty?
|
236
|
+
retval += "\n"
|
237
|
+
section = @header.opCode == "UPDATE" ? "UPDATE" : "AUTHORITY"
|
238
|
+
retval += ";; #{section} SECTION (#{@header.nsCount} record#{@header.nsCount == 1 ? '' : 's'}):\n"
|
239
|
+
@authority.each do |rr|
|
240
|
+
retval += rr.inspect + "\n"
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
unless @additional.empty?
|
245
|
+
retval += "\n"
|
246
|
+
retval += ";; ADDITIONAL SECTION (#{@header.arCount} record#{@header.arCount == 1 ? '' : 's'}):\n"
|
247
|
+
@additional.each do |rr|
|
248
|
+
retval += rr.inspect + "\n"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
retval
|
253
|
+
end
|
254
|
+
alias to_s inspect
|
255
|
+
|
256
|
+
# Delegates to <tt>Net::DNS::Header#truncated?</tt>.
|
257
|
+
def truncated?
|
258
|
+
@header.truncated?
|
259
|
+
end
|
260
|
+
|
261
|
+
# Assigns a <tt>Net::DNS::Header</tt> <tt>object</tt>
|
262
|
+
# to this <tt>Net::DNS::Packet</tt> instance.
|
263
|
+
def header=(object)
|
264
|
+
if object.is_a? Net::DNS::Header
|
265
|
+
@header = object
|
266
|
+
else
|
267
|
+
raise ArgumentError, "Argument must be a Net::DNS::Header object"
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# Assigns a <tt>Net::DNS::Question</tt> <tt>object</tt>
|
272
|
+
# to this <tt>Net::DNS::Packet</tt> instance.
|
273
|
+
def question=(object)
|
274
|
+
case object
|
275
|
+
when Array
|
276
|
+
if object.all? { |x| x.is_a? Net::DNS::Question }
|
277
|
+
@question = object
|
278
|
+
else
|
279
|
+
raise ArgumentError, "Some of the elements is not an Net::DNS::Question object"
|
280
|
+
end
|
281
|
+
when Net::DNS::Question
|
282
|
+
@question = [object]
|
283
|
+
else
|
284
|
+
raise ArgumentError, "Invalid argument, not a Question object nor an array of objects"
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Assigns one or an array of <tt>Net::DNS::RR</tt> <tt>object</tt>s
|
289
|
+
# to the answer section of this <tt>Net::DNS::Packet</tt> instance.
|
290
|
+
def answer=(object)
|
291
|
+
case object
|
292
|
+
when Array
|
293
|
+
if object.all? { |x| x.is_a? Net::DNS::RR }
|
294
|
+
@answer = object
|
295
|
+
else
|
296
|
+
raise ArgumentError, "Some of the elements is not an Net::DNS::RR object"
|
297
|
+
end
|
298
|
+
when Net::DNS::RR
|
299
|
+
@answer = [object]
|
300
|
+
else
|
301
|
+
raise ArgumentError, "Invalid argument, not a RR object nor an array of objects"
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
# Assigns one or an array of <tt>Net::DNS::RR</tt> <tt>object</tt>s
|
306
|
+
# to the additional section of this <tt>Net::DNS::Packet</tt> instance.
|
307
|
+
def additional=(object)
|
308
|
+
case object
|
309
|
+
when Array
|
310
|
+
if object.all? { |x| x.is_a? Net::DNS::RR }
|
311
|
+
@additional = object
|
312
|
+
else
|
313
|
+
raise ArgumentError, "Some of the elements is not an Net::DNS::RR object"
|
314
|
+
end
|
315
|
+
when Net::DNS::RR
|
316
|
+
@additional = [object]
|
317
|
+
else
|
318
|
+
raise ArgumentError, "Invalid argument, not a RR object nor an array of objects"
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
# Assigns one or an array of <tt>Net::DNS::RR</tt> <tt>object</tt>s
|
323
|
+
# to the authority section of this <tt>Net::DNS::Packet</tt> instance.
|
324
|
+
def authority=(object)
|
325
|
+
case object
|
326
|
+
when Array
|
327
|
+
if object.all? { |x| x.is_a? Net::DNS::RR }
|
328
|
+
@authority = object
|
329
|
+
else
|
330
|
+
raise ArgumentError, "Some of the elements is not an Net::DNS::RR object"
|
331
|
+
end
|
332
|
+
when Net::DNS::RR
|
333
|
+
@authority = [object]
|
334
|
+
else
|
335
|
+
raise ArgumentError, "Invalid argument, not a RR object nor an array of objects"
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# Iterates every address in the +answer+ section
|
340
|
+
# of this <tt>Net::DNS::Packet</tt> instance.
|
341
|
+
#
|
342
|
+
# packet.each_address do |ip|
|
343
|
+
# ping ip.to_s
|
344
|
+
# end
|
345
|
+
#
|
346
|
+
# As you can see in the documentation for the <tt>Net::DNS::RR::A</tt> class,
|
347
|
+
# the address returned is an instance of <tt>IPAddr</tt> class.
|
348
|
+
def each_address(&block)
|
349
|
+
@answer.each do |elem|
|
350
|
+
next unless elem.class == Net::DNS::RR::A
|
351
|
+
|
352
|
+
yield elem.address
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
# Iterates every nameserver in the +answer+ section
|
357
|
+
# of this <tt>Net::DNS::Packet</tt> instance.
|
358
|
+
#
|
359
|
+
# packet.each_nameserver do |ns|
|
360
|
+
# puts "Nameserver found: #{ns}"
|
361
|
+
# end
|
362
|
+
#
|
363
|
+
def each_nameserver(&block)
|
364
|
+
@answer.each do |elem|
|
365
|
+
next unless elem.class == Net::DNS::RR::NS
|
366
|
+
|
367
|
+
yield elem.nsdname
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
# Iterates every exchange record in the +answer+ section
|
372
|
+
# of this <tt>Net::DNS::Packet</tt> instance.
|
373
|
+
#
|
374
|
+
# packet.each_mx do |pref,name|
|
375
|
+
# puts "Mail exchange #{name} has preference #{pref}"
|
376
|
+
# end
|
377
|
+
#
|
378
|
+
def each_mx(&block)
|
379
|
+
@answer.each do |elem|
|
380
|
+
next unless elem.class == Net::DNS::RR::MX
|
381
|
+
|
382
|
+
yield elem.preference, elem.exchange
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
# Iterates every canonical name in the +answer+ section
|
387
|
+
# of this <tt>Net::DNS::Packet</tt> instance.
|
388
|
+
#
|
389
|
+
# packet.each_cname do |cname|
|
390
|
+
# puts "Canonical name: #{cname}"
|
391
|
+
# end
|
392
|
+
#
|
393
|
+
def each_cname(&block)
|
394
|
+
@answer.each do |elem|
|
395
|
+
next unless elem.class == Net::DNS::RR::CNAME
|
396
|
+
|
397
|
+
yield elem.cname
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
# Iterates every pointer in the +answer+ section
|
402
|
+
# of this <tt>Net::DNS::Packet</tt> instance.
|
403
|
+
#
|
404
|
+
# packet.each_ptr do |ptr|
|
405
|
+
# puts "Pointer for resource: #{ptr}"
|
406
|
+
# end
|
407
|
+
#
|
408
|
+
def each_ptr(&block)
|
409
|
+
@answer.each do |elem|
|
410
|
+
next unless elem.class == Net::DNS::RR::PTR
|
411
|
+
|
412
|
+
yield elem.ptrdname
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
# Returns the packet size in bytes.
|
417
|
+
#
|
418
|
+
# Resolver("www.google.com") do |packet|
|
419
|
+
# puts packet.size + " bytes"}
|
420
|
+
# end
|
421
|
+
# # => 484 bytes
|
422
|
+
#
|
423
|
+
def size
|
424
|
+
data.size
|
425
|
+
end
|
426
|
+
|
427
|
+
# Checks whether the query returned a NXDOMAIN error,
|
428
|
+
# meaning the queried domain name doesn't exist.
|
429
|
+
#
|
430
|
+
# %w[a.com google.com ibm.com d.com].each do |domain|
|
431
|
+
# response = Net::DNS::Resolver.new.send(domain)
|
432
|
+
# puts "#{domain} doesn't exist" if response.nxdomain?
|
433
|
+
# end
|
434
|
+
# # => a.com doesn't exist
|
435
|
+
# # => d.com doesn't exist
|
436
|
+
#
|
437
|
+
def nxdomain?
|
438
|
+
header.rCode.code == Net::DNS::Header::RCode::NAME
|
439
|
+
end
|
440
|
+
|
441
|
+
# Creates a new instance of <tt>Net::DNS::Packet</tt> class from binary data,
|
442
|
+
# taken out from a network stream. For example:
|
443
|
+
#
|
444
|
+
# # udp_socket is an UDPSocket waiting for a response
|
445
|
+
# ans = udp_socket.recvfrom(1500)
|
446
|
+
# packet = Net::DNS::Packet::parse(ans)
|
447
|
+
#
|
448
|
+
# An optional +from+ argument can be used to specify the information
|
449
|
+
# of the sender. If data is passed as is from a Socket#recvfrom call,
|
450
|
+
# the method will accept it.
|
451
|
+
#
|
452
|
+
# Be sure that your network data is clean from any UDP/TCP header,
|
453
|
+
# especially when using RAW sockets.
|
454
|
+
#
|
455
|
+
def self.parse(*args)
|
456
|
+
o = allocate
|
457
|
+
o.send(:new_from_data, *args)
|
458
|
+
o
|
459
|
+
end
|
460
|
+
|
461
|
+
private
|
462
|
+
|
463
|
+
# New packet from binary data
|
464
|
+
def new_from_data(data, from = nil)
|
465
|
+
unless from
|
466
|
+
if data.is_a? Array
|
467
|
+
data, from = data
|
468
|
+
else
|
469
|
+
from = [0, 0, "0.0.0.0", "unknown"]
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
@answerfrom = from[2] + ":" + from[1].to_s
|
474
|
+
@answersize = data.size
|
475
|
+
@logger = Logger.new $stdout
|
476
|
+
@logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
|
477
|
+
|
478
|
+
#------------------------------------------------------------
|
479
|
+
# Header section
|
480
|
+
#------------------------------------------------------------
|
481
|
+
offset = Net::DNS::HFIXEDSZ
|
482
|
+
@header = Net::DNS::Header.parse(data[0..offset - 1])
|
483
|
+
|
484
|
+
@logger.debug ";; HEADER SECTION"
|
485
|
+
@logger.debug @header.inspect
|
486
|
+
|
487
|
+
#------------------------------------------------------------
|
488
|
+
# Question section
|
489
|
+
#------------------------------------------------------------
|
490
|
+
section = @header.opCode == "UPDATE" ? "ZONE" : "QUESTION"
|
491
|
+
@logger.debug ";; #{section} SECTION (#{@header.qdCount} record#{@header.qdCount == 1 ? '' : 's'})"
|
492
|
+
|
493
|
+
@question = []
|
494
|
+
@header.qdCount.times do
|
495
|
+
qobj, offset = parse_question(data, offset)
|
496
|
+
@question << qobj
|
497
|
+
@logger.debug ";; #{qobj.inspect}"
|
498
|
+
end
|
499
|
+
|
500
|
+
#------------------------------------------------------------
|
501
|
+
# Answer/prerequisite section
|
502
|
+
#------------------------------------------------------------
|
503
|
+
section = @header.opCode == "UPDATE" ? "PREREQUISITE" : "ANSWER"
|
504
|
+
@logger.debug ";; #{section} SECTION (#{@header.qdCount} record#{@header.qdCount == 1 ? '' : 's'})"
|
505
|
+
|
506
|
+
@answer = []
|
507
|
+
@header.anCount.times do
|
508
|
+
begin
|
509
|
+
rrobj, offset = Net::DNS::RR.parse_packet(data, offset)
|
510
|
+
@answer << rrobj
|
511
|
+
@logger.debug rrobj.inspect
|
512
|
+
rescue NameError => e
|
513
|
+
warn "Net::DNS unsupported record type: #{e.message}"
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
#------------------------------------------------------------
|
518
|
+
# Authority/update section
|
519
|
+
#------------------------------------------------------------
|
520
|
+
section = @header.opCode == "UPDATE" ? "UPDATE" : "AUTHORITY"
|
521
|
+
@logger.debug ";; #{section} SECTION (#{@header.nsCount} record#{@header.nsCount == 1 ? '' : 's'})"
|
522
|
+
|
523
|
+
@authority = []
|
524
|
+
@header.nsCount.times do
|
525
|
+
begin
|
526
|
+
rrobj, offset = Net::DNS::RR.parse_packet(data, offset)
|
527
|
+
@authority << rrobj
|
528
|
+
@logger.debug rrobj.inspect
|
529
|
+
rescue NameError => e
|
530
|
+
warn "Net::DNS unsupported record type: #{e.message}"
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
#------------------------------------------------------------
|
535
|
+
# Additional section
|
536
|
+
#------------------------------------------------------------
|
537
|
+
@logger.debug ";; ADDITIONAL SECTION (#{@header.arCount} record#{@header.arCount == 1 ? '' : 's'})"
|
538
|
+
|
539
|
+
@additional = []
|
540
|
+
@header.arCount.times do
|
541
|
+
begin
|
542
|
+
rrobj, offset = Net::DNS::RR.parse_packet(data, offset)
|
543
|
+
@additional << rrobj
|
544
|
+
@logger.debug rrobj.inspect
|
545
|
+
rescue NameError => e
|
546
|
+
warn "Net::DNS unsupported record type: #{e.message}"
|
547
|
+
end
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
# Parse question section
|
552
|
+
def parse_question(data, offset)
|
553
|
+
size = (dn_expand(data, offset)[1] - offset) + (2 * Net::DNS::INT16SZ)
|
554
|
+
[Net::DNS::Question.parse(data[offset, size]), offset + size]
|
555
|
+
rescue StandardError => e
|
556
|
+
raise PacketError, "Caught exception, maybe packet malformed => #{e.message}"
|
557
|
+
end
|
558
|
+
end
|
559
|
+
end
|
560
|
+
end
|