-
Notifications
You must be signed in to change notification settings - Fork 1
/
dhcpv6_codec.lua
326 lines (294 loc) · 9.05 KB
/
dhcpv6_codec.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
#!/usr/bin/env lua
-- -*-lua-*-
--
-- $Id: dhcpv6codec.lua $
--
-- Author: Markus Stenberg <markus [email protected]>
--
-- Copyright (c) 2013 cisco Systems, Inc.
--
-- Created: Wed Feb 20 18:30:04 2013 mstenber
-- Last modified: Sat Oct 19 00:33:40 2013 mstenber
-- Edit time: 85 min
--
require 'codec'
require 'ipv6s'
require 'dhcpv6_const'
require 'dns_name'
local vstruct = require 'vstruct'
module(..., package.seeall)
local MT_RELAY_FORW = dhcpv6_const.MT_RELAY_FORW
local MT_RELAY_REPL = dhcpv6_const.MT_RELAY_REPL
local _ad = codec.abstract_data
-- per-option bidirectional mapping between 'data' <> dict (that will
-- have type also hard-coded, so we don't need to worry about 'type'
-- at this stage)
option_map = {
[dhcpv6_const.O_ORO]={
decode = function (data)
if #data % 2 > 0 then return nil, 'non-even # of bytes in payload' end
local t = {}
mst.d('got data', #data)
for i = 1, #data/2
do
local v = codec.nb_to_u16(data, i * 2 - 1)
table.insert(t, v)
end
return t
end,
encode = function (o)
local t = {}
for i, v in ipairs(o)
do
table.insert(t, codec.u16_to_nb(v))
end
return table.concat(t)
end,
},
[dhcpv6_const.O_DNS_RNS]={
decode = function (data)
if #data % 16 > 0 then return nil, 'non 16 divisible # of payload' end
local t = {}
for i = 1, #data/16
do
local st = (i - 1) * 16 + 1
local en = st + 16
local b = string.sub(data, st, en)
mst.a(#b == 16)
table.insert(t, ipv6s.binary_address_to_address(b))
end
return t
end,
encode = function (o)
local t = {}
for i, v in ipairs(o)
do
table.insert(t, ipv6s.address_to_binary_address(v))
end
return table.concat(t)
end
},
[dhcpv6_const.O_DOMAIN_SEARCH]={
decode = function (data)
local cur = vstruct.cursor(data)
local t = {}
while true
do
local r = dns_name.try_decode_name(cur)
if not r
then
mst.d('decode', mst.repr(data), t)
return t
end
table.insert(t, r)
end
end,
encode = function (o)
local t = {}
for i, v in ipairs(o)
do
local t2, err = dns_name.try_encode_name(v)
if t2
then
mst.array_extend(t, t2)
else
mst.d('unable to encode', v)
end
end
local data = table.concat(t)
mst.d('encode', o, mst.repr(data))
return data
end,
},
}
-- single DHCPv6 option _instance_
dhcpv6_option = _ad:new{class='dhcpv6_option',
format='option:u2 length:u2',
header_default={option=0, length=0},
copy_on_encode=true,}
function dhcpv6_option:try_decode(cur)
local o, err = _ad.try_decode(self, cur)
if not o then return o, err end
-- ok, looks like we got the header. let's see if we can get the data too
if not codec.cursor_has_left(cur, o.length)
then
return nil, 'out of space decoding body'
end
local data = cur:read(o.length)
mst.a(#data == o.length)
local t = tonumber(o.option)
local h = option_map[t]
if h
then
local no, err = h.decode(data)
if not no then return nil, 'handler failed ' .. tostring(err) end
no.option = o.option
return no
else
self:d('no handler for type', t)
o.length = nil
data = mst.string_to_hex(data)
o.data = data
return o
end
end
function dhcpv6_option:do_encode(o)
local t = tonumber(o.option)
local h = option_map[t]
local data
if h
then
data = h.encode(o)
mst.a(data, 'unable to encode', o)
else
mst.a(o.data, 'no o.data?!?', o)
data = mst.hex_to_string(o.data)
end
o.length = #data
return _ad.do_encode(self, o) .. data
end
-- convenience subclass, which has header (child responsibility) +
-- a list of options.. as a Lua oddity, we treat the whole thing as a list
-- so the list has .headerfield=value parts, and [1]=first sub-option..
optlist = _ad:new_subclass{class='optlist'}
function optlist:try_decode(cur)
local o, err = _ad.try_decode(self, cur)
if not o then return o, err end
while codec.cursor_has_left(cur, 4)
do
local o2, err2 = dhcpv6_option:decode(cur)
if not o2 then return o2, err2 end
table.insert(o, o2)
end
return o
end
function optlist:do_encode(o)
local t = {}
-- first header
table.insert(t, _ad.do_encode(self, o))
-- then all options
for i, o2 in ipairs(o)
do
local s = dhcpv6_option:encode(o2)
table.insert(t, s)
end
return table.concat(t)
end
-- class _instances_ for handling options with ugly content
-- both ia_na, and ia_pd seem to look like this
data_ia = optlist:new{class='data_ia',
format='iaid:u4 t1:u4 t2:u4',
header_default={iaid=0, t1=0, t2=0}}
data_iaprefix = optlist:new{class='data_iaprefix',
format='preferred:u4 valid:u4 plength:u1 rawprefix:s16',
header_default={preferred=0, valid=0,
plength=0, rawprefix=''},
copy_on_encode=true,
}
function data_iaprefix:try_decode(cur)
local o, err = optlist.try_decode(self, cur)
if not o then return nil, err end
self:d('try_decode', o)
-- replace rawprefix + plength with human-readable prefix
self:a(o.rawprefix and o.plength)
o.prefix = ipv6s.new_prefix_from_binary(o.rawprefix, o.plength):get_ascii()
o.plength = nil
o.rawprefix = nil
return o
end
function data_iaprefix:do_encode(o)
local p = ipv6s.new_prefix_from_ascii(o.prefix)
o.rawprefix = p:get_binary()
o.plength = p:get_binary_bits()
return optlist.do_encode(self, o)
end
data_iaaddr = optlist:new{class='data_iaaddr',
format='rawaddr:s16 preferred:u4 valid:u4',
header_default={preferred=0, valid=0,
rawaddr=''},
copy_on_encode=true,
}
function data_iaaddr:try_decode(cur)
local o, err = optlist.try_decode(self, cur)
if not o then return nil, err end
self:d('try_decode', o)
-- replace rawprefix + plength with human-readable prefix
self:a(o.rawaddr)
o.addr = ipv6s.binary_address_to_address(o.rawaddr)
o.rawaddr = nil
return o
end
function data_iaaddr:do_encode(o)
o.rawaddr = ipv6s.address_to_binary_address(o.addr)
return optlist.do_encode(self, o)
end
data_status_code = _ad:new{class='data_status_code',
format='code:u2 message:s',
header_default={code=0, message=''}}
data_uint16 = _ad:new{class='data_uint16',
format='value:u2',
header_default={value=0}}
data_uint8 = _ad:new{class='data_uint8',
format='value:u1',
header_default={value=0}}
-- class _instances_ for handling different types of DHCPv6 messages
dhcpv6_base_message =
optlist:new{class='dhcpv6_base_message',
format='type:u1 xid:u3',
header_default={type=0, xid=0}}
dhcpv6_relay_message =
optlist:new{class='dhcpv6_relay_message',
format='type:u1 hopcount:u1 lladdr:s16 peeraddr:s16',
header_default={type=0, hopcount=0,
lladdr='', peeraddr='',}
}
-- wrapper class around dhcpv6_base_message and dhcpv6_relay_message
dhcpv6_message = codec.abstract_base:new{class='dhcpv6_message'}
function dhcpv6_message:try_decode(cur)
if not codec.cursor_has_left(cur, 1)
then
return nil, 'empty cursor'
end
local opos = cur.pos
local t = cur:read(1)
cur:seek('set', opos)
local mt = string.byte(t)
if mt == MT_RELAY_FORW or mt == MT_RELAY_REPL
then
return dhcpv6_relay_message:decode(cur)
else
return dhcpv6_base_message:decode(cur)
end
end
function dhcpv6_message:do_encode(o)
if o.type == MT_RELAY_FORW or o.type == MT_RELAY_REPL
then
return dhcpv6_relay_message:encode(o)
else
return dhcpv6_base_message:encode(o)
end
end
-- copy contents of option_to_clasS_map to option_map
option_to_class_map = {
-- RFC3633
[dhcpv6_const.O_IA_PD]=data_ia,
[dhcpv6_const.O_IAPREFIX]=data_iaprefix,
-- RFC3315
[dhcpv6_const.O_IA_NA]=data_ia,
[dhcpv6_const.O_IAADDR]=data_iaaddr,
[dhcpv6_const.O_PREFERENCE]=data_uint8,
[dhcpv6_const.O_ELAPSED_TIME]=data_uint16,
[dhcpv6_const.O_PREFIX_CLASS]=data_uint16,
[dhcpv6_const.O_STATUS_CODE]=data_status_code,
}
for k, v in pairs(option_to_class_map)
do
option_map[k]={
decode = function (data)
return v:decode(data)
end,
encode = function (o)
return v:encode(o)
end,
}
end