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