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