1 // Copyright (C) 2011-2021 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7 #include <config.h>
8
9 #include <dhcp/dhcp4.h>
10 #include <dhcp/dhcp6.h>
11 #include <dhcp/libdhcp++.h>
12 #include <dhcp/option.h>
13 #include <dhcp/option_vendor.h>
14 #include <dhcp/option6_ia.h>
15 #include <dhcp/option6_iaaddr.h>
16 #include <dhcp/option_definition.h>
17 #include <dhcp/option_int_array.h>
18 #include <dhcp/std_option_defs.h>
19 #include <dhcp/docsis3_option_defs.h>
20 #include <exceptions/exceptions.h>
21 #include <exceptions/isc_assert.h>
22 #include <util/buffer.h>
23
24 #include <boost/lexical_cast.hpp>
25 #include <boost/shared_array.hpp>
26 #include <boost/shared_ptr.hpp>
27
28 #include <limits>
29 #include <list>
30
31 using namespace std;
32 using namespace isc::dhcp;
33 using namespace isc::util;
34
35 namespace isc {
36 namespace dhcp {
37
38 namespace {
39
40 /// @brief the option definitions and the respective space mapping
41 ///
42 /// used for easier initialization of option definitions by space name
43 const OptionDefParamsEncapsulation OPTION_DEF_PARAMS[] = {
44 { STANDARD_V4_OPTION_DEFINITIONS, STANDARD_V4_OPTION_DEFINITIONS_SIZE, DHCP4_OPTION_SPACE },
45 { STANDARD_V6_OPTION_DEFINITIONS, STANDARD_V6_OPTION_DEFINITIONS_SIZE, DHCP6_OPTION_SPACE },
46 { DOCSIS3_V4_OPTION_DEFINITIONS, DOCSIS3_V4_OPTION_DEFINITIONS_SIZE, DOCSIS3_V4_OPTION_SPACE },
47 { DOCSIS3_V6_OPTION_DEFINITIONS, DOCSIS3_V6_OPTION_DEFINITIONS_SIZE, DOCSIS3_V6_OPTION_SPACE },
48 { ISC_V6_OPTION_DEFINITIONS, ISC_V6_OPTION_DEFINITIONS_SIZE, ISC_V6_OPTION_SPACE },
49 { MAPE_V6_OPTION_DEFINITIONS, MAPE_V6_OPTION_DEFINITIONS_SIZE, MAPE_V6_OPTION_SPACE },
50 { MAPT_V6_OPTION_DEFINITIONS, MAPT_V6_OPTION_DEFINITIONS_SIZE, MAPT_V6_OPTION_SPACE },
51 { LW_V6_OPTION_DEFINITIONS, LW_V6_OPTION_DEFINITIONS_SIZE, LW_V6_OPTION_SPACE },
52 { V4V6_RULE_OPTION_DEFINITIONS, V4V6_RULE_OPTION_DEFINITIONS_SIZE, V4V6_RULE_OPTION_SPACE },
53 { V4V6_BIND_OPTION_DEFINITIONS, V4V6_BIND_OPTION_DEFINITIONS_SIZE, V4V6_BIND_OPTION_SPACE },
54 { LAST_RESORT_V4_OPTION_DEFINITIONS, LAST_RESORT_V4_OPTION_DEFINITIONS_SIZE, LAST_RESORT_V4_OPTION_SPACE },
55 { NULL, 0, "" }
56 };
57
58 } // namespace
59
60 } // namespace dhcp
61 } // namespace isc
62
63 // static array with factories for options
64 std::map<unsigned short, Option::Factory*> LibDHCP::v4factories_;
65
66 // static array with factories for options
67 std::map<unsigned short, Option::Factory*> LibDHCP::v6factories_;
68
69 // Static container with option definitions grouped by option space.
70 OptionDefContainers LibDHCP::option_defs_;
71
72 // Static container with option definitions created in runtime.
73 StagedValue<OptionDefSpaceContainer> LibDHCP::runtime_option_defs_;
74
75 // Null container.
76 const OptionDefContainerPtr null_option_def_container_(new OptionDefContainer());
77
78 // Those two vendor classes are used for cable modems:
79
80 /// DOCSIS3.0 compatible cable modem
81 const char* isc::dhcp::DOCSIS3_CLASS_MODEM = "docsis3.0";
82
83 /// DOCSIS3.0 cable modem that has router built-in
84 const char* isc::dhcp::DOCSIS3_CLASS_EROUTER = "eRouter1.0";
85
86 // Let's keep it in .cc file. Moving it to .h would require including optionDefParams
87 // definitions there
88 void initOptionSpace(OptionDefContainerPtr& defs,
89 const OptionDefParams* params,
90 size_t params_size);
91
92 bool LibDHCP::initialized_ = LibDHCP::initOptionDefs();
93
94 const OptionDefContainerPtr
getOptionDefs(const std::string & space)95 LibDHCP::getOptionDefs(const std::string& space) {
96 OptionDefContainers::const_iterator container = option_defs_.find(space);
97 if (container != option_defs_.end()) {
98 return (container->second);
99 }
100
101 return (null_option_def_container_);
102 }
103
104 const OptionDefContainerPtr
getVendorOptionDefs(const Option::Universe u,const uint32_t vendor_id)105 LibDHCP::getVendorOptionDefs(const Option::Universe u, const uint32_t vendor_id) {
106 if (Option::V4 == u) {
107 if (VENDOR_ID_CABLE_LABS == vendor_id) {
108 return getOptionDefs(DOCSIS3_V4_OPTION_SPACE);
109 }
110 } else if (Option::V6 == u) {
111 if (VENDOR_ID_CABLE_LABS == vendor_id) {
112 return getOptionDefs(DOCSIS3_V6_OPTION_SPACE);
113 } else if (ENTERPRISE_ID_ISC == vendor_id) {
114 return getOptionDefs(ISC_V6_OPTION_SPACE);
115 }
116 }
117
118 return (null_option_def_container_);
119 }
120
121 OptionDefinitionPtr
getOptionDef(const std::string & space,const uint16_t code)122 LibDHCP::getOptionDef(const std::string& space, const uint16_t code) {
123 const OptionDefContainerPtr& defs = getOptionDefs(space);
124 const OptionDefContainerTypeIndex& idx = defs->get<1>();
125 const OptionDefContainerTypeRange& range = idx.equal_range(code);
126 if (range.first != range.second) {
127 return (*range.first);
128 }
129
130 return (OptionDefinitionPtr());
131 }
132
133 OptionDefinitionPtr
getOptionDef(const std::string & space,const std::string & name)134 LibDHCP::getOptionDef(const std::string& space, const std::string& name) {
135 const OptionDefContainerPtr& defs = getOptionDefs(space);
136 const OptionDefContainerNameIndex& idx = defs->get<2>();
137 const OptionDefContainerNameRange& range = idx.equal_range(name);
138 if (range.first != range.second) {
139 return (*range.first);
140 }
141
142 return (OptionDefinitionPtr());
143 }
144
145 OptionDefinitionPtr
getVendorOptionDef(const Option::Universe u,const uint32_t vendor_id,const std::string & name)146 LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id,
147 const std::string& name) {
148 const OptionDefContainerPtr& defs = getVendorOptionDefs(u, vendor_id);
149
150 if (!defs) {
151 return (OptionDefinitionPtr());
152 }
153
154 const OptionDefContainerNameIndex& idx = defs->get<2>();
155 const OptionDefContainerNameRange& range = idx.equal_range(name);
156 if (range.first != range.second) {
157 return (*range.first);
158 }
159
160 return (OptionDefinitionPtr());
161 }
162
163 OptionDefinitionPtr
getVendorOptionDef(const Option::Universe u,const uint32_t vendor_id,const uint16_t code)164 LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id,
165 const uint16_t code) {
166 const OptionDefContainerPtr& defs = getVendorOptionDefs(u, vendor_id);
167
168 if (!defs) {
169 // Weird universe or unknown vendor_id. We don't care. No definitions
170 // one way or another
171 // What is it anyway?
172 return (OptionDefinitionPtr());
173 }
174
175 const OptionDefContainerTypeIndex& idx = defs->get<1>();
176 const OptionDefContainerTypeRange& range = idx.equal_range(code);
177 if (range.first != range.second) {
178 return (*range.first);
179 }
180
181 return (OptionDefinitionPtr());
182 }
183
184 OptionDefinitionPtr
getRuntimeOptionDef(const std::string & space,const uint16_t code)185 LibDHCP::getRuntimeOptionDef(const std::string& space, const uint16_t code) {
186 OptionDefContainerPtr container = runtime_option_defs_.getValue().getItems(space);
187 const OptionDefContainerTypeIndex& index = container->get<1>();
188 const OptionDefContainerTypeRange& range = index.equal_range(code);
189 if (range.first != range.second) {
190 return (*range.first);
191 }
192
193 return (OptionDefinitionPtr());
194 }
195
196 OptionDefinitionPtr
getRuntimeOptionDef(const std::string & space,const std::string & name)197 LibDHCP::getRuntimeOptionDef(const std::string& space, const std::string& name) {
198 OptionDefContainerPtr container = runtime_option_defs_.getValue().getItems(space);
199 const OptionDefContainerNameIndex& index = container->get<2>();
200 const OptionDefContainerNameRange& range = index.equal_range(name);
201 if (range.first != range.second) {
202 return (*range.first);
203 }
204
205 return (OptionDefinitionPtr());
206 }
207
208 OptionDefContainerPtr
getRuntimeOptionDefs(const std::string & space)209 LibDHCP::getRuntimeOptionDefs(const std::string& space) {
210 return (runtime_option_defs_.getValue().getItems(space));
211 }
212
213 void
setRuntimeOptionDefs(const OptionDefSpaceContainer & defs)214 LibDHCP::setRuntimeOptionDefs(const OptionDefSpaceContainer& defs) {
215 OptionDefSpaceContainer defs_copy;
216 std::list<std::string> option_space_names = defs.getOptionSpaceNames();
217 for (std::list<std::string>::const_iterator name = option_space_names.begin();
218 name != option_space_names.end(); ++name) {
219 OptionDefContainerPtr container = defs.getItems(*name);
220 for (OptionDefContainer::const_iterator def = container->begin();
221 def != container->end(); ++def) {
222 OptionDefinitionPtr def_copy(new OptionDefinition(**def));
223 defs_copy.addItem(def_copy);
224 }
225 }
226 runtime_option_defs_ = defs_copy;
227 }
228
229 void
clearRuntimeOptionDefs()230 LibDHCP::clearRuntimeOptionDefs() {
231 runtime_option_defs_.reset();
232 }
233
234 void
revertRuntimeOptionDefs()235 LibDHCP::revertRuntimeOptionDefs() {
236 runtime_option_defs_.revert();
237 }
238
239 void
commitRuntimeOptionDefs()240 LibDHCP::commitRuntimeOptionDefs() {
241 runtime_option_defs_.commit();
242 }
243
244 OptionDefinitionPtr
getLastResortOptionDef(const std::string & space,const uint16_t code)245 LibDHCP::getLastResortOptionDef(const std::string& space, const uint16_t code) {
246 OptionDefContainerPtr container = getLastResortOptionDefs(space);
247 const OptionDefContainerTypeIndex& index = container->get<1>();
248 const OptionDefContainerTypeRange& range = index.equal_range(code);
249 if (range.first != range.second) {
250 return (*range.first);
251 }
252
253 return (OptionDefinitionPtr());
254 }
255
256 OptionDefinitionPtr
getLastResortOptionDef(const std::string & space,const std::string & name)257 LibDHCP::getLastResortOptionDef(const std::string& space, const std::string& name) {
258 OptionDefContainerPtr container = getLastResortOptionDefs(space);
259 const OptionDefContainerNameIndex& index = container->get<2>();
260 const OptionDefContainerNameRange& range = index.equal_range(name);
261 if (range.first != range.second) {
262 return (*range.first);
263 }
264
265 return (OptionDefinitionPtr());
266 }
267
268 OptionDefContainerPtr
getLastResortOptionDefs(const std::string & space)269 LibDHCP::getLastResortOptionDefs(const std::string& space) {
270 if (space == DHCP4_OPTION_SPACE) {
271 return getOptionDefs(LAST_RESORT_V4_OPTION_SPACE);
272 }
273
274 return (null_option_def_container_);
275 }
276
277 bool
shouldDeferOptionUnpack(const std::string & space,const uint16_t code)278 LibDHCP::shouldDeferOptionUnpack(const std::string& space, const uint16_t code) {
279 return ((space == DHCP4_OPTION_SPACE) &&
280 ((code == DHO_VENDOR_ENCAPSULATED_OPTIONS) ||
281 ((code >= 224) && (code <= 254))));
282 }
283
284 OptionPtr
optionFactory(Option::Universe u,uint16_t type,const OptionBuffer & buf)285 LibDHCP::optionFactory(Option::Universe u,
286 uint16_t type,
287 const OptionBuffer& buf) {
288 FactoryMap::iterator it;
289 if (u == Option::V4) {
290 it = v4factories_.find(type);
291 if (it == v4factories_.end()) {
292 isc_throw(BadValue, "factory function not registered "
293 "for DHCP v4 option type " << type);
294 }
295 } else if (u == Option::V6) {
296 it = v6factories_.find(type);
297 if (it == v6factories_.end()) {
298 isc_throw(BadValue, "factory function not registered "
299 "for DHCPv6 option type " << type);
300 }
301 } else {
302 isc_throw(BadValue, "invalid universe specified (expected "
303 "Option::V4 or Option::V6");
304 }
305 return (it->second(u, type, buf));
306 }
307
308
309 size_t
unpackOptions6(const OptionBuffer & buf,const std::string & option_space,isc::dhcp::OptionCollection & options,size_t * relay_msg_offset,size_t * relay_msg_len)310 LibDHCP::unpackOptions6(const OptionBuffer& buf,
311 const std::string& option_space,
312 isc::dhcp::OptionCollection& options,
313 size_t* relay_msg_offset /* = 0 */,
314 size_t* relay_msg_len /* = 0 */) {
315 size_t offset = 0;
316 size_t length = buf.size();
317 size_t last_offset = 0;
318
319 // Get the list of standard option definitions.
320 const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(option_space);
321 // Runtime option definitions for non standard option space and if
322 // the definition doesn't exist within the standard option definitions.
323 const OptionDefContainerPtr& runtime_option_defs = LibDHCP::getRuntimeOptionDefs(option_space);
324
325 // @todo Once we implement other option spaces we should add else clause
326 // here and gather option definitions for them. For now leaving option_defs
327 // empty will imply creation of generic Option.
328
329 // Get the search indexes #1. It allows to search for option definitions
330 // using option code.
331 const OptionDefContainerTypeIndex& idx = option_defs->get<1>();
332 const OptionDefContainerTypeIndex& runtime_idx = runtime_option_defs->get<1>();
333
334 // The buffer being read comprises a set of options, each starting with
335 // a two-byte type code and a two-byte length field.
336 while (offset < length) {
337 // Save the current offset for backtracking
338 last_offset = offset;
339
340 // Check if there is room for another option
341 if (offset + 4 > length) {
342 // Still something but smaller than an option
343 return (last_offset);
344 }
345
346 // Parse the option header
347 uint16_t opt_type = isc::util::readUint16(&buf[offset], 2);
348 offset += 2;
349
350 uint16_t opt_len = isc::util::readUint16(&buf[offset], 2);
351 offset += 2;
352
353 if (offset + opt_len > length) {
354 // We peeked at the option header of the next option, but
355 // discovered that it would end up beyond buffer end, so
356 // the option is truncated. Hence we can't parse
357 // it. Therefore we revert back by those bytes (as if
358 // we never parsed them).
359 //
360 // @note it is the responsibility of the caller to throw
361 // an exception on partial parsing
362 return (last_offset);
363 }
364
365 if (opt_type == D6O_RELAY_MSG && relay_msg_offset && relay_msg_len) {
366 // remember offset of the beginning of the relay-msg option
367 *relay_msg_offset = offset;
368 *relay_msg_len = opt_len;
369
370 // do not create that relay-msg option
371 offset += opt_len;
372 continue;
373 }
374
375 if (opt_type == D6O_VENDOR_OPTS) {
376 if (offset + 4 > length) {
377 // Truncated vendor-option. We expect at least
378 // 4 bytes for the enterprise-id field. Let's roll back
379 // option code + option length (4 bytes) and return.
380 return (last_offset);
381 }
382
383 // Parse this as vendor option
384 OptionPtr vendor_opt(new OptionVendor(Option::V6, buf.begin() + offset,
385 buf.begin() + offset + opt_len));
386 options.insert(std::make_pair(opt_type, vendor_opt));
387
388 offset += opt_len;
389 continue;
390 }
391
392 // Get all definitions with the particular option code. Note
393 // that option code is non-unique within this container
394 // however at this point we expect to get one option
395 // definition with the particular code. If more are returned
396 // we report an error.
397 OptionDefContainerTypeRange range;
398 // Number of option definitions returned.
399 size_t num_defs = 0;
400
401 // We previously did the lookup only for dhcp6 option space, but with the
402 // addition of S46 options, we now do it for every space.
403 range = idx.equal_range(opt_type);
404 num_defs = std::distance(range.first, range.second);
405
406 // Standard option definitions do not include the definition for
407 // our option or we're searching for non-standard option. Try to
408 // find the definition among runtime option definitions.
409 if (num_defs == 0) {
410 range = runtime_idx.equal_range(opt_type);
411 num_defs = std::distance(range.first, range.second);
412 }
413
414 OptionPtr opt;
415 if (num_defs > 1) {
416 // Multiple options of the same code are not supported right now!
417 isc_throw(isc::Unexpected, "Internal error: multiple option"
418 " definitions for option type " << opt_type <<
419 " returned. Currently it is not supported to initialize"
420 " multiple option definitions for the same option code."
421 " This will be supported once support for option spaces"
422 " is implemented");
423 } else if (num_defs == 0) {
424 // @todo Don't crash if definition does not exist because
425 // only a few option definitions are initialized right
426 // now. In the future we will initialize definitions for
427 // all options and we will remove this elseif. For now,
428 // return generic option.
429 opt = OptionPtr(new Option(Option::V6, opt_type,
430 buf.begin() + offset,
431 buf.begin() + offset + opt_len));
432 } else {
433 try {
434 // The option definition has been found. Use it to create
435 // the option instance from the provided buffer chunk.
436 const OptionDefinitionPtr& def = *(range.first);
437 isc_throw_assert(def);
438 opt = def->optionFactory(Option::V6, opt_type,
439 buf.begin() + offset,
440 buf.begin() + offset + opt_len);
441 } catch (const SkipThisOptionError&) {
442 opt.reset();
443 }
444 }
445
446 // add option to options
447 if (opt) {
448 options.insert(std::make_pair(opt_type, opt));
449 }
450
451 offset += opt_len;
452 }
453
454 last_offset = offset;
455 return (last_offset);
456 }
457
458 size_t
unpackOptions4(const OptionBuffer & buf,const std::string & option_space,isc::dhcp::OptionCollection & options,std::list<uint16_t> & deferred,bool flexible_pad_end)459 LibDHCP::unpackOptions4(const OptionBuffer& buf,
460 const std::string& option_space,
461 isc::dhcp::OptionCollection& options,
462 std::list<uint16_t>& deferred,
463 bool flexible_pad_end) {
464 size_t offset = 0;
465 size_t last_offset = 0;
466
467 // Special case when option_space is dhcp4.
468 bool space_is_dhcp4 = (option_space == DHCP4_OPTION_SPACE);
469
470 // Get the list of standard option definitions.
471 const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(option_space);
472 // Runtime option definitions for non standard option space and if
473 // the definition doesn't exist within the standard option definitions.
474 const OptionDefContainerPtr& runtime_option_defs = LibDHCP::getRuntimeOptionDefs(option_space);
475
476 // Get the search indexes #1. It allows to search for option definitions
477 // using option code.
478 const OptionDefContainerTypeIndex& idx = option_defs->get<1>();
479 const OptionDefContainerTypeIndex& runtime_idx = runtime_option_defs->get<1>();
480
481 // Flexible PAD and END parsing.
482 bool flex_pad = (flexible_pad_end && (runtime_idx.count(DHO_PAD) == 0));
483 bool flex_end = (flexible_pad_end && (runtime_idx.count(DHO_END) == 0));
484
485 // The buffer being read comprises a set of options, each starting with
486 // a one-byte type code and a one-byte length field.
487 while (offset < buf.size()) {
488 // Save the current offset for backtracking
489 last_offset = offset;
490
491 // Get the option type
492 uint8_t opt_type = buf[offset++];
493
494 // DHO_END is a special, one octet long option
495 // Valid in dhcp4 space or when flexible_pad_end is true and
496 // there is a sub-option configured for this code.
497 if ((opt_type == DHO_END) && (space_is_dhcp4 || flex_end)) {
498 // just return. Don't need to add DHO_END option
499 // Don't return offset because it makes this condition
500 // and partial parsing impossible to recognize.
501 return (last_offset);
502 }
503
504 // DHO_PAD is just a padding after DHO_END. Let's continue parsing
505 // in case we receive a message without DHO_END.
506 // Valid in dhcp4 space or when flexible_pad_end is true and
507 // there is a sub-option configured for this code.
508 if ((opt_type == DHO_PAD) && (space_is_dhcp4 || flex_pad)) {
509 continue;
510 }
511
512 if (offset + 1 > buf.size()) {
513 // We peeked at the option header of the next option, but
514 // discovered that it would end up beyond buffer end, so
515 // the option is truncated. Hence we can't parse
516 // it. Therefore we revert back (as if we never parsed it).
517 //
518 // @note it is the responsibility of the caller to throw
519 // an exception on partial parsing
520 return (last_offset);
521 }
522
523 uint8_t opt_len = buf[offset++];
524 if (offset + opt_len > buf.size()) {
525 // We peeked at the option header of the next option, but
526 // discovered that it would end up beyond buffer end, so
527 // the option is truncated. Hence we can't parse
528 // it. Therefore we revert back (as if we never parsed it).
529 return (last_offset);
530 }
531
532 // While an empty Host Name option is non-RFC compliant, some clients
533 // do send it. In the spirit of being liberal, we'll just drop it,
534 // rather than the dropping the whole packet. We do not have a
535 // way to log this from here but meh... a PCAP will show it arriving,
536 // and we know we drop it.
537 if (space_is_dhcp4 && opt_len == 0 && opt_type == DHO_HOST_NAME) {
538 continue;
539 }
540
541 // Get all definitions with the particular option code. Note
542 // that option code is non-unique within this container
543 // however at this point we expect to get one option
544 // definition with the particular code. If more are returned
545 // we report an error.
546 OptionDefContainerTypeRange range;
547 // Number of option definitions returned.
548 size_t num_defs = 0;
549
550 // Previously we did the lookup only for "dhcp4" option space, but there
551 // may be standard options in other spaces (e.g. radius). So we now do
552 // the lookup for every space.
553 range = idx.equal_range(opt_type);
554 num_defs = std::distance(range.first, range.second);
555
556 // Standard option definitions do not include the definition for
557 // our option or we're searching for non-standard option. Try to
558 // find the definition among runtime option definitions.
559 if (num_defs == 0) {
560 range = runtime_idx.equal_range(opt_type);
561 num_defs = std::distance(range.first, range.second);
562 }
563
564 // Check if option unpacking must be deferred
565 if (shouldDeferOptionUnpack(option_space, opt_type)) {
566 num_defs = 0;
567 deferred.push_back(opt_type);
568 }
569
570 OptionPtr opt;
571 if (num_defs > 1) {
572 // Multiple options of the same code are not supported right now!
573 isc_throw(isc::Unexpected, "Internal error: multiple option"
574 " definitions for option type " <<
575 static_cast<int>(opt_type) <<
576 " returned. Currently it is not supported to initialize"
577 " multiple option definitions for the same option code."
578 " This will be supported once support for option spaces"
579 " is implemented");
580 } else if (num_defs == 0) {
581 opt = OptionPtr(new Option(Option::V4, opt_type,
582 buf.begin() + offset,
583 buf.begin() + offset + opt_len));
584 opt->setEncapsulatedSpace(DHCP4_OPTION_SPACE);
585 } else {
586 try {
587 // The option definition has been found. Use it to create
588 // the option instance from the provided buffer chunk.
589 const OptionDefinitionPtr& def = *(range.first);
590 isc_throw_assert(def);
591 opt = def->optionFactory(Option::V4, opt_type,
592 buf.begin() + offset,
593 buf.begin() + offset + opt_len);
594 } catch (const SkipThisOptionError&) {
595 opt.reset();
596 }
597 }
598
599 // If we have the option, insert it
600 if (opt) {
601 options.insert(std::make_pair(opt_type, opt));
602 }
603
604 offset += opt_len;
605 }
606 last_offset = offset;
607 return (last_offset);
608 }
609
610 size_t
unpackVendorOptions6(const uint32_t vendor_id,const OptionBuffer & buf,isc::dhcp::OptionCollection & options)611 LibDHCP::unpackVendorOptions6(const uint32_t vendor_id,
612 const OptionBuffer& buf,
613 isc::dhcp::OptionCollection& options) {
614 size_t offset = 0;
615 size_t length = buf.size();
616
617 // Get the list of option definitions for this particular vendor-id
618 const OptionDefContainerPtr& option_defs =
619 LibDHCP::getVendorOptionDefs(Option::V6, vendor_id);
620
621 // Get the search index #1. It allows to search for option definitions
622 // using option code. If there's no such vendor-id space, we're out of luck
623 // anyway.
624 const OptionDefContainerTypeIndex* idx = NULL;
625 if (option_defs) {
626 idx = &(option_defs->get<1>());
627 }
628
629 // The buffer being read comprises a set of options, each starting with
630 // a two-byte type code and a two-byte length field.
631 while (offset < length) {
632 if (offset + 4 > length) {
633 isc_throw(SkipRemainingOptionsError,
634 "Vendor option parse failed: truncated header");
635 }
636
637 uint16_t opt_type = isc::util::readUint16(&buf[offset], 2);
638 offset += 2;
639
640 uint16_t opt_len = isc::util::readUint16(&buf[offset], 2);
641 offset += 2;
642
643 if (offset + opt_len > length) {
644 isc_throw(SkipRemainingOptionsError,
645 "Vendor option parse failed. Tried to parse "
646 << offset + opt_len << " bytes from " << length
647 << "-byte long buffer.");
648 }
649
650 OptionPtr opt;
651 opt.reset();
652
653 // If there is a definition for such a vendor option...
654 if (idx) {
655 // Get all definitions with the particular option
656 // code. Note that option code is non-unique within this
657 // container however at this point we expect to get one
658 // option definition with the particular code. If more are
659 // returned we report an error.
660 const OptionDefContainerTypeRange& range =
661 idx->equal_range(opt_type);
662 // Get the number of returned option definitions for the
663 // option code.
664 size_t num_defs = std::distance(range.first, range.second);
665
666 if (num_defs > 1) {
667 // Multiple options of the same code are not supported
668 // right now!
669 isc_throw(isc::Unexpected, "Internal error: multiple option"
670 " definitions for option type " << opt_type <<
671 " returned. Currently it is not supported to"
672 " initialize multiple option definitions for the"
673 " same option code. This will be supported once"
674 " support for option spaces is implemented");
675 } else if (num_defs == 1) {
676 // The option definition has been found. Use it to create
677 // the option instance from the provided buffer chunk.
678 const OptionDefinitionPtr& def = *(range.first);
679 isc_throw_assert(def);
680 opt = def->optionFactory(Option::V6, opt_type,
681 buf.begin() + offset,
682 buf.begin() + offset + opt_len);
683 }
684 }
685
686 // This can happen in one of 2 cases:
687 // 1. we do not have definitions for that vendor-space
688 // 2. we do have definitions, but that particular option was
689 // not defined
690
691 if (!opt) {
692 opt = OptionPtr(new Option(Option::V6, opt_type,
693 buf.begin() + offset,
694 buf.begin() + offset + opt_len));
695 }
696
697 // add option to options
698 if (opt) {
699 options.insert(std::make_pair(opt_type, opt));
700 }
701 offset += opt_len;
702 }
703
704 return (offset);
705 }
706
707 size_t
unpackVendorOptions4(const uint32_t vendor_id,const OptionBuffer & buf,isc::dhcp::OptionCollection & options)708 LibDHCP::unpackVendorOptions4(const uint32_t vendor_id, const OptionBuffer& buf,
709 isc::dhcp::OptionCollection& options) {
710 size_t offset = 0;
711
712 // Get the list of standard option definitions.
713 const OptionDefContainerPtr& option_defs =
714 LibDHCP::getVendorOptionDefs(Option::V4, vendor_id);
715 // Get the search index #1. It allows to search for option definitions
716 // using option code.
717 const OptionDefContainerTypeIndex* idx = NULL;
718 if (option_defs) {
719 idx = &(option_defs->get<1>());
720 }
721
722 // The buffer being read comprises a set of options, each starting with
723 // a one-byte type code and a one-byte length field.
724 while (offset < buf.size()) {
725 // Note that Vendor-Specific info option (RFC3925) has a
726 // different option format than Vendor-Spec info for
727 // DHCPv6. (there's additional layer of data-length)
728 uint8_t data_len = buf[offset++];
729
730 if (offset + data_len > buf.size()) {
731 // The option is truncated.
732 isc_throw(SkipRemainingOptionsError,
733 "Attempt to parse truncated vendor option");
734 }
735
736 uint8_t offset_end = offset + data_len;
737
738 // beginning of data-chunk parser
739 while (offset < offset_end) {
740 uint8_t opt_type = buf[offset++];
741
742 // No DHO_END or DHO_PAD in vendor options
743
744 if (offset + 1 > offset_end) {
745 // opt_type must be cast to integer so as it is not
746 // treated as unsigned char value (a number is
747 // presented in error message).
748 isc_throw(SkipRemainingOptionsError,
749 "Attempt to parse truncated vendor option "
750 << static_cast<int>(opt_type));
751 }
752
753 uint8_t opt_len = buf[offset++];
754 if (offset + opt_len > offset_end) {
755 isc_throw(SkipRemainingOptionsError,
756 "Option parse failed. Tried to parse "
757 << offset + opt_len << " bytes from " << buf.size()
758 << "-byte long buffer.");
759 }
760
761 OptionPtr opt;
762 opt.reset();
763
764 if (idx) {
765 // Get all definitions with the particular option
766 // code. Note that option code is non-unique within
767 // this container however at this point we expect to
768 // get one option definition with the particular
769 // code. If more are returned we report an error.
770 const OptionDefContainerTypeRange& range =
771 idx->equal_range(opt_type);
772 // Get the number of returned option definitions for
773 // the option code.
774 size_t num_defs = std::distance(range.first, range.second);
775
776 if (num_defs > 1) {
777 // Multiple options of the same code are not
778 // supported right now!
779 isc_throw(isc::Unexpected, "Internal error: multiple"
780 " option definitions for option type "
781 << opt_type << " returned. Currently it is"
782 " not supported to initialize multiple option"
783 " definitions for the same option code."
784 " This will be supported once support for"
785 " option spaces is implemented");
786 } else if (num_defs == 1) {
787 // The option definition has been found. Use it to create
788 // the option instance from the provided buffer chunk.
789 const OptionDefinitionPtr& def = *(range.first);
790 isc_throw_assert(def);
791 opt = def->optionFactory(Option::V4, opt_type,
792 buf.begin() + offset,
793 buf.begin() + offset + opt_len);
794 }
795 }
796
797 if (!opt) {
798 opt = OptionPtr(new Option(Option::V4, opt_type,
799 buf.begin() + offset,
800 buf.begin() + offset + opt_len));
801 }
802
803 options.insert(std::make_pair(opt_type, opt));
804 offset += opt_len;
805
806 } // end of data-chunk
807
808 break; // end of the vendor block.
809 }
810 return (offset);
811 }
812
813 void
packOptions4(isc::util::OutputBuffer & buf,const OptionCollection & options,bool top)814 LibDHCP::packOptions4(isc::util::OutputBuffer& buf,
815 const OptionCollection& options,
816 bool top /* = false */) {
817 OptionPtr agent;
818 OptionPtr end;
819
820 // We only look for type when we're the top level
821 // call that starts packing for options for a packet.
822 // This way we avoid doing type logic in all ensuing
823 // recursive calls.
824 if (top) {
825 auto x = options.find(DHO_DHCP_MESSAGE_TYPE);
826 if (x != options.end()) {
827 x->second->pack(buf);
828 }
829 }
830
831 for (OptionCollection::const_iterator it = options.begin();
832 it != options.end(); ++it) {
833
834 // TYPE is already done, RAI and END options must be last.
835 switch (it->first) {
836 case DHO_DHCP_MESSAGE_TYPE:
837 break;
838 case DHO_DHCP_AGENT_OPTIONS:
839 agent = it->second;
840 break;
841 case DHO_END:
842 end = it->second;
843 break;
844 default:
845 it->second->pack(buf);
846 break;
847 }
848 }
849
850 // Add the RAI option if it exists.
851 if (agent) {
852 agent->pack(buf);
853 }
854
855 // And at the end the END option.
856 if (end) {
857 end->pack(buf);
858 }
859 }
860
861 void
packOptions6(isc::util::OutputBuffer & buf,const OptionCollection & options)862 LibDHCP::packOptions6(isc::util::OutputBuffer& buf,
863 const OptionCollection& options) {
864 for (OptionCollection::const_iterator it = options.begin();
865 it != options.end(); ++it) {
866 it->second->pack(buf);
867 }
868 }
869
870 void
OptionFactoryRegister(Option::Universe u,uint16_t opt_type,Option::Factory * factory)871 LibDHCP::OptionFactoryRegister(Option::Universe u,
872 uint16_t opt_type,
873 Option::Factory* factory) {
874 switch (u) {
875 case Option::V6:
876 {
877 if (v6factories_.find(opt_type) != v6factories_.end()) {
878 isc_throw(BadValue, "There is already DHCPv6 factory registered "
879 << "for option type " << opt_type);
880 }
881 v6factories_[opt_type] = factory;
882 return;
883 }
884 case Option::V4:
885 {
886 // Option 0 is special (a one octet-long, equal 0) PAD option. It is never
887 // instantiated as an Option object, but rather consumed during packet parsing.
888 if (opt_type == 0) {
889 isc_throw(BadValue, "Cannot redefine PAD option (code=0)");
890 }
891 // Option 255 is never instantiated as an option object. It is special
892 // (a one-octet equal 255) option that is added at the end of all options
893 // during packet assembly. It is also silently consumed during packet parsing.
894 if (opt_type > 254) {
895 isc_throw(BadValue, "Too big option type for DHCPv4, only 0-254 allowed.");
896 }
897 if (v4factories_.find(opt_type) != v4factories_.end()) {
898 isc_throw(BadValue, "There is already DHCPv4 factory registered "
899 << "for option type " << opt_type);
900 }
901 v4factories_[opt_type] = factory;
902 return;
903 }
904 default:
905 isc_throw(BadValue, "Invalid universe type specified.");
906 }
907
908 return;
909 }
910
911 bool
initOptionDefs()912 LibDHCP::initOptionDefs() {
913 for (uint32_t i = 0; OPTION_DEF_PARAMS[i].optionDefParams; ++i) {
914 std::string space = OPTION_DEF_PARAMS[i].space;
915 option_defs_[space] = OptionDefContainerPtr(new OptionDefContainer);
916 initOptionSpace(option_defs_[space],
917 OPTION_DEF_PARAMS[i].optionDefParams,
918 OPTION_DEF_PARAMS[i].size);
919 }
920
921 return (true);
922 }
923
924 uint32_t
optionSpaceToVendorId(const std::string & option_space)925 LibDHCP::optionSpaceToVendorId(const std::string& option_space) {
926 // 8 is a minimal length of "vendor-X" format
927 if ((option_space.size() < 8) || (option_space.substr(0,7) != "vendor-")) {
928 return (0);
929 }
930
931 int64_t check;
932 try {
933 // text after "vendor-", supposedly numbers only
934 std::string x = option_space.substr(7);
935
936 check = boost::lexical_cast<int64_t>(x);
937 } catch (const boost::bad_lexical_cast &) {
938 return (0);
939 }
940
941 if ((check < 0) || (check > std::numeric_limits<uint32_t>::max())) {
942 return (0);
943 }
944
945 // value is small enough to fit
946 return (static_cast<uint32_t>(check));
947 }
948
949 void
initOptionSpace(OptionDefContainerPtr & defs,const OptionDefParams * params,size_t params_size)950 initOptionSpace(OptionDefContainerPtr& defs,
951 const OptionDefParams* params,
952 size_t params_size) {
953 // Container holding vendor options is typically not initialized, as it
954 // is held in map of null pointers. We need to initialize here in this
955 // case.
956 if (!defs) {
957 defs.reset(new OptionDefContainer());
958 } else {
959 defs->clear();
960 }
961
962 for (size_t i = 0; i < params_size; ++i) {
963 std::string encapsulates(params[i].encapsulates);
964 if (!encapsulates.empty() && params[i].array) {
965 isc_throw(isc::BadValue, "invalid standard option definition: "
966 << "option with code '" << params[i].code
967 << "' may not encapsulate option space '"
968 << encapsulates << "' because the definition"
969 << " indicates that this option comprises an array"
970 << " of values");
971 }
972
973 // Depending whether an option encapsulates an option space or not
974 // we pick different constructor to create an instance of the option
975 // definition.
976 OptionDefinitionPtr definition;
977 if (encapsulates.empty()) {
978 // Option does not encapsulate any option space.
979 definition.reset(new OptionDefinition(params[i].name,
980 params[i].code,
981 params[i].space,
982 params[i].type,
983 params[i].array));
984 } else {
985 // Option does encapsulate an option space.
986 definition.reset(new OptionDefinition(params[i].name,
987 params[i].code,
988 params[i].space,
989 params[i].type,
990 params[i].encapsulates));
991
992 }
993
994 for (size_t rec = 0; rec < params[i].records_size; ++rec) {
995 definition->addRecordField(params[i].records[rec]);
996 }
997
998 try {
999 definition->validate();
1000 } catch (const isc::Exception&) {
1001 // This is unlikely event that validation fails and may
1002 // be only caused by programming error. To guarantee the
1003 // data consistency we clear all option definitions that
1004 // have been added so far and pass the exception forward.
1005 defs->clear();
1006 throw;
1007 }
1008
1009 // option_defs is a multi-index container with no unique indexes
1010 // so push_back can't fail).
1011 static_cast<void>(defs->push_back(definition));
1012 }
1013 }
1014