packetgen 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/lib/packetgen/header/arp.rb +54 -125
- data/lib/packetgen/header/base.rb +175 -0
- data/lib/packetgen/header/dns/name.rb +110 -0
- data/lib/packetgen/header/dns/opt.rb +137 -0
- data/lib/packetgen/header/dns/option.rb +17 -0
- data/lib/packetgen/header/dns/qdsection.rb +39 -0
- data/lib/packetgen/header/dns/question.rb +129 -0
- data/lib/packetgen/header/dns/rr.rb +89 -0
- data/lib/packetgen/header/dns/rrsection.rb +72 -0
- data/lib/packetgen/header/dns.rb +276 -0
- data/lib/packetgen/header/esp.rb +38 -70
- data/lib/packetgen/header/eth.rb +35 -106
- data/lib/packetgen/header/icmp.rb +19 -70
- data/lib/packetgen/header/icmpv6.rb +3 -3
- data/lib/packetgen/header/ip.rb +54 -210
- data/lib/packetgen/header/ipv6.rb +73 -164
- data/lib/packetgen/header/tcp/option.rb +34 -50
- data/lib/packetgen/header/tcp/options.rb +19 -20
- data/lib/packetgen/header/tcp.rb +66 -129
- data/lib/packetgen/header/udp.rb +31 -88
- data/lib/packetgen/header.rb +5 -10
- data/lib/packetgen/inspect.rb +5 -4
- data/lib/packetgen/packet.rb +74 -57
- data/lib/packetgen/pcapng/block.rb +49 -7
- data/lib/packetgen/pcapng/epb.rb +36 -34
- data/lib/packetgen/pcapng/file.rb +24 -8
- data/lib/packetgen/pcapng/idb.rb +28 -33
- data/lib/packetgen/pcapng/shb.rb +35 -39
- data/lib/packetgen/pcapng/spb.rb +18 -27
- data/lib/packetgen/pcapng/unknown_block.rb +11 -21
- data/lib/packetgen/pcapng.rb +9 -7
- data/lib/packetgen/types/array.rb +56 -0
- data/lib/packetgen/types/fields.rb +325 -0
- data/lib/packetgen/types/int.rb +164 -0
- data/lib/packetgen/types/int_string.rb +69 -0
- data/lib/packetgen/types/string.rb +36 -0
- data/lib/packetgen/types/tlv.rb +41 -0
- data/lib/packetgen/types.rb +13 -0
- data/lib/packetgen/version.rb +1 -1
- data/lib/packetgen.rb +1 -1
- metadata +19 -6
- data/lib/packetgen/header/header_class_methods.rb +0 -106
- data/lib/packetgen/header/header_methods.rb +0 -73
- data/lib/packetgen/structfu.rb +0 -363
@@ -0,0 +1,39 @@
|
|
1
|
+
module PacketGen
|
2
|
+
module Header
|
3
|
+
class DNS
|
4
|
+
|
5
|
+
# Define a DNS Question Section
|
6
|
+
# @author Sylvain Daubert
|
7
|
+
class QDSection < RRSection
|
8
|
+
# @!method push(q)
|
9
|
+
# Add a question to this section without incrementing associated counter
|
10
|
+
# @param [Question,Hash] q
|
11
|
+
# @return [QDSection] self
|
12
|
+
# @!method <<(q)
|
13
|
+
# Add a question to this section. Increment associated counter
|
14
|
+
# @param [Question,Hash] q
|
15
|
+
# @return [QDSection] self
|
16
|
+
# @!method delete(q)
|
17
|
+
# Delete a question
|
18
|
+
# @param [Question] q
|
19
|
+
# @return [Question]
|
20
|
+
|
21
|
+
# Read Question section from a string
|
22
|
+
# @param [String] str binary string
|
23
|
+
# @return [QDSection] self
|
24
|
+
def read(str)
|
25
|
+
clear
|
26
|
+
return self if str.nil?
|
27
|
+
force_binary str
|
28
|
+
while str.length > 0 and self.size < @counter.to_i
|
29
|
+
question = Question.new(@dns).read(str)
|
30
|
+
str.slice!(0, question.sz)
|
31
|
+
self.push question
|
32
|
+
end
|
33
|
+
self
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module PacketGen
|
2
|
+
module Header
|
3
|
+
class DNS
|
4
|
+
|
5
|
+
# DNS Question
|
6
|
+
# @author Sylvain Daubert
|
7
|
+
class Question < Base
|
8
|
+
|
9
|
+
# @!attribute name
|
10
|
+
# Question domain name
|
11
|
+
# @return [String]
|
12
|
+
define_field :name, Name, default: '.'
|
13
|
+
# @!attribute type
|
14
|
+
# 16-bit question type
|
15
|
+
# @return [Integer]
|
16
|
+
define_field :type, Types::Int16, default: 1
|
17
|
+
# @!attribute rrclass
|
18
|
+
# 16-bit question class
|
19
|
+
# @return [Integer]
|
20
|
+
define_field :rrclass, Types::Int16, default: 1
|
21
|
+
|
22
|
+
# Ressource Record types
|
23
|
+
TYPES = {
|
24
|
+
'A' => 1,
|
25
|
+
'NS' => 2,
|
26
|
+
'MD' => 3,
|
27
|
+
'MF' => 4,
|
28
|
+
'CNAME' => 5,
|
29
|
+
'SOA' => 6,
|
30
|
+
'MB' => 7,
|
31
|
+
'MG' => 8,
|
32
|
+
'MR' => 9,
|
33
|
+
'NULL' => 10,
|
34
|
+
'WKS' => 11,
|
35
|
+
'PTR' => 12,
|
36
|
+
'HINFO' => 13,
|
37
|
+
'MINFO' => 14,
|
38
|
+
'MX' => 15,
|
39
|
+
'TXT' => 16,
|
40
|
+
'AAAA' => 28,
|
41
|
+
'NAPTR' => 35,
|
42
|
+
'KX' => 36,
|
43
|
+
'CERT' => 37,
|
44
|
+
'OPT' => 41,
|
45
|
+
'DS' => 43,
|
46
|
+
'RRSIG' => 46,
|
47
|
+
'NSEC' => 47,
|
48
|
+
'DNSKEY' => 48,
|
49
|
+
'TKEY' => 249,
|
50
|
+
'TSIG' => 250,
|
51
|
+
'*' => 255
|
52
|
+
}
|
53
|
+
|
54
|
+
# Ressource Record classes
|
55
|
+
CLASSES = {
|
56
|
+
'IN' => 1,
|
57
|
+
'CH' => 3,
|
58
|
+
'HS' => 4,
|
59
|
+
'NONE' => 254,
|
60
|
+
'*' => 255
|
61
|
+
}
|
62
|
+
|
63
|
+
# @param [DNS] dns
|
64
|
+
# @param [Hash] options
|
65
|
+
# @option options [String] :name domain as a dotted string
|
66
|
+
# @option options [Integer,String] :type see {TYPES}. Default to +'A'+
|
67
|
+
# @option options [Integer,String] :rrclass see {CLASSES}. Default to +'IN'+
|
68
|
+
def initialize(dns, options={})
|
69
|
+
super(options)
|
70
|
+
self[:name].dns = dns
|
71
|
+
self.type = options[:type] if options[:type]
|
72
|
+
self.rrclass = options[:rrclass] if options[:rrclass]
|
73
|
+
end
|
74
|
+
|
75
|
+
# Setter for type
|
76
|
+
# @param [Integer] val
|
77
|
+
# @return [Integer,String]
|
78
|
+
def type=(val)
|
79
|
+
v = case val
|
80
|
+
when String
|
81
|
+
self.class::TYPES[val.upcase]
|
82
|
+
else
|
83
|
+
val
|
84
|
+
end
|
85
|
+
raise ArgumentError, "unknown type #{val.inspect}" unless v
|
86
|
+
self[:type].read v
|
87
|
+
end
|
88
|
+
|
89
|
+
# Setter for class
|
90
|
+
# @param [Integer] val
|
91
|
+
# @return [Integer,String]
|
92
|
+
def rrclass=(val)
|
93
|
+
v = case val
|
94
|
+
when String
|
95
|
+
self.class::CLASSES[val.upcase]
|
96
|
+
else
|
97
|
+
val
|
98
|
+
end
|
99
|
+
raise ArgumentError, "unknown class #{val.inspect}" unless v
|
100
|
+
self[:rrclass].read v
|
101
|
+
end
|
102
|
+
|
103
|
+
# Check type
|
104
|
+
# @param [String] type name
|
105
|
+
# @return [Boolean]
|
106
|
+
def has_type?(type)
|
107
|
+
self.class::TYPES[type] == self.type
|
108
|
+
end
|
109
|
+
|
110
|
+
# Get human readable type
|
111
|
+
# @return [String]
|
112
|
+
def human_type
|
113
|
+
self.class::TYPES.key(type) || "0x%04x" % type
|
114
|
+
end
|
115
|
+
|
116
|
+
# Get human readable class
|
117
|
+
# @return [String]
|
118
|
+
def human_rrclass
|
119
|
+
self.class::CLASSES.key(self.rrclass) || "0x%04x" % self.rrclass
|
120
|
+
end
|
121
|
+
|
122
|
+
# @return [String]
|
123
|
+
def to_human
|
124
|
+
"#{human_type} #{human_rrclass} #{name}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module PacketGen
|
2
|
+
module Header
|
3
|
+
class DNS
|
4
|
+
|
5
|
+
# DNS Ressource Record
|
6
|
+
# @author Sylvain Daubert
|
7
|
+
class RR < Question
|
8
|
+
|
9
|
+
# @!attribute ttl
|
10
|
+
# 32-bit time to live
|
11
|
+
# @return [Integer]
|
12
|
+
define_field :ttl, Types::Int32
|
13
|
+
# @!attribute rdlength
|
14
|
+
# 16-bit {#rdata} length
|
15
|
+
# @return [Integer]
|
16
|
+
define_field :rdlength, Types::Int16
|
17
|
+
# @!attribute rdata
|
18
|
+
# @return [Types::String]
|
19
|
+
define_field :rdata, Types::String,
|
20
|
+
builder: ->(rr) { Types::String.new('', length_from: rr[:rdlength]) }
|
21
|
+
|
22
|
+
# @param [DNS] dns
|
23
|
+
# @param [Hash] options
|
24
|
+
# @option options [String] :name domain as a dotted string
|
25
|
+
# @option options [Integer,String] :type see {TYPES}. Default to +'A'+
|
26
|
+
# @option options [Integer,String] :rrclass see {CLASSES}. Default to +'IN'+
|
27
|
+
# @option options [Integer] :ttl
|
28
|
+
# @option options [Integer] :rdlength if not provided, automatically set
|
29
|
+
# from +:rdata+ length
|
30
|
+
# @option options [String] :rdata
|
31
|
+
def initialize(dns, options={})
|
32
|
+
super
|
33
|
+
if options[:rdata] and options[:rdlength].nil?
|
34
|
+
self.rdata = options[:rdata]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Set rdata and rdlength from +data+
|
39
|
+
# @param [String] data
|
40
|
+
# @return [void]
|
41
|
+
def rdata=(data)
|
42
|
+
self[:rdlength].read data.size
|
43
|
+
self[:rdata].read data
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get human readable rdata
|
47
|
+
# @return [String]
|
48
|
+
def human_rdata
|
49
|
+
str = self[:rdata].inspect
|
50
|
+
|
51
|
+
if self.rrclass == CLASSES['IN']
|
52
|
+
case type
|
53
|
+
when TYPES['A'], TYPES['AAAA']
|
54
|
+
str = IPAddr.new_ntoh(self[:rdata]).to_s
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
name = Name.new
|
59
|
+
name.dns = @dns
|
60
|
+
case type
|
61
|
+
when TYPES['NS'], TYPES['PTR'], TYPES['CNAME']
|
62
|
+
str = name.read(self[:rdata]).to_human
|
63
|
+
when TYPES['SOA']
|
64
|
+
mname = name.read(self[:rdata]).dup
|
65
|
+
rname = name.read(self[:rdata][mname.sz..-1])
|
66
|
+
serial = Types::Int32.new.read(self[:rdata][mname.sz+rname.sz, 4])
|
67
|
+
refresh = Types::Int32.new.read(self[:rdata][mname.sz+rname.sz+4, 4])
|
68
|
+
retryi = Types::Int32.new.read(self[:rdata][mname.sz+rname.sz+8, 4])
|
69
|
+
expire = Types::Int32.new.read(self[:rdata][mname.sz+rname.sz+12, 4])
|
70
|
+
minimum = Types::Int32.new.read(self[:rdata][mname.sz+rname.sz+16, 4])
|
71
|
+
str = "#{mname.to_human} #{rname.to_human} #{serial.to_i} #{refresh.to_i} " \
|
72
|
+
"#{retryi.to_i} #{expire.to_i} #{minimum.to_i}"
|
73
|
+
when TYPES['MX']
|
74
|
+
pref = Types::Int16.new.read(self[:rdata][0, 2])
|
75
|
+
exchange = name.read(self[:rdata][2..-1]).to_human
|
76
|
+
str = '%u %s' % [pref.to_i, exchange]
|
77
|
+
end
|
78
|
+
|
79
|
+
str
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [String]
|
83
|
+
def to_human
|
84
|
+
"#{human_type} #{human_rrclass} #{name} TTL #{ttl} #{human_rdata}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module PacketGen
|
2
|
+
module Header
|
3
|
+
class DNS
|
4
|
+
|
5
|
+
# Define a DNS Ressource Record Section
|
6
|
+
# @author Sylvain Daubert
|
7
|
+
class RRSection < Types::Array
|
8
|
+
|
9
|
+
# @api private
|
10
|
+
# @param [DNS] dns
|
11
|
+
# @param [Types::Int] counter
|
12
|
+
def initialize(dns, counter)
|
13
|
+
@dns = dns
|
14
|
+
@counter = counter
|
15
|
+
end
|
16
|
+
|
17
|
+
# Read RR section from a string
|
18
|
+
# @param [String] str binary string
|
19
|
+
# @return [RRSection] self
|
20
|
+
def read(str)
|
21
|
+
clear
|
22
|
+
return self if str.nil?
|
23
|
+
force_binary str
|
24
|
+
while str.length > 0 and self.size < @counter.to_i
|
25
|
+
rr = RR.new(@dns).read(str)
|
26
|
+
rr = OPT.new(@dns).read(str) if rr.has_type?('OPT')
|
27
|
+
str.slice!(0, rr.sz)
|
28
|
+
self.push rr
|
29
|
+
end
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
# Add a ressource record to this section. Increment associated counter
|
34
|
+
# @param [RR,Hash] rr
|
35
|
+
# @return [RRSection] self
|
36
|
+
def <<(rr)
|
37
|
+
push rr
|
38
|
+
@counter.read(@counter.to_i + 1)
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
# Delete a ressource
|
43
|
+
# @param [RR] rr
|
44
|
+
# @return [RR]
|
45
|
+
def delete(rr)
|
46
|
+
obj = super
|
47
|
+
@counter.read(@counter.to_i - 1) if obj
|
48
|
+
obj
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def record_from_hash(hsh)
|
54
|
+
if hsh.has_key? :rtype
|
55
|
+
case hsh.delete(:rtype)
|
56
|
+
when 'Question'
|
57
|
+
Question.new(@dns, hsh)
|
58
|
+
when 'OPT'
|
59
|
+
OPT.new(@dns, hsh)
|
60
|
+
when 'RR'
|
61
|
+
RR.new(@dns, hsh)
|
62
|
+
else
|
63
|
+
raise TypeError, 'rtype should be a Question, OPT or RR'
|
64
|
+
end
|
65
|
+
else
|
66
|
+
hsh
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,276 @@
|
|
1
|
+
module PacketGen
|
2
|
+
module Header
|
3
|
+
|
4
|
+
# DNS: Domain Name Service
|
5
|
+
#
|
6
|
+
# A DNS packet consists of a header:
|
7
|
+
# 1 1 1 1 1 1
|
8
|
+
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
9
|
+
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
10
|
+
# | ID |
|
11
|
+
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
12
|
+
# |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
|
13
|
+
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
14
|
+
# | QDCOUNT |
|
15
|
+
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
16
|
+
# | ANCOUNT |
|
17
|
+
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
18
|
+
# | NSCOUNT |
|
19
|
+
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
20
|
+
# | ARCOUNT |
|
21
|
+
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
22
|
+
# A DNS packet also contains up to 4 sections:
|
23
|
+
# * {#qd}, question section,
|
24
|
+
# * {#an}, answer section,
|
25
|
+
# * {#ns}, authoritary section,
|
26
|
+
# * {#ar}, additional information section.
|
27
|
+
#
|
28
|
+
# == Create a DNS header
|
29
|
+
# # standalone
|
30
|
+
# dns = PacketGen::Header::DNS.new
|
31
|
+
# # in a IP packet
|
32
|
+
# pkt = PacketGen.gen('IP').add('DNS')
|
33
|
+
# # access to DNS header
|
34
|
+
# pkt.dns # => PacketGen::Header::DNS
|
35
|
+
#
|
36
|
+
# == DNS attributes
|
37
|
+
# dns.id = 0x1234
|
38
|
+
# dns.qr = false
|
39
|
+
# dns.opcode = 0xe
|
40
|
+
# dns.opcode = 'query'
|
41
|
+
# dns.aa = dns.tc = dns.rd = dns.ra = false
|
42
|
+
# dns.rcode = 0xa
|
43
|
+
# dns.rcode = 'refused'
|
44
|
+
# dns.qdcount = 123
|
45
|
+
# dns.ancount = 0x1234
|
46
|
+
# dns.nscount = 1
|
47
|
+
# dns.arcount = 0
|
48
|
+
# One can also access to DNS sections:
|
49
|
+
# dns.qd # => PacketGen::Header::DNS::QDSection
|
50
|
+
# dns.an # => PacketGen::Header::DNS::RRSection
|
51
|
+
# dns.ns # => PacketGen::Header::DNS::RRSection
|
52
|
+
# dns.ar # => PacketGen::Header::DNS::RRSection
|
53
|
+
#
|
54
|
+
# == Add a question to DNS question section
|
55
|
+
# Adding a {Question} with {QDSection#<<} automagically increments {#qdcount}.
|
56
|
+
# To not modify +qdcount+, use {QDSection#push}.
|
57
|
+
# # add a question about example.net IP address. Increment qdcount
|
58
|
+
# dns.qd << PacketGen::Header::DNS::Question.new(dns, name: 'example.net')
|
59
|
+
# # or
|
60
|
+
# dns.qd << { rtype: 'Question', name: 'example.net' }
|
61
|
+
# # add a question about example.net IPv6 address. Dot not modify qdcount
|
62
|
+
# dns.qd.push PacketGen::Header::DNS::Question.new(dns, name: 'example.net', type: 'AAAA')
|
63
|
+
# # or
|
64
|
+
# dns.qd.push({ rtype: 'Question', name: 'example.net', type: 'AAAA' })
|
65
|
+
#
|
66
|
+
# == Add a ressource record to a DNS section
|
67
|
+
# Adding a {RR} with {RRSection#<<} automagically increments section counter.
|
68
|
+
# To not modify it, use {RRSection#push}
|
69
|
+
# # add a RR to answer section. Increment ancount
|
70
|
+
# dns.an << PacketGen::Header::DNS::RR.new(dns, name: 'example.net', rdata: IPAddr.new('1.2.3.4').hton)
|
71
|
+
# # or
|
72
|
+
# dns.an << { rtype: 'RR', name: 'example.net', rdata: IPAddr.new('1.2.3.4').hton }
|
73
|
+
# # add a RR to NS section. Dot not modify nscount
|
74
|
+
# rdata = PacketGen::Header::DNS::Name.new(dns).parse('dns.net')
|
75
|
+
# dns.ns.push PacketGen::Header::DNS::RR.new(dns, name: 'example.net', type: 'NS', rdata: rdata)
|
76
|
+
# # or
|
77
|
+
# dns.ns.push(rtype: 'RR', name: 'example.net', type: 'NS', rdata: rdata)
|
78
|
+
#
|
79
|
+
# == Extended DNS EDNS(0)
|
80
|
+
# # Add an OPT to ar section
|
81
|
+
# dns.ar << PacketGen::Header::DNS::OPT.new(dns, udp_size: 4096, ext_rcode: 43)
|
82
|
+
# # or
|
83
|
+
# dns.ar << { rtype: 'OPT', udp_size: 4096, ext_rcode: 43 }
|
84
|
+
# # add an option to OPT record
|
85
|
+
# dns.ar.last.options << PacketGen::Header::DNS::Option.new(code: 48, length: 2, data: "12")
|
86
|
+
# @author Sylvain Daubert
|
87
|
+
class DNS < Base
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
require_relative 'dns/rrsection'
|
93
|
+
require_relative 'dns/qdsection'
|
94
|
+
require_relative 'dns/name'
|
95
|
+
require_relative 'dns/question'
|
96
|
+
require_relative 'dns/rr'
|
97
|
+
require_relative 'dns/opt'
|
98
|
+
|
99
|
+
module PacketGen
|
100
|
+
module Header
|
101
|
+
class DNS
|
102
|
+
# Port number for DNS over UDP
|
103
|
+
UDP_PORT = 53
|
104
|
+
# Port number for DNS over TCP
|
105
|
+
TCP_PORT = UDP_PORT
|
106
|
+
|
107
|
+
# DNS opcodes
|
108
|
+
OPCODES = {
|
109
|
+
'query' => 0,
|
110
|
+
'iquery' => 1,
|
111
|
+
'status' => 2,
|
112
|
+
'notify' => 4,
|
113
|
+
'update' => 5
|
114
|
+
}
|
115
|
+
|
116
|
+
# DNS Response codes
|
117
|
+
RCODES = {
|
118
|
+
'ok' => 0,
|
119
|
+
'no-error' => 0,
|
120
|
+
'format-error' => 1,
|
121
|
+
'server-failure' => 2,
|
122
|
+
'name-error' => 3,
|
123
|
+
'not-implemented' => 4,
|
124
|
+
'refused' => 5
|
125
|
+
}
|
126
|
+
|
127
|
+
# @!attribute id
|
128
|
+
# @return [Integer]
|
129
|
+
define_field :id, Types::Int16
|
130
|
+
# @!attribute u16
|
131
|
+
# @return [Integer]
|
132
|
+
define_field :u16, Types::Int16
|
133
|
+
# @!attribute qdcount
|
134
|
+
# @return [Integer]
|
135
|
+
define_field :qdcount, Types::Int16
|
136
|
+
# @!attribute ancount
|
137
|
+
# @return [Integer]
|
138
|
+
define_field :ancount, Types::Int16
|
139
|
+
# @!attribute nscount
|
140
|
+
# @return [Integer]
|
141
|
+
define_field :nscount, Types::Int16
|
142
|
+
# @!attribute arcount
|
143
|
+
# @return [Integer]
|
144
|
+
define_field :arcount, Types::Int16
|
145
|
+
# @!attribute qd
|
146
|
+
# @return [QDSection]
|
147
|
+
define_field :qd, QDSection, builder: ->(dns) { QDSection.new(dns, dns[:qdcount]) }
|
148
|
+
# @!attribute an
|
149
|
+
# @return [RRSection]
|
150
|
+
define_field :an, RRSection, builder: ->(dns) { RRSection.new(dns, dns[:ancount]) }
|
151
|
+
# @!attribute ns
|
152
|
+
# @return [RRSection]
|
153
|
+
define_field :ns, RRSection, builder: ->(dns) { RRSection.new(dns, dns[:nscount]) }
|
154
|
+
# @!attribute ar
|
155
|
+
# @return [RRSection]
|
156
|
+
define_field :ar, RRSection, builder: ->(dns) { RRSection.new(dns, dns[:arcount]) }
|
157
|
+
|
158
|
+
# @!attribute qr
|
159
|
+
# @return [Boolean] query (+false+) or response (+true+)
|
160
|
+
# @!attribute opcode
|
161
|
+
# @return [Integer] Kind of query. See {OPCODES}.
|
162
|
+
# @!attribute aa
|
163
|
+
# @return [Boolean] Authoritative answer
|
164
|
+
# @!attribute tc
|
165
|
+
# @return [Boolean] Truncation
|
166
|
+
# @!attribute rd
|
167
|
+
# @return [Boolean] Recursion Desired
|
168
|
+
# @!attribute ra
|
169
|
+
# @return [Boolean] Recursion Available
|
170
|
+
# @!attribute ad
|
171
|
+
# @return [Boolean] Authentic Data
|
172
|
+
# @!attribute cd
|
173
|
+
# @return [Boolean] Checking Disabled
|
174
|
+
# @!attribute rcode
|
175
|
+
# @return [Integer] Response code. See {RCODES}.
|
176
|
+
define_bit_fields_on :u16, :qr, :opcode, 4, :aa, :tc, :rd, :ra, :z,
|
177
|
+
:ad, :cd, :rcode, 4
|
178
|
+
|
179
|
+
# Read DNS header and sections from a string
|
180
|
+
# @param [String] str binary string
|
181
|
+
# @return [self]
|
182
|
+
def read(str)
|
183
|
+
return self if str.nil?
|
184
|
+
force_binary str
|
185
|
+
self[:id].read str[0, 2]
|
186
|
+
self[:u16].read str[2, 2]
|
187
|
+
self[:qdcount].read str[4, 2]
|
188
|
+
self[:ancount].read str[6, 2]
|
189
|
+
self[:nscount].read str[8, 2]
|
190
|
+
self[:arcount].read str[10, 2]
|
191
|
+
self[:qd].read str[12..-1] if self.qdcount > 0
|
192
|
+
start = 12 + self[:qd].sz
|
193
|
+
self[:an].read str[start..-1] if self.ancount > 0
|
194
|
+
start += self[:an].sz
|
195
|
+
self[:ns].read str[start..-1] if self.nscount > 0
|
196
|
+
start += self[:ns].sz
|
197
|
+
self[:ar].read str[start..-1] if self.arcount > 0
|
198
|
+
self
|
199
|
+
end
|
200
|
+
|
201
|
+
# Set opcode
|
202
|
+
# @param [Integer,String] value
|
203
|
+
# @return [Integer]
|
204
|
+
def opcode=(value)
|
205
|
+
intg = case value
|
206
|
+
when Integer
|
207
|
+
value
|
208
|
+
else
|
209
|
+
OPCODES[value.to_s]
|
210
|
+
end
|
211
|
+
raise ArgumentError, "unknown opcode #{value.inspect}" unless intg
|
212
|
+
self.u16 &= 0x87ff
|
213
|
+
self.u16 |= (intg & 0xf) << 11
|
214
|
+
end
|
215
|
+
|
216
|
+
# Set rcode
|
217
|
+
# @param [Integer,String] value
|
218
|
+
# @return [Integer]
|
219
|
+
def rcode=(value)
|
220
|
+
intg = case value
|
221
|
+
when Integer
|
222
|
+
value
|
223
|
+
else
|
224
|
+
RCODES[value]
|
225
|
+
end
|
226
|
+
raise ArgumentError, "unknown rcode #{value.inspect}" unless intg
|
227
|
+
self.u16 &= 0xfff0
|
228
|
+
self.u16 |= intg & 0xf
|
229
|
+
end
|
230
|
+
|
231
|
+
# Is message a response
|
232
|
+
# @return [Boolean]
|
233
|
+
def response?
|
234
|
+
qr?
|
235
|
+
end
|
236
|
+
|
237
|
+
# Is message a query
|
238
|
+
# @return [Boolean]
|
239
|
+
def query?
|
240
|
+
!qr?
|
241
|
+
end
|
242
|
+
|
243
|
+
# @return [String]
|
244
|
+
def inspect
|
245
|
+
str = Inspect.dashed_line(self.class, 2)
|
246
|
+
to_h.each do |attr, value|
|
247
|
+
if attr == :u16
|
248
|
+
flags = [:qr, :aa, :tc, :rd, :ra].select! { |attr| send "#{attr}?" }.
|
249
|
+
map(&:to_s).join(',')
|
250
|
+
str << Inspect.shift_level(2)
|
251
|
+
str << Inspect::INSPECT_FMT_ATTR % ['Flags', 'flags', flags]
|
252
|
+
opcode = '%-10s (%u)' % [OPCODES.key(self.opcode), self.opcode]
|
253
|
+
str << Inspect.shift_level(2)
|
254
|
+
str << Inspect::INSPECT_FMT_ATTR % ['Integer', 'opcode', opcode]
|
255
|
+
rcode = '%-10s (%u)' % [RCODES.key(self.rcode), self.rcode]
|
256
|
+
str << Inspect.shift_level(2)
|
257
|
+
str << Inspect::INSPECT_FMT_ATTR % ['Integer', 'rcode', rcode]
|
258
|
+
else
|
259
|
+
str << Inspect.inspect_attribute(attr, value, 2)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
str
|
263
|
+
end
|
264
|
+
|
265
|
+
private
|
266
|
+
|
267
|
+
def boolean2integer(bool)
|
268
|
+
bool ? 1 : 0
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
self.add_class DNS
|
273
|
+
UDP.bind_header DNS, dport: DNS::UDP_PORT, sport: DNS::UDP_PORT
|
274
|
+
TCP.bind_header DNS, dport: DNS::TCP_PORT, sport: DNS::TCP_PORT
|
275
|
+
end
|
276
|
+
end
|