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
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
|
// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option_data_types.h>
#include <dhcp/option_custom.h>
#include <util/encode/hex.h>
using namespace isc::asiolink;
namespace isc {
namespace dhcp {
OptionCustom::OptionCustom(const OptionDefinition& def,
Universe u)
: Option(u, def.getCode(), OptionBuffer()),
definition_(def) {
setEncapsulatedSpace(def.getEncapsulatedSpace());
createBuffers();
}
OptionCustom::OptionCustom(const OptionDefinition& def,
Universe u,
const OptionBuffer& data)
: Option(u, def.getCode(), data.begin(), data.end()),
definition_(def) {
setEncapsulatedSpace(def.getEncapsulatedSpace());
createBuffers(getData());
}
OptionCustom::OptionCustom(const OptionDefinition& def,
Universe u,
OptionBufferConstIter first,
OptionBufferConstIter last)
: Option(u, def.getCode(), first, last),
definition_(def) {
setEncapsulatedSpace(def.getEncapsulatedSpace());
createBuffers(getData());
}
OptionPtr
OptionCustom::clone() const {
return (cloneInternal<OptionCustom>());
}
void
OptionCustom::addArrayDataField(const IOAddress& address) {
checkArrayType();
if ((address.isV4() && definition_.getType() != OPT_IPV4_ADDRESS_TYPE) ||
(address.isV6() && definition_.getType() != OPT_IPV6_ADDRESS_TYPE)) {
isc_throw(BadDataTypeCast, "invalid address specified "
<< address << ". Expected a valid IPv"
<< (definition_.getType() == OPT_IPV4_ADDRESS_TYPE ?
"4" : "6") << " address.");
}
OptionBuffer buf;
OptionDataTypeUtil::writeAddress(address, buf);
buffers_.push_back(buf);
}
void
OptionCustom::addArrayDataField(const bool value) {
checkArrayType();
OptionBuffer buf;
OptionDataTypeUtil::writeBool(value, buf);
buffers_.push_back(buf);
}
void
OptionCustom::addArrayDataField(const PrefixLen& prefix_len,
const asiolink::IOAddress& prefix) {
checkArrayType();
if (definition_.getType() != OPT_IPV6_PREFIX_TYPE) {
isc_throw(BadDataTypeCast, "IPv6 prefix can be specified only for"
" an option comprising an array of IPv6 prefix values");
}
OptionBuffer buf;
OptionDataTypeUtil::writePrefix(prefix_len, prefix, buf);
buffers_.push_back(buf);
}
void
OptionCustom::addArrayDataField(const PSIDLen& psid_len, const PSID& psid) {
checkArrayType();
if (definition_.getType() != OPT_PSID_TYPE) {
isc_throw(BadDataTypeCast, "PSID value can be specified onlu for"
" an option comprising an array of PSID length / value"
" tuples");
}
OptionBuffer buf;
OptionDataTypeUtil::writePsid(psid_len, psid, buf);
buffers_.push_back(buf);
}
void
OptionCustom::checkIndex(const uint32_t index) const {
if (index >= buffers_.size()) {
isc_throw(isc::OutOfRange, "specified data field index " << index
<< " is out of range.");
}
}
void
OptionCustom::createBuffers() {
definition_.validate();
std::vector<OptionBuffer> buffers;
OptionDataType data_type = definition_.getType();
// This function is called when an empty data buffer has been
// passed to the constructor. In such cases values for particular
// data fields will be set using modifier functions but for now
// we need to initialize a set of buffers that are specified
// for an option by its definition. Since there is no data yet,
// we are going to fill these buffers with default values.
if (data_type == OPT_RECORD_TYPE) {
// For record types we need to iterate over all data fields
// specified in option definition and create corresponding
// buffers for each of them.
const OptionDefinition::RecordFieldsCollection fields =
definition_.getRecordFields();
for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
field != fields.end(); ++field) {
OptionBuffer buf;
// For data types that have a fixed size we can use the
// utility function to get the buffer's size.
size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field);
// For variable data sizes the utility function returns zero.
// It is ok for string values because the default string
// is 'empty'. However for FQDN the empty value is not valid
// so we initialize it to '.'. For prefix there is a prefix
// length fixed field.
if (data_size == 0) {
if (*field == OPT_FQDN_TYPE) {
OptionDataTypeUtil::writeFqdn(".", buf);
} else if (*field == OPT_IPV6_PREFIX_TYPE) {
OptionDataTypeUtil::writePrefix(PrefixLen(0),
IOAddress::IPV6_ZERO_ADDRESS(),
buf);
}
} else {
// At this point we can resize the buffer. Note that
// for string values we are setting the empty buffer
// here.
buf.resize(data_size);
}
// We have the buffer with default value prepared so we
// add it to the set of buffers.
buffers.push_back(buf);
}
} else if (!definition_.getArrayType() &&
data_type != OPT_EMPTY_TYPE) {
// For either 'empty' options we don't have to create any buffers
// for obvious reason. For arrays we also don't create any buffers
// yet because the set of fields that belong to the array is open
// ended so we can't allocate required buffers until we know how
// many of them are needed.
// For non-arrays we have a single value being held by the option
// so we have to allocate exactly one buffer.
OptionBuffer buf;
size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
if (data_size == 0) {
if (data_type == OPT_FQDN_TYPE) {
OptionDataTypeUtil::writeFqdn(".", buf);
} else if (data_type == OPT_IPV6_PREFIX_TYPE) {
OptionDataTypeUtil::writePrefix(PrefixLen(0),
IOAddress::IPV6_ZERO_ADDRESS(),
buf);
}
} else {
// Note that if our option holds a string value then
// we are making empty buffer here.
buf.resize(data_size);
}
// Add a buffer that we have created and leave.
buffers.push_back(buf);
}
// The 'swap' is used here because we want to make sure that we
// don't touch buffers_ until we successfully allocate all
// buffers to be stored there.
std::swap(buffers, buffers_);
}
void
OptionCustom::createBuffers(const OptionBuffer& data_buf) {
// Check that the option definition is correct as we are going
// to use it to split the data_ buffer into set of sub buffers.
definition_.validate();
std::vector<OptionBuffer> buffers;
OptionBuffer::const_iterator data = data_buf.begin();
OptionDataType data_type = definition_.getType();
if (data_type == OPT_RECORD_TYPE) {
// An option comprises a record of data fields. We need to
// get types of these data fields to allocate enough space
// for each buffer.
const OptionDefinition::RecordFieldsCollection& fields =
definition_.getRecordFields();
// Go over all data fields within a record.
for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
field != fields.end(); ++field) {
// For fixed-size data type such as boolean, integer, even
// IP address we can use the utility function to get the required
// buffer size.
size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field);
// For variable size types (e.g. string) the function above will
// return 0 so we need to do a runtime check of the length.
if (data_size == 0) {
// FQDN is a special data type as it stores variable length data
// but the data length is encoded in the buffer. The easiest way
// to obtain the length of the data is to read the FQDN. The
// utility function will return the size of the buffer on success.
if (*field == OPT_FQDN_TYPE) {
std::string fqdn =
OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
// The size of the buffer holding an FQDN is always
// 1 byte larger than the size of the string
// representation of this FQDN.
data_size = fqdn.size() + 1;
} else if ((*field == OPT_BINARY_TYPE) || (*field == OPT_STRING_TYPE)) {
// In other case we are dealing with string or binary value
// which size can't be determined. Thus we consume the
// remaining part of the buffer for it. Note that variable
// size data can be laid at the end of the option only and
// that the validate() function in OptionDefinition object
// should have checked wheter it is a case for this option.
data_size = std::distance(data, data_buf.end());
} else if (*field == OPT_IPV6_PREFIX_TYPE ) {
// The size of the IPV6 prefix type is determined as
// one byte (which is the size of the prefix in bits)
// followed by the prefix bits (right-padded with
// zeros to the nearest octet boundary).
if (std::distance(data, data_buf.end()) > 0) {
data_size = static_cast<size_t>(sizeof(uint8_t) + (*data + 7) / 8);
}
} else {
// If we reached the end of buffer we assume that this option is
// truncated because there is no remaining data to initialize
// an option field.
isc_throw(OutOfRange, "option buffer truncated");
}
}
// Our data field requires that there is a certain chunk of
// data left in the buffer. If not, option is truncated.
if (std::distance(data, data_buf.end()) < data_size) {
isc_throw(OutOfRange, "option buffer truncated");
}
// Store the created buffer.
buffers.push_back(OptionBuffer(data, data + data_size));
// Proceed to the next data field.
data += data_size;
}
// Unpack suboptions if any.
if (data != data_buf.end() && !getEncapsulatedSpace().empty()) {
unpackOptions(OptionBuffer(data, data_buf.end()));
}
} else if (data_type != OPT_EMPTY_TYPE) {
// If data_type value is other than OPT_RECORD_TYPE, our option is
// empty (have no data at all) or it comprises one or more
// data fields of the same type. The type of those fields
// is held in the data_type variable so let's use it to determine
// a size of buffers.
size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
// The check below will fail if the input buffer is too short
// for the data size being held by this option.
// Note that data_size returned by getDataTypeLen may be zero
// if variable length data is being held by the option but
// this will not cause this check to throw exception.
if (std::distance(data, data_buf.end()) < data_size) {
isc_throw(OutOfRange, "option buffer truncated");
}
// For an array of values we are taking different path because
// we have to handle multiple buffers.
if (definition_.getArrayType()) {
while (data != data_buf.end()) {
// FQDN is a special case because it is of a variable length.
// The actual length for a particular FQDN is encoded within
// a buffer so we have to actually read the FQDN from a buffer
// to get it.
if (data_type == OPT_FQDN_TYPE) {
std::string fqdn =
OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
// The size of the buffer holding an FQDN is always
// 1 byte larger than the size of the string
// representation of this FQDN.
data_size = fqdn.size() + 1;
} else if (data_type == OPT_IPV6_PREFIX_TYPE) {
PrefixTuple prefix =
OptionDataTypeUtil::readPrefix(OptionBuffer(data, data_buf.end()));
// Data size comprises 1 byte holding a prefix length and the
// prefix length (in bytes) rounded to the nearest byte boundary.
data_size = sizeof(uint8_t) + (prefix.first.asUint8() + 7) / 8;
}
// We don't perform other checks for data types that can't be
// used together with array indicator such as strings, empty field
// etc. This is because OptionDefinition::validate function should
// have checked this already. Thus data_size must be greater than
// zero.
assert(data_size > 0);
// Get chunks of data and store as a collection of buffers.
// Truncate any remaining part which length is not divisible by
// data_size. Note that it is ok to truncate the data if and only
// if the data buffer is long enough to keep at least one value.
// This has been checked above already.
if (std::distance(data, data_buf.end()) < data_size) {
break;
}
buffers.push_back(OptionBuffer(data, data + data_size));
data += data_size;
}
} else {
// For non-arrays the data_size can be zero because
// getDataTypeLen returns zero for variable size data types
// such as strings. Simply take whole buffer.
if (data_size == 0) {
// For FQDN we get the size by actually reading the FQDN.
if (data_type == OPT_FQDN_TYPE) {
std::string fqdn =
OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
// The size of the buffer holding an FQDN is always
// 1 bytes larger than the size of the string
// representation of this FQDN.
data_size = fqdn.size() + 1;
} else if (data_type == OPT_IPV6_PREFIX_TYPE) {
if (!data_buf.empty()) {
data_size = static_cast<size_t>
(sizeof(uint8_t) + (data_buf[0] + 7) / 8);
}
} else {
data_size = std::distance(data, data_buf.end());
}
}
if ((data_size > 0) && (std::distance(data, data_buf.end()) >= data_size)) {
buffers.push_back(OptionBuffer(data, data + data_size));
data += data_size;
} else {
isc_throw(OutOfRange, "option buffer truncated");
}
// Unpack suboptions if any.
if (data != data_buf.end() && !getEncapsulatedSpace().empty()) {
unpackOptions(OptionBuffer(data, data_buf.end()));
}
}
} else {
// Unpack suboptions if any.
if (data != data_buf.end() && !getEncapsulatedSpace().empty()) {
unpackOptions(OptionBuffer(data, data_buf.end()));
}
}
// If everything went ok we can replace old buffer set with new ones.
std::swap(buffers_, buffers);
}
std::string
OptionCustom::dataFieldToText(const OptionDataType data_type,
const uint32_t index) const {
std::ostringstream text;
// Get the value of the data field.
switch (data_type) {
case OPT_BINARY_TYPE:
text << util::encode::encodeHex(readBinary(index));
break;
case OPT_BOOLEAN_TYPE:
text << (readBoolean(index) ? "true" : "false");
break;
case OPT_INT8_TYPE:
text << static_cast<int>(readInteger<int8_t>(index));
break;
case OPT_INT16_TYPE:
text << readInteger<int16_t>(index);
break;
case OPT_INT32_TYPE:
text << readInteger<int32_t>(index);
break;
case OPT_UINT8_TYPE:
text << static_cast<unsigned>(readInteger<uint8_t>(index));
break;
case OPT_UINT16_TYPE:
text << readInteger<uint16_t>(index);
break;
case OPT_UINT32_TYPE:
text << readInteger<uint32_t>(index);
break;
case OPT_IPV4_ADDRESS_TYPE:
case OPT_IPV6_ADDRESS_TYPE:
text << readAddress(index);
break;
case OPT_FQDN_TYPE:
text << "\"" << readFqdn(index) << "\"";
break;
case OPT_STRING_TYPE:
text << "\"" << readString(index) << "\"";
break;
default:
;
}
// Append data field type in brackets.
text << " (" << OptionDataTypeUtil::getDataTypeName(data_type) << ")";
return (text.str());
}
void
OptionCustom::pack(isc::util::OutputBuffer& buf) const {
// Pack DHCP header (V4 or V6).
packHeader(buf);
// Write data from buffers.
for (std::vector<OptionBuffer>::const_iterator it = buffers_.begin();
it != buffers_.end(); ++it) {
// In theory the createBuffers function should have taken
// care that there are no empty buffers added to the
// collection but it is almost always good to make sure.
if (!it->empty()) {
buf.writeData(&(*it)[0], it->size());
}
}
// Write suboptions.
packOptions(buf);
}
IOAddress
OptionCustom::readAddress(const uint32_t index) const {
checkIndex(index);
// The address being read can be either IPv4 or IPv6. The decision
// is made based on the buffer length. If it holds 4 bytes it is IPv4
// address, if it holds 16 bytes it is IPv6.
if (buffers_[index].size() == asiolink::V4ADDRESS_LEN) {
return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET));
} else if (buffers_[index].size() == asiolink::V6ADDRESS_LEN) {
return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET6));
} else {
isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
<< " IP address. Invalid buffer length "
<< buffers_[index].size() << ".");
}
}
void
OptionCustom::writeAddress(const IOAddress& address,
const uint32_t index) {
checkIndex(index);
if ((address.isV4() && buffers_[index].size() != V4ADDRESS_LEN) ||
(address.isV6() && buffers_[index].size() != V6ADDRESS_LEN)) {
isc_throw(BadDataTypeCast, "invalid address specified "
<< address << ". Expected a valid IPv"
<< (buffers_[index].size() == V4ADDRESS_LEN ? "4" : "6")
<< " address.");
}
OptionBuffer buf;
OptionDataTypeUtil::writeAddress(address, buf);
std::swap(buf, buffers_[index]);
}
const OptionBuffer&
OptionCustom::readBinary(const uint32_t index) const {
checkIndex(index);
return (buffers_[index]);
}
void
OptionCustom::writeBinary(const OptionBuffer& buf,
const uint32_t index) {
checkIndex(index);
buffers_[index] = buf;
}
bool
OptionCustom::readBoolean(const uint32_t index) const {
checkIndex(index);
return (OptionDataTypeUtil::readBool(buffers_[index]));
}
void
OptionCustom::writeBoolean(const bool value, const uint32_t index) {
checkIndex(index);
buffers_[index].clear();
OptionDataTypeUtil::writeBool(value, buffers_[index]);
}
std::string
OptionCustom::readFqdn(const uint32_t index) const {
checkIndex(index);
return (OptionDataTypeUtil::readFqdn(buffers_[index]));
}
void
OptionCustom::writeFqdn(const std::string& fqdn, const uint32_t index) {
checkIndex(index);
// Create a temporary buffer where the FQDN will be written.
OptionBuffer buf;
// Try to write to the temporary buffer rather than to the
// buffers_ member directly guarantees that we don't modify
// (clear) buffers_ until we are sure that the provided FQDN
// is valid.
OptionDataTypeUtil::writeFqdn(fqdn, buf);
// If we got to this point it means that the FQDN is valid.
// We can move the contents of the temporary buffer to the
// target buffer.
std::swap(buffers_[index], buf);
}
PrefixTuple
OptionCustom::readPrefix(const uint32_t index) const {
checkIndex(index);
return (OptionDataTypeUtil::readPrefix(buffers_[index]));
}
void
OptionCustom::writePrefix(const PrefixLen& prefix_len,
const IOAddress& prefix,
const uint32_t index) {
checkIndex(index);
OptionBuffer buf;
OptionDataTypeUtil::writePrefix(prefix_len, prefix, buf);
// If there are no errors while writing PSID to a buffer, we can
// replace the current buffer with a new buffer.
std::swap(buffers_[index], buf);
}
PSIDTuple
OptionCustom::readPsid(const uint32_t index) const {
checkIndex(index);
return (OptionDataTypeUtil::readPsid(buffers_[index]));
}
void
OptionCustom::writePsid(const PSIDLen& psid_len, const PSID& psid,
const uint32_t index) {
checkIndex(index);
OptionBuffer buf;
OptionDataTypeUtil::writePsid(psid_len, psid, buf);
// If there are no errors while writing PSID to a buffer, we can
// replace the current buffer with a new buffer.
std::swap(buffers_[index], buf);
}
std::string
OptionCustom::readString(const uint32_t index) const {
checkIndex(index);
return (OptionDataTypeUtil::readString(buffers_[index]));
}
void
OptionCustom::writeString(const std::string& text, const uint32_t index) {
checkIndex(index);
// Let's clear a buffer as we want to replace the value of the
// whole buffer. If we fail to clear the buffer the data will
// be appended.
buffers_[index].clear();
// If the text value is empty we can leave because the buffer
// is already empty.
if (!text.empty()) {
OptionDataTypeUtil::writeString(text, buffers_[index]);
}
}
void
OptionCustom::unpack(OptionBufferConstIter begin,
OptionBufferConstIter end) {
initialize(begin, end);
}
uint16_t
OptionCustom::len() const {
// The length of the option is a sum of option header ...
size_t length = getHeaderLen();
// ... lengths of all buffers that hold option data ...
for (std::vector<OptionBuffer>::const_iterator buf = buffers_.begin();
buf != buffers_.end(); ++buf) {
length += buf->size();
}
// ... and lengths of all suboptions
for (OptionCollection::const_iterator it = options_.begin();
it != options_.end();
++it) {
length += (*it).second->len();
}
return (static_cast<uint16_t>(length));
}
void OptionCustom::initialize(const OptionBufferConstIter first,
const OptionBufferConstIter last) {
setData(first, last);
// Chop the data_ buffer into set of buffers that represent
// option fields data.
createBuffers(getData());
}
std::string OptionCustom::toText(int indent) const {
std::stringstream output;
output << headerToText(indent) << ":";
OptionDataType data_type = definition_.getType();
if (data_type == OPT_RECORD_TYPE) {
const OptionDefinition::RecordFieldsCollection& fields =
definition_.getRecordFields();
// For record types we iterate over fields defined in
// option definition and match the appropriate buffer
// with them.
for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
field != fields.end(); ++field) {
output << " " << dataFieldToText(*field, std::distance(fields.begin(),
field));
}
} else {
// For non-record types we iterate over all buffers
// and print the data type set globally for an option
// definition. We take the same code path for arrays
// and non-arrays as they only differ in such a way that
// non-arrays have just single data field.
for (unsigned int i = 0; i < getDataFieldsNum(); ++i) {
output << " " << dataFieldToText(definition_.getType(), i);
}
}
// Append suboptions.
output << suboptionsToText(indent + 2);
return (output.str());
}
} // end of isc::dhcp namespace
} // end of isc namespace
|