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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/lib/packetgen/header/arp.rb +54 -125
  4. data/lib/packetgen/header/base.rb +175 -0
  5. data/lib/packetgen/header/dns/name.rb +110 -0
  6. data/lib/packetgen/header/dns/opt.rb +137 -0
  7. data/lib/packetgen/header/dns/option.rb +17 -0
  8. data/lib/packetgen/header/dns/qdsection.rb +39 -0
  9. data/lib/packetgen/header/dns/question.rb +129 -0
  10. data/lib/packetgen/header/dns/rr.rb +89 -0
  11. data/lib/packetgen/header/dns/rrsection.rb +72 -0
  12. data/lib/packetgen/header/dns.rb +276 -0
  13. data/lib/packetgen/header/esp.rb +38 -70
  14. data/lib/packetgen/header/eth.rb +35 -106
  15. data/lib/packetgen/header/icmp.rb +19 -70
  16. data/lib/packetgen/header/icmpv6.rb +3 -3
  17. data/lib/packetgen/header/ip.rb +54 -210
  18. data/lib/packetgen/header/ipv6.rb +73 -164
  19. data/lib/packetgen/header/tcp/option.rb +34 -50
  20. data/lib/packetgen/header/tcp/options.rb +19 -20
  21. data/lib/packetgen/header/tcp.rb +66 -129
  22. data/lib/packetgen/header/udp.rb +31 -88
  23. data/lib/packetgen/header.rb +5 -10
  24. data/lib/packetgen/inspect.rb +5 -4
  25. data/lib/packetgen/packet.rb +74 -57
  26. data/lib/packetgen/pcapng/block.rb +49 -7
  27. data/lib/packetgen/pcapng/epb.rb +36 -34
  28. data/lib/packetgen/pcapng/file.rb +24 -8
  29. data/lib/packetgen/pcapng/idb.rb +28 -33
  30. data/lib/packetgen/pcapng/shb.rb +35 -39
  31. data/lib/packetgen/pcapng/spb.rb +18 -27
  32. data/lib/packetgen/pcapng/unknown_block.rb +11 -21
  33. data/lib/packetgen/pcapng.rb +9 -7
  34. data/lib/packetgen/types/array.rb +56 -0
  35. data/lib/packetgen/types/fields.rb +325 -0
  36. data/lib/packetgen/types/int.rb +164 -0
  37. data/lib/packetgen/types/int_string.rb +69 -0
  38. data/lib/packetgen/types/string.rb +36 -0
  39. data/lib/packetgen/types/tlv.rb +41 -0
  40. data/lib/packetgen/types.rb +13 -0
  41. data/lib/packetgen/version.rb +1 -1
  42. data/lib/packetgen.rb +1 -1
  43. metadata +19 -6
  44. data/lib/packetgen/header/header_class_methods.rb +0 -106
  45. data/lib/packetgen/header/header_methods.rb +0 -73
  46. 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