CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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