nesser 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/README.md +28 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/nesser.rb +149 -0
- data/lib/nesser/dns_exception.rb +18 -0
- data/lib/nesser/packets/answer.rb +79 -0
- data/lib/nesser/packets/constants.rb +111 -0
- data/lib/nesser/packets/packer.rb +97 -0
- data/lib/nesser/packets/packet.rb +186 -0
- data/lib/nesser/packets/question.rb +43 -0
- data/lib/nesser/packets/rr_types.rb +290 -0
- data/lib/nesser/packets/unpacker.rb +127 -0
- data/lib/nesser/transaction.rb +94 -0
- data/lib/nesser/version.rb +3 -0
- data/nesser.gemspec +22 -0
- metadata +118 -0
@@ -0,0 +1,97 @@
|
|
1
|
+
# Encoding: ASCII-8BIT
|
2
|
+
##
|
3
|
+
# packer.rb
|
4
|
+
# Created June 20, 2017
|
5
|
+
# By Ron Bowes
|
6
|
+
#
|
7
|
+
# See: LICENSE.md
|
8
|
+
#
|
9
|
+
# DNS has some unusual properties that we have to handle, which is why I
|
10
|
+
# wrote this class. It handles building / parsing DNS packets and keeping
|
11
|
+
# track of where in the packet we currently are. The advantage, besides
|
12
|
+
# simpler unpacking, is that encoded names (with pointers to other parts
|
13
|
+
# of the packet) can be trivially handled.
|
14
|
+
##
|
15
|
+
|
16
|
+
require 'nesser/dns_exception'
|
17
|
+
require 'nesser/packets/constants'
|
18
|
+
|
19
|
+
module Nesser
|
20
|
+
class Packer
|
21
|
+
public
|
22
|
+
def initialize()
|
23
|
+
@data = ''
|
24
|
+
@segment_cache = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
public
|
28
|
+
def pack(format, *data)
|
29
|
+
@data += data.pack(format)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def validate!(name)
|
34
|
+
if name.chars.detect { |ch| !LEGAL_CHARACTERS.include?(ch) }
|
35
|
+
raise(FormatException, "DNS name contains illegal characters")
|
36
|
+
end
|
37
|
+
if name.length > 253
|
38
|
+
raise(FormatException, "DNS name can't be longer than 253 characters")
|
39
|
+
end
|
40
|
+
name.split(/\./).each do |segment|
|
41
|
+
if segment.length == 0 || segment.length > 63
|
42
|
+
raise(FormatException, "DNS segments must be between 1 and 63 characters!")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Take a name, as a dotted string ("google.com") and return it as length-
|
48
|
+
# prefixed segments ("\x06google\x03com\x00"). It also does a pointer
|
49
|
+
# (\xc0\xXX) when possible!
|
50
|
+
public
|
51
|
+
def pack_name(name, dry_run:false, compress:true)
|
52
|
+
length = 0
|
53
|
+
validate!(name)
|
54
|
+
|
55
|
+
# `name` becomes nil at the end, unless there's a comma on the end, in
|
56
|
+
# which case it's a 0-length string
|
57
|
+
while name and name.length() > 0
|
58
|
+
if compress && @segment_cache[name]
|
59
|
+
# User a pointer if we've already done this
|
60
|
+
if not dry_run
|
61
|
+
@data += [0xc000 | @segment_cache[name]].pack("n")
|
62
|
+
end
|
63
|
+
|
64
|
+
# If we use break here, we get a bad NUL terminator
|
65
|
+
return length + 2
|
66
|
+
end
|
67
|
+
|
68
|
+
# Log where we put this segment
|
69
|
+
if not dry_run
|
70
|
+
@segment_cache[name] = @data.length
|
71
|
+
end
|
72
|
+
|
73
|
+
# Get the next label
|
74
|
+
segment, name = name.split(/\./, 2)
|
75
|
+
|
76
|
+
# Encode it into the string
|
77
|
+
if not dry_run
|
78
|
+
@data += [segment.length(), segment].pack("Ca*")
|
79
|
+
end
|
80
|
+
length += 1 + segment.length()
|
81
|
+
end
|
82
|
+
|
83
|
+
# Always be null terminating
|
84
|
+
if not dry_run
|
85
|
+
@data += "\0"
|
86
|
+
end
|
87
|
+
length += 1
|
88
|
+
|
89
|
+
return length
|
90
|
+
end
|
91
|
+
|
92
|
+
public
|
93
|
+
def get()
|
94
|
+
return @data
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
# Encoding: ASCII-8BIT
|
2
|
+
##
|
3
|
+
# packet.rb
|
4
|
+
# Created June 20, 2017
|
5
|
+
# By Ron Bowes
|
6
|
+
#
|
7
|
+
# See: LICENSE.md
|
8
|
+
##
|
9
|
+
|
10
|
+
require 'nesser/dns_exception'
|
11
|
+
require 'nesser/packets/answer'
|
12
|
+
require 'nesser/packets/constants'
|
13
|
+
require 'nesser/packets/packer'
|
14
|
+
require 'nesser/packets/question'
|
15
|
+
require 'nesser/packets/rr_types'
|
16
|
+
require 'nesser/packets/unpacker'
|
17
|
+
|
18
|
+
module Nesser
|
19
|
+
class Packet
|
20
|
+
attr_accessor :trn_id, :qr, :opcode, :flags, :rcode, :questions, :answers
|
21
|
+
|
22
|
+
def initialize(trn_id:, qr:, opcode:, flags:, rcode:, questions:[], answers:[])
|
23
|
+
@trn_id = trn_id
|
24
|
+
@qr = qr
|
25
|
+
@opcode = opcode
|
26
|
+
@flags = flags
|
27
|
+
@rcode = rcode
|
28
|
+
|
29
|
+
questions.each { |q| raise(DnsException, "Questions must be of type Answer!") if !q.is_a?(Question) }
|
30
|
+
@questions = questions
|
31
|
+
|
32
|
+
answers.each { |a| raise(DnsException, "Answers must be of type Answer!") if !a.is_a?(Answer) }
|
33
|
+
@answers = answers
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_question(question)
|
37
|
+
if !question.is_a?(Question)
|
38
|
+
raise(DnsException, "Questions must be of type Question!")
|
39
|
+
end
|
40
|
+
|
41
|
+
@questions << question
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_answer(answer)
|
45
|
+
if !answer.is_a?(Answer)
|
46
|
+
raise(DnsException, "Questions must be of type Question!")
|
47
|
+
end
|
48
|
+
|
49
|
+
@answers << answer
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.parse(data)
|
53
|
+
unpacker = Unpacker.new(data)
|
54
|
+
trn_id, full_flags, qdcount, ancount, _, _ = unpacker.unpack("nnnnnn")
|
55
|
+
|
56
|
+
qr = (full_flags >> 15) & 0x0001
|
57
|
+
opcode = (full_flags >> 11) & 0x000F
|
58
|
+
flags = (full_flags >> 7) & 0x000F
|
59
|
+
rcode = (full_flags >> 0) & 0x000F
|
60
|
+
|
61
|
+
packet = self.new(
|
62
|
+
trn_id: trn_id,
|
63
|
+
qr: qr,
|
64
|
+
opcode: opcode,
|
65
|
+
flags: flags,
|
66
|
+
rcode: rcode,
|
67
|
+
questions: [],
|
68
|
+
answers: [],
|
69
|
+
)
|
70
|
+
|
71
|
+
0.upto(qdcount - 1) do
|
72
|
+
question = Question.unpack(unpacker)
|
73
|
+
packet.add_question(question)
|
74
|
+
end
|
75
|
+
|
76
|
+
0.upto(ancount - 1) do
|
77
|
+
answer = Answer.unpack(unpacker)
|
78
|
+
packet.add_answer(answer)
|
79
|
+
end
|
80
|
+
|
81
|
+
return packet
|
82
|
+
end
|
83
|
+
|
84
|
+
def answer(answers:[], question:nil)
|
85
|
+
question = question || @questions[0]
|
86
|
+
|
87
|
+
return Packet.new(
|
88
|
+
trn_id: @trn_id,
|
89
|
+
qr: QR_RESPONSE,
|
90
|
+
opcode: OPCODE_QUERY,
|
91
|
+
flags: FLAG_RD | FLAG_RA,
|
92
|
+
rcode: RCODE_SUCCESS,
|
93
|
+
questions: [question],
|
94
|
+
answers: answers,
|
95
|
+
)
|
96
|
+
end
|
97
|
+
|
98
|
+
def error(rcode:, question:nil)
|
99
|
+
question = question || @questions[0]
|
100
|
+
|
101
|
+
return Packet.new(
|
102
|
+
trn_id: @trn_id,
|
103
|
+
qr: QR_RESPONSE,
|
104
|
+
opcode: OPCODE_QUERY,
|
105
|
+
flags: FLAG_RD | FLAG_RA,
|
106
|
+
rcode: rcode,
|
107
|
+
questions: [question],
|
108
|
+
answers: [],
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
def to_bytes()
|
113
|
+
packer = Packer.new()
|
114
|
+
|
115
|
+
full_flags = ((@qr << 15) & 0x8000) |
|
116
|
+
((@opcode << 11) & 0x7800) |
|
117
|
+
((@flags << 7) & 0x0780) |
|
118
|
+
((@rcode << 0) & 0x000F)
|
119
|
+
|
120
|
+
packer.pack('nnnnnn',
|
121
|
+
@trn_id, # trn_id
|
122
|
+
full_flags, # qr, opcode, flags, rcode
|
123
|
+
@questions.length(), # qdcount
|
124
|
+
@answers.length(), # ancount
|
125
|
+
0, # nscount (we don't handle)
|
126
|
+
0, # arcount (we don't handle)
|
127
|
+
)
|
128
|
+
|
129
|
+
questions.each do |q|
|
130
|
+
q.pack(packer)
|
131
|
+
end
|
132
|
+
|
133
|
+
answers.each do |a|
|
134
|
+
a.pack(packer)
|
135
|
+
end
|
136
|
+
|
137
|
+
return packer.get()
|
138
|
+
end
|
139
|
+
|
140
|
+
def to_s(brief:false)
|
141
|
+
if(brief)
|
142
|
+
question = @questions[0] || '<unknown>'
|
143
|
+
|
144
|
+
# Print error packets more clearly
|
145
|
+
if(@rcode != RCODE_SUCCESS)
|
146
|
+
return "Request for #{question}: error: #{RCODES[@rcode]}"
|
147
|
+
end
|
148
|
+
|
149
|
+
if(@qr == QR_QUERY)
|
150
|
+
return "Request for #{question}"
|
151
|
+
else
|
152
|
+
if(@answers.length == 0)
|
153
|
+
return "Response for %s: n/a" % question.to_s
|
154
|
+
else
|
155
|
+
return "Response for %s: %s (and %d others)" % [
|
156
|
+
question.to_s(),
|
157
|
+
@answers[0].to_s(),
|
158
|
+
@answers.length - 1,
|
159
|
+
]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
results = []
|
165
|
+
results << "DNS %s: id=0x%04x, opcode = %s, flags = %s, rcode = %s, qdcount = 0x%04x, ancount = 0x%04x" % [
|
166
|
+
QRS[@qr] || "unknown",
|
167
|
+
@trn_id,
|
168
|
+
OPCODES[@opcode] || "unknown opcode",
|
169
|
+
Nesser::FLAGS(@flags),
|
170
|
+
RCODES[@rcode] || "unknown",
|
171
|
+
@questions.length,
|
172
|
+
@answers.length,
|
173
|
+
]
|
174
|
+
|
175
|
+
@questions.each do |q|
|
176
|
+
results << " Question: %s" % q.to_s()
|
177
|
+
end
|
178
|
+
|
179
|
+
@answers.each do |a|
|
180
|
+
results << " Answer: %s" % a.to_s()
|
181
|
+
end
|
182
|
+
|
183
|
+
return results.join("\n")
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Encoding: ASCII-8BIT
|
2
|
+
##
|
3
|
+
# question.rb
|
4
|
+
# Created June 21, 2017
|
5
|
+
# By Ron Bowes
|
6
|
+
#
|
7
|
+
# See: LICENSE.md
|
8
|
+
#
|
9
|
+
# This defines a DNS question. One question is sent in outgoing packets,
|
10
|
+
# and one question is also sent in the response - generally, the same as
|
11
|
+
# the question that was asked.
|
12
|
+
##
|
13
|
+
module Nesser
|
14
|
+
class Question
|
15
|
+
attr_reader :name, :type, :cls
|
16
|
+
|
17
|
+
def initialize(name:, type:, cls:)
|
18
|
+
@name = name
|
19
|
+
@type = type
|
20
|
+
@cls = cls
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.unpack(unpacker)
|
24
|
+
name = unpacker.unpack_name()
|
25
|
+
type, cls = unpacker.unpack("nn")
|
26
|
+
|
27
|
+
return self.new(name: name, type: type, cls: cls)
|
28
|
+
end
|
29
|
+
|
30
|
+
def pack(packer)
|
31
|
+
packer.pack_name(@name)
|
32
|
+
packer.pack('nn', type, cls)
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s()
|
36
|
+
return '%s [%s %s]' % [
|
37
|
+
@name,
|
38
|
+
TYPES[@type] || '<0x%04x?>' % @type,
|
39
|
+
CLSES[@cls] || '<0x%04x?>' % @cls,
|
40
|
+
]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,290 @@
|
|
1
|
+
# Encoding: ASCII-8BIT
|
2
|
+
##
|
3
|
+
# types.rb
|
4
|
+
# Created June 20, 2017
|
5
|
+
# By Ron Bowes
|
6
|
+
#
|
7
|
+
# See: LICENSE.md
|
8
|
+
##
|
9
|
+
|
10
|
+
require 'ipaddr'
|
11
|
+
|
12
|
+
require 'nesser/packets/constants'
|
13
|
+
require 'nesser/dns_exception'
|
14
|
+
require 'nesser/packets/packer'
|
15
|
+
require 'nesser/packets/unpacker'
|
16
|
+
|
17
|
+
module Nesser
|
18
|
+
class A
|
19
|
+
attr_accessor :address
|
20
|
+
|
21
|
+
def initialize(address:)
|
22
|
+
if !address.is_a?(String)
|
23
|
+
raise(FormatException, "String required!")
|
24
|
+
end
|
25
|
+
|
26
|
+
begin
|
27
|
+
@address = IPAddr.new(address)
|
28
|
+
rescue IPAddr::InvalidAddressError => e
|
29
|
+
raise(FormatException, "Invalid address: %s" % e)
|
30
|
+
end
|
31
|
+
|
32
|
+
if !@address.ipv4?()
|
33
|
+
raise(FormatException, "IPv4 address required!")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.unpack(unpacker)
|
38
|
+
length = unpacker.unpack_one('n')
|
39
|
+
if length != 4
|
40
|
+
raise(FormatException, "Invalid A record!")
|
41
|
+
end
|
42
|
+
|
43
|
+
data = unpacker.unpack('a4').join()
|
44
|
+
return self.new(address: IPAddr.ntop(data))
|
45
|
+
end
|
46
|
+
|
47
|
+
def pack(packer)
|
48
|
+
packer.pack('n', 4) # length
|
49
|
+
packer.pack('C4', *@address.hton().bytes())
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s()
|
53
|
+
return "#{@address} [A]"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class NS
|
58
|
+
attr_accessor :name
|
59
|
+
|
60
|
+
def initialize(name:)
|
61
|
+
@name = name
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.unpack(unpacker)
|
65
|
+
# We don't really need the name for anything, so just discard it
|
66
|
+
unpacker.unpack('n')
|
67
|
+
|
68
|
+
return self.new(name: unpacker.unpack_name())
|
69
|
+
end
|
70
|
+
|
71
|
+
def pack(packer)
|
72
|
+
length = packer.pack_name(@name, dry_run: true)
|
73
|
+
packer.pack('n', length)
|
74
|
+
|
75
|
+
packer.pack_name(@name)
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_s()
|
79
|
+
return "#{@name} [NS]"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class CNAME
|
84
|
+
attr_accessor :name
|
85
|
+
|
86
|
+
def initialize(name:)
|
87
|
+
@name = name
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.unpack(unpacker)
|
91
|
+
# We don't really need the name for anything, so just discard it
|
92
|
+
unpacker.unpack('n')
|
93
|
+
|
94
|
+
return self.new(name: unpacker.unpack_name())
|
95
|
+
end
|
96
|
+
|
97
|
+
def pack(packer)
|
98
|
+
length = packer.pack_name(@name, dry_run: true)
|
99
|
+
packer.pack('n', length)
|
100
|
+
packer.pack_name(@name)
|
101
|
+
end
|
102
|
+
|
103
|
+
def to_s()
|
104
|
+
return "#{@name} [CNAME]"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class SOA
|
109
|
+
attr_accessor :primary, :responsible, :serial, :refresh, :retry_interval, :expire, :ttl
|
110
|
+
|
111
|
+
def initialize(primary:, responsible:, serial:, refresh:, retry_interval:, expire:, ttl:)
|
112
|
+
@primary = primary
|
113
|
+
@responsible = responsible
|
114
|
+
@serial = serial
|
115
|
+
@refresh = refresh
|
116
|
+
@retry_interval = retry_interval
|
117
|
+
@expire = expire
|
118
|
+
@ttl = ttl
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.unpack(unpacker)
|
122
|
+
length = unpacker.unpack_one('n')
|
123
|
+
if length < 22
|
124
|
+
raise(FormatException, "Invalid SOA record")
|
125
|
+
end
|
126
|
+
|
127
|
+
primary = unpacker.unpack_name()
|
128
|
+
responsible = unpacker.unpack_name()
|
129
|
+
serial, refresh, retry_interval, expire, ttl = unpacker.unpack("NNNNN")
|
130
|
+
|
131
|
+
return self.new(primary: primary, responsible: responsible, serial: serial, refresh: refresh, retry_interval: retry_interval, expire: expire, ttl: ttl)
|
132
|
+
end
|
133
|
+
|
134
|
+
def pack(packer)
|
135
|
+
length = packer.pack_name(@primary, dry_run: true) + packer.pack_name(@responsible, dry_run: true, compress: false) + 20
|
136
|
+
packer.pack('n', length)
|
137
|
+
|
138
|
+
packer.pack_name(@primary)
|
139
|
+
# It's a pain to calculate the length when both of these can be
|
140
|
+
# compressed, so we're just not going to compress the second name
|
141
|
+
packer.pack_name(@responsible, compress: false)
|
142
|
+
packer.pack("NNNNN", @serial, @refresh, @retry_interval, @expire, @ttl)
|
143
|
+
end
|
144
|
+
|
145
|
+
def to_s()
|
146
|
+
return "Primary name server = %s, responsible authority's mailbox: %s, serial number: 0x%08x, refresh interval: 0x%08x, retry interval: 0x%08x, expire limit: 0x%08x, min_ttl: 0x%08x, [SOA]" % [
|
147
|
+
@primary,
|
148
|
+
@responsible,
|
149
|
+
@serial,
|
150
|
+
@refresh,
|
151
|
+
@retry_interval,
|
152
|
+
@expire,
|
153
|
+
@ttl,
|
154
|
+
]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class MX
|
159
|
+
attr_accessor :preference, :name
|
160
|
+
|
161
|
+
def initialize(name:, preference:)
|
162
|
+
@name = name
|
163
|
+
@preference = preference
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.unpack(unpacker)
|
167
|
+
length = unpacker.unpack_one('n')
|
168
|
+
if length < 3
|
169
|
+
raise(FormatException, "Invalid MX record")
|
170
|
+
end
|
171
|
+
|
172
|
+
preference = unpacker.unpack_one('n')
|
173
|
+
name = unpacker.unpack_name()
|
174
|
+
|
175
|
+
return self.new(name: name, preference: preference)
|
176
|
+
end
|
177
|
+
|
178
|
+
def pack(packer)
|
179
|
+
length = packer.pack_name(@name, dry_run: true) + 2
|
180
|
+
packer.pack('n', length)
|
181
|
+
|
182
|
+
packer.pack('n', @preference)
|
183
|
+
packer.pack_name(@name)
|
184
|
+
end
|
185
|
+
|
186
|
+
def to_s()
|
187
|
+
return "#{@preference} #{@name} [MX]"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
class TXT
|
192
|
+
attr_accessor :data
|
193
|
+
|
194
|
+
def initialize(data:)
|
195
|
+
@data = data
|
196
|
+
end
|
197
|
+
|
198
|
+
def self.unpack(unpacker)
|
199
|
+
length = unpacker.unpack_one('n')
|
200
|
+
if length < 1
|
201
|
+
raise(FormatException, "Invalid TXT record")
|
202
|
+
end
|
203
|
+
|
204
|
+
len = unpacker.unpack_one("C")
|
205
|
+
|
206
|
+
if len != length - 1
|
207
|
+
raise(FormatException, "Invalid TXT record")
|
208
|
+
end
|
209
|
+
|
210
|
+
data = unpacker.unpack_one("a#{len}")
|
211
|
+
|
212
|
+
return self.new(data: data)
|
213
|
+
end
|
214
|
+
|
215
|
+
def pack(packer)
|
216
|
+
packer.pack('n', @data.length + 1)
|
217
|
+
|
218
|
+
packer.pack('Ca*', @data.length, @data)
|
219
|
+
end
|
220
|
+
|
221
|
+
def to_s()
|
222
|
+
return "#{@data} [TXT]"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
class AAAA
|
227
|
+
attr_accessor :address
|
228
|
+
|
229
|
+
def initialize(address:)
|
230
|
+
if !address.is_a?(String)
|
231
|
+
raise(FormatException, "String required!")
|
232
|
+
end
|
233
|
+
|
234
|
+
begin
|
235
|
+
@address = IPAddr.new(address)
|
236
|
+
rescue IPAddr::InvalidAddressError => e
|
237
|
+
raise(FormatException, "Invalid address: %s" % e)
|
238
|
+
end
|
239
|
+
|
240
|
+
if !@address.ipv6?()
|
241
|
+
raise(FormatException, "IPv6 address required!")
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def self.unpack(unpacker)
|
246
|
+
length = unpacker.unpack_one('n')
|
247
|
+
if length != 16
|
248
|
+
raise(FormatException, "Invalid AAAA record")
|
249
|
+
end
|
250
|
+
|
251
|
+
data = unpacker.unpack('a16').join()
|
252
|
+
return self.new(address: IPAddr.ntop(data))
|
253
|
+
end
|
254
|
+
|
255
|
+
def pack(packer)
|
256
|
+
packer.pack('n', 16)
|
257
|
+
|
258
|
+
packer.pack('C16', *@address.hton().bytes())
|
259
|
+
end
|
260
|
+
|
261
|
+
|
262
|
+
def to_s()
|
263
|
+
return "#{@address} [AAAA]"
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
class RRUnknown
|
268
|
+
attr_reader :type, :data
|
269
|
+
def initialize(type:, data:)
|
270
|
+
@type = type
|
271
|
+
@data = data
|
272
|
+
end
|
273
|
+
|
274
|
+
def self.unpack(unpacker, type)
|
275
|
+
length = unpacker.unpack_one('n')
|
276
|
+
data = unpacker.unpack_one("a#{length}")
|
277
|
+
return self.new(type: type, data: data)
|
278
|
+
end
|
279
|
+
|
280
|
+
def pack(packer)
|
281
|
+
packer.pack('n', @data.length)
|
282
|
+
|
283
|
+
packer.pack('a*', @data)
|
284
|
+
end
|
285
|
+
|
286
|
+
def to_s()
|
287
|
+
return "(Unknown record type 0x%04x: %s)" % [@type, @data]
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|