nesser 0.0.1
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 +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
|