packetgen 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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