net-dns 0.1

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