gitlab-net-dns 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.gitlab-ci.yml +20 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +3 -0
  6. data/.rubocop_defaults.yml +359 -0
  7. data/.rubocop_todo.yml +207 -0
  8. data/.travis.yml +13 -0
  9. data/CHANGELOG.md +113 -0
  10. data/Gemfile +9 -0
  11. data/LICENSE.txt +56 -0
  12. data/README.md +172 -0
  13. data/Rakefile +38 -0
  14. data/THANKS.rdoc +24 -0
  15. data/bin/console +14 -0
  16. data/demo/check_soa.rb +104 -0
  17. data/demo/threads.rb +18 -0
  18. data/gitlab-net-dns.gemspec +24 -0
  19. data/lib/net/dns.rb +104 -0
  20. data/lib/net/dns/header.rb +705 -0
  21. data/lib/net/dns/names.rb +120 -0
  22. data/lib/net/dns/packet.rb +560 -0
  23. data/lib/net/dns/question.rb +185 -0
  24. data/lib/net/dns/resolver.rb +1214 -0
  25. data/lib/net/dns/resolver/socks.rb +148 -0
  26. data/lib/net/dns/resolver/timeouts.rb +70 -0
  27. data/lib/net/dns/rr.rb +356 -0
  28. data/lib/net/dns/rr/a.rb +114 -0
  29. data/lib/net/dns/rr/aaaa.rb +94 -0
  30. data/lib/net/dns/rr/classes.rb +130 -0
  31. data/lib/net/dns/rr/cname.rb +74 -0
  32. data/lib/net/dns/rr/hinfo.rb +96 -0
  33. data/lib/net/dns/rr/mr.rb +70 -0
  34. data/lib/net/dns/rr/mx.rb +82 -0
  35. data/lib/net/dns/rr/ns.rb +70 -0
  36. data/lib/net/dns/rr/null.rb +50 -0
  37. data/lib/net/dns/rr/ptr.rb +77 -0
  38. data/lib/net/dns/rr/soa.rb +75 -0
  39. data/lib/net/dns/rr/srv.rb +41 -0
  40. data/lib/net/dns/rr/txt.rb +58 -0
  41. data/lib/net/dns/rr/types.rb +191 -0
  42. data/lib/net/dns/version.rb +8 -0
  43. data/spec/fixtures/resolv.conf +4 -0
  44. data/spec/spec_helper.rb +14 -0
  45. data/spec/unit/resolver/dns_timeout_spec.rb +36 -0
  46. data/spec/unit/resolver/tcp_timeout_spec.rb +46 -0
  47. data/spec/unit/resolver/udp_timeout_spec.rb +46 -0
  48. data/test/test_helper.rb +13 -0
  49. data/test/unit/header_test.rb +164 -0
  50. data/test/unit/names_test.rb +21 -0
  51. data/test/unit/packet_test.rb +47 -0
  52. data/test/unit/question_test.rb +81 -0
  53. data/test/unit/resolver_test.rb +114 -0
  54. data/test/unit/rr/a_test.rb +106 -0
  55. data/test/unit/rr/aaaa_test.rb +102 -0
  56. data/test/unit/rr/classes_test.rb +83 -0
  57. data/test/unit/rr/cname_test.rb +90 -0
  58. data/test/unit/rr/hinfo_test.rb +111 -0
  59. data/test/unit/rr/mr_test.rb +99 -0
  60. data/test/unit/rr/mx_test.rb +106 -0
  61. data/test/unit/rr/ns_test.rb +80 -0
  62. data/test/unit/rr/types_test.rb +71 -0
  63. data/test/unit/rr_test.rb +127 -0
  64. 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