packetgen 1.2.0 → 1.3.0
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.
- 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
|