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 #ifndef OPTION_H
8 #define OPTION_H
9 
10 #include <util/buffer.h>
11 
12 #include <boost/shared_ptr.hpp>
13 
14 #include <map>
15 #include <string>
16 #include <vector>
17 
18 namespace isc {
19 namespace dhcp {
20 
21 /// @brief buffer types used in DHCP code.
22 ///
23 /// Dereferencing OptionBuffer iterator will point out to contiguous memory.
24 typedef std::vector<uint8_t> OptionBuffer;
25 
26 /// iterator for walking over OptionBuffer
27 typedef OptionBuffer::iterator OptionBufferIter;
28 
29 /// const_iterator for walking over OptionBuffer
30 typedef OptionBuffer::const_iterator OptionBufferConstIter;
31 
32 /// pointer to a DHCP buffer
33 typedef boost::shared_ptr<OptionBuffer> OptionBufferPtr;
34 
35 /// shared pointer to Option object
36 class Option;
37 typedef boost::shared_ptr<Option> OptionPtr;
38 
39 /// A collection of DHCP (v4 or v6) options
40 typedef std::multimap<unsigned int, OptionPtr> OptionCollection;
41 /// A pointer to an OptionCollection
42 typedef boost::shared_ptr<OptionCollection> OptionCollectionPtr;
43 
44 /// @brief Exception thrown during option unpacking
45 /// This exception is thrown when an error has occurred, unpacking
46 /// an option from a packet and we wish to abandon any any further
47 /// unpacking efforts and allow the server to attempt to process
48 /// the packet as it stands.  In other words, the option that failed
49 /// is perhaps optional, and rather than drop the packet as unusable
50 /// we wish to attempt to process it.
51 class SkipRemainingOptionsError : public Exception {
52 public:
SkipRemainingOptionsError(const char * file,size_t line,const char * what)53     SkipRemainingOptionsError (const char* file, size_t line, const char* what) :
54         isc::Exception(file, line, what) { };
55 };
56 
57 /// @brief Exception thrown during option unpacking
58 /// This exception is thrown when an error has occurred unpacking
59 /// an option from a packet and rather than drop the whole packet, we
60 /// wish to simply skip over the option (i.e. omit it from the unpacked
61 /// results), and resume unpacking with the next option in the buffer.
62 /// The intent is to allow us to be liberal with what we receive, and
63 /// skip nonsensical options rather than drop the whole packet. This
64 /// exception is thrown, for instance, when string options are found to
65 /// be empty or to contain only nuls.
66 class SkipThisOptionError : public Exception {
67 public:
SkipThisOptionError(const char * file,size_t line,const char * what)68     SkipThisOptionError (const char* file, size_t line, const char* what) :
69         isc::Exception(file, line, what) { };
70 };
71 
72 
73 class Option {
74 public:
75     /// length of the usual DHCPv4 option header (there are exceptions)
76     const static size_t OPTION4_HDR_LEN = 2;
77 
78     /// length of any DHCPv6 option header
79     const static size_t OPTION6_HDR_LEN = 4;
80 
81     /// defines option universe DHCPv4 or DHCPv6
82     enum Universe { V4, V6 };
83 
84 
85     /// @brief a factory function prototype
86     ///
87     /// @param u option universe (DHCPv4 or DHCPv6)
88     /// @param type option type
89     /// @param buf pointer to a buffer
90     ///
91     /// @todo Passing a separate buffer for each option means that a copy
92     ///       was done. We can avoid it by passing 2 iterators.
93     ///
94     /// @return a pointer to a created option object
95     typedef OptionPtr Factory(Option::Universe u, uint16_t type, const OptionBuffer& buf);
96 
97     /// @brief Factory function to create instance of option.
98     ///
99     /// Factory method creates instance of specified option. The option
100     /// to be created has to have corresponding factory function
101     /// registered with \ref LibDHCP::OptionFactoryRegister.
102     ///
103     /// @param u universe of the option (V4 or V6)
104     /// @param type option-type
105     /// @param buf option-buffer
106     ///
107     /// @return instance of option.
108     ///
109     /// @throw isc::InvalidOperation if there is no factory function
110     ///        registered for specified option type.
111     static OptionPtr factory(Option::Universe u,
112                              uint16_t type,
113                              const OptionBuffer& buf);
114 
115     /// @brief Factory function to create instance of option.
116     ///
117     /// Factory method creates instance of specified option. The option
118     /// to be created has to have corresponding factory function
119     /// registered with \ref LibDHCP::OptionFactoryRegister.
120     /// This method creates empty \ref OptionBuffer object. Use this
121     /// factory function if it is not needed to pass custom buffer.
122     ///
123     /// @param u universe of the option (V4 or V6)
124     /// @param type option-type
125     ///
126     /// @return instance of option.
127     ///
128     /// @throw isc::InvalidOperation if there is no factory function
129     ///        registered for specified option type.
factory(Option::Universe u,uint16_t type)130     static OptionPtr factory(Option::Universe u, uint16_t type) {
131         return factory(u, type, OptionBuffer());
132     }
133 
134     /// @brief ctor, used for options constructed, usually during transmission
135     ///
136     /// @param u option universe (DHCPv4 or DHCPv6)
137     /// @param type option type
138     Option(Universe u, uint16_t type);
139 
140     /// @brief Constructor, used for received options.
141     ///
142     /// This constructor takes vector<uint8_t>& which is used in cases
143     /// when content of the option will be copied and stored within
144     /// option object. V4 Options follow that approach already.
145     /// @todo Migrate V6 options to that approach.
146     ///
147     /// @param u specifies universe (V4 or V6)
148     /// @param type option type (0-255 for V4 and 0-65535 for V6)
149     /// @param data content of the option
150     Option(Universe u, uint16_t type, const OptionBuffer& data);
151 
152     /// @brief Constructor, used for received options.
153     ///
154     /// This constructor is similar to the previous one, but it does not take
155     /// the whole vector<uint8_t>, but rather subset of it.
156     ///
157     /// @todo This can be templated to use different containers, not just
158     /// vector. Prototype should look like this:
159     /// template<typename InputIterator> Option(Universe u, uint16_t type,
160     /// InputIterator first, InputIterator last);
161     ///
162     /// vector<int8_t> myData;
163     /// Example usage: new Option(V4, 123, myData.begin()+1, myData.end()-1)
164     /// This will create DHCPv4 option of type 123 that contains data from
165     /// trimmed (first and last byte removed) myData vector.
166     ///
167     /// @param u specifies universe (V4 or V6)
168     /// @param type option type (0-255 for V4 and 0-65535 for V6)
169     /// @param first iterator to the first element that should be copied
170     /// @param last iterator to the next element after the last one
171     ///        to be copied.
172     Option(Universe u, uint16_t type, OptionBufferConstIter first,
173            OptionBufferConstIter last);
174 
175     /// @brief Copy constructor.
176     ///
177     /// This constructor makes a deep copy of the option and all of the
178     /// suboptions. It calls @ref getOptionsCopy to deep copy suboptions.
179     ///
180     /// @param source Option to be copied.
181     Option(const Option& source);
182 
183     /// @brief Factory function creating an instance of the @c Option.
184     ///
185     /// This function should be used to create an instance of the DHCP
186     /// option within a hooks library in cases when the library may be
187     /// unloaded before the object is destroyed. This ensures that the
188     /// ownership of the object by the Kea process is retained.
189     ///
190     /// @param u specifies universe (V4 or V6)
191     /// @param type option type (0-255 for V4 and 0-65535 for V6)
192     ///
193     /// @return Pointer to the @c Option instance.
194     static OptionPtr create(Universe u, uint16_t type);
195 
196     /// @brief Factory function creating an instance of the @c Option.
197     ///
198     /// This function should be used to create an instance of the DHCP
199     /// option within a hooks library in cases when the library may be
200     /// unloaded before the object is destroyed. This ensures that the
201     /// ownership of the object by the Kea process is retained.
202     ///
203     /// @param u specifies universe (V4 or V6)
204     /// @param type option type (0-255 for V4 and 0-65535 for V6)
205     /// @param data content of the option
206     ///
207     /// @return Pointer to the @c Option instance.
208     static OptionPtr create(Universe u, uint16_t type, const OptionBuffer& data);
209 
210     /// @brief Assignment operator.
211     ///
212     /// The assignment operator performs a deep copy of the option and
213     /// its suboptions. It calls @ref getOptionsCopy to deep copy
214     /// suboptions.
215     ///
216     /// @param rhs Option to be assigned.
217     Option& operator=(const Option& rhs);
218 
219     /// @brief Copies this option and returns a pointer to the copy.
220     ///
221     /// This function must be overridden in the derived classes to make
222     /// a copy of the derived type. The simplest way to do it is by
223     /// calling @ref cloneInternal function with an appropriate template
224     /// parameter.
225     ///
226     /// @return Pointer to the copy of the option.
227     virtual OptionPtr clone() const;
228 
229     /// @brief returns option universe (V4 or V6)
230     ///
231     /// @return universe type
getUniverse()232     Universe  getUniverse() const { return universe_; };
233 
234     /// @brief Writes option in wire-format to a buffer.
235     ///
236     /// Writes option in wire-format to buffer, returns pointer to first unused
237     /// byte after stored option (that is useful for writing options one after
238     /// another).
239     ///
240     /// @param buf pointer to a buffer
241     ///
242     /// @throw BadValue Universe of the option is neither V4 nor V6.
243     virtual void pack(isc::util::OutputBuffer& buf) const;
244 
245     /// @brief Parses received buffer.
246     ///
247     /// @param begin iterator to first byte of option data
248     /// @param end iterator to end of option data (first byte after option end)
249     virtual void unpack(OptionBufferConstIter begin,
250                         OptionBufferConstIter end);
251 
252     /// Returns string representation of the option.
253     ///
254     /// @param indent number of spaces before printing text
255     ///
256     /// @return string with text representation.
257     virtual std::string toText(int indent = 0) const;
258 
259     /// @brief Returns string representation of the value
260     ///
261     /// This is terse representation used in cases where client classification
262     /// refers to a specific option.
263     ///
264     /// @return string that represents the value of the option.
265     virtual std::string toString() const;
266 
267     /// @brief Returns binary representation of the option.
268     ///
269     /// @param include_header Boolean flag which indicates if the output should
270     /// also contain header fields. The default is that it shouldn't include
271     /// header fields.
272     ///
273     /// @return Vector holding binary representation of the option.
274     virtual std::vector<uint8_t> toBinary(const bool include_header = false) const;
275 
276     /// @brief Returns string containing hexadecimal representation of option.
277     ///
278     /// @param include_header Boolean flag which indicates if the output should
279     /// also contain header fields. The default is that it shouldn't include
280     /// header fields.
281     ///
282     /// @return String containing hexadecimal representation of the option.
283     virtual std::string toHexString(const bool include_header = false) const;
284 
285     /// Returns option type (0-255 for DHCPv4, 0-65535 for DHCPv6)
286     ///
287     /// @return option type
getType()288     uint16_t getType() const { return (type_); }
289 
290     /// Returns length of the complete option (data length + DHCPv4/DHCPv6
291     /// option header)
292     ///
293     /// @return length of the option
294     virtual uint16_t len() const;
295 
296     /// @brief Returns length of header (2 for v4, 4 for v6)
297     ///
298     /// @return length of option header
299     virtual uint16_t getHeaderLen() const;
300 
301     /// returns if option is valid (e.g. option may be truncated)
302     ///
303     /// @return true, if option is valid
304     virtual bool valid() const;
305 
306     /// Returns pointer to actual data.
307     ///
308     /// @return pointer to actual data (or reference to an empty vector
309     ///         if there is no data)
getData()310     virtual const OptionBuffer& getData() const { return (data_); }
311 
312     /// Adds a sub-option.
313     ///
314     /// Some DHCPv6 options can have suboptions. This method allows adding
315     /// options within options.
316     ///
317     /// Note: option is passed by value. That is very convenient as it allows
318     /// downcasting from any derived classes, e.g. shared_ptr<Option6_IA> type
319     /// can be passed directly, without any casts. That would not be possible
320     /// with passing by reference. addOption() is expected to be used in
321     /// many places. Requiring casting is not feasible.
322     ///
323     /// @param opt shared pointer to a suboption that is going to be added.
324     void addOption(OptionPtr opt);
325 
326     /// Returns shared_ptr to suboption of specific type
327     ///
328     /// @param type type of requested suboption
329     ///
330     /// @return shared_ptr to requested suboption
331     OptionPtr getOption(uint16_t type) const;
332 
333     /// @brief Returns all encapsulated options.
334     ///
335     /// @warning This function returns a reference to the container holding
336     /// encapsulated options, which is valid as long as the object which
337     /// returned it exists.
getOptions()338     const OptionCollection& getOptions() const {
339         return (options_);
340     }
341 
342     /// @brief Performs deep copy of suboptions.
343     ///
344     /// This method calls @ref clone method to deep copy each option.
345     ///
346     /// @param [out] options_copy Container where copied options are stored.
347     void getOptionsCopy(OptionCollection& options_copy) const;
348 
349     /// Attempts to delete first suboption of requested type
350     ///
351     /// @param type Type of option to be deleted.
352     ///
353     /// @return true if option was deleted, false if no such option existed
354     bool delOption(uint16_t type);
355 
356     /// @brief Returns content of first byte.
357     ///
358     /// @throw isc::OutOfRange Thrown if the option has a length of 0.
359     ///
360     /// @return value of the first byte
361     uint8_t getUint8() const;
362 
363     /// @brief Returns content of first word.
364     ///
365     /// @throw isc::OutOfRange Thrown if the option has a length less than 2.
366     ///
367     /// @return uint16_t value stored on first two bytes
368     uint16_t getUint16() const;
369 
370     /// @brief Returns content of first double word.
371     ///
372     /// @throw isc::OutOfRange Thrown if the option has a length less than 4.
373     ///
374     /// @return uint32_t value stored on first four bytes
375     uint32_t getUint32() const;
376 
377     /// @brief Sets content of this option to a single uint8 value.
378     ///
379     /// Option it resized appropriately (to length of 1 octet).
380     ///
381     /// @param value value to be set
382     void setUint8(uint8_t value);
383 
384     /// @brief Sets content of this option to a single uint16 value.
385     ///
386     /// Option it resized appropriately (to length of 2 octets).
387     ///
388     /// @param value value to be set
389     void setUint16(uint16_t value);
390 
391     /// @brief Sets content of this option to a single uint32 value.
392     ///
393     /// Option it resized appropriately (to length of 4 octets).
394     ///
395     /// @param value value to be set
396     void setUint32(uint32_t value);
397 
398     /// @brief Sets content of this option from buffer.
399     ///
400     /// Option will be resized to length of buffer.
401     ///
402     /// @param first iterator pointing to beginning of buffer to copy.
403     /// @param last iterator pointing to end of buffer to copy.
404     ///
405     /// @tparam InputIterator type of the iterator representing the
406     /// limits of the buffer to be assigned to a data_ buffer.
407     template<typename InputIterator>
setData(InputIterator first,InputIterator last)408     void setData(InputIterator first, InputIterator last) {
409         data_.assign(first, last);
410     }
411 
412     /// @brief Sets the name of the option space encapsulated by this option.
413     ///
414     /// @param encapsulated_space name of the option space encapsulated by
415     /// this option.
setEncapsulatedSpace(const std::string & encapsulated_space)416     void setEncapsulatedSpace(const std::string& encapsulated_space) {
417         encapsulated_space_ = encapsulated_space;
418     }
419 
420     /// @brief Returns the name of the option space encapsulated by this option.
421     ///
422     /// @return name of the option space encapsulated by this option.
getEncapsulatedSpace()423     std::string getEncapsulatedSpace() const {
424         return (encapsulated_space_);
425     }
426 
427     /// just to force that every option has virtual dtor
428     virtual ~Option();
429 
430     /// @brief Checks if options are equal.
431     ///
432     /// This method calls a virtual @c equals function to compare objects.
433     /// This method is not meant to be overridden in the derived classes.
434     /// Instead, the other @c equals function must be overridden.
435     ///
436     /// @param other Pointer to the option to compare this option to.
437     /// @return true if both options are equal, false otherwise.
438     bool equals(const OptionPtr& other) const;
439 
440     /// @brief Checks if two options are equal.
441     ///
442     /// Equality verifies option type and option content. Care should
443     /// be taken when using this method. Implementation for derived
444     /// classes should be provided when this method is expected to be
445     /// used. It is safe in general, as the first check (different types)
446     /// will detect differences between base Option and derived
447     /// objects.
448     ///
449     /// @param other Instance of the option to compare to.
450     ///
451     /// @return true if options are equal, false otherwise.
452     virtual bool equals(const Option& other) const;
453 
454     /// @brief Governs whether options should be parsed less strictly.
455     ///
456     /// Populated on configuration commit.
457     ///
458     /// When enabled:
459     /// * Tuples are parsed as length-value pairs as usual, but if a length
460     /// surpasses the total option length, the rest of the option buffer is
461     /// parsed as the next value. This more commonly affects DHCPv6's vendor
462     /// class option (16), but it also affects custom options that are defined
463     /// with tuple fields.
464     static bool lenient_parsing_;
465 
466 protected:
467 
468     /// @brief Copies this option and returns a pointer to the copy.
469     ///
470     /// The deep copy of the option is performed by calling copy
471     /// constructor of the option of a given type. Derived classes call
472     /// this method in the implementations of @ref clone methods to
473     /// create a copy of the option of their type.
474     ///
475     /// @tparam OptionType Type of the option of which a clone should
476     /// be created.
477     template<typename OptionType>
cloneInternal()478     OptionPtr cloneInternal() const {
479         const OptionType* cast_this = dynamic_cast<const OptionType*>(this);
480         if (cast_this) {
481             return (boost::shared_ptr<OptionType>(new OptionType(*cast_this)));
482         }
483         return (OptionPtr());
484     }
485 
486     /// @brief Store option's header in a buffer.
487     ///
488     /// This method writes option's header into a buffer in the
489     /// on-wire format. The universe set for the particular option
490     /// is used to determine whether option code and length are
491     /// stored as 2-byte (for DHCPv6) or single-byte (for DHCPv4)
492     /// values. For DHCPv4 options, this method checks if the
493     /// length does not exceed 255 bytes and throws exception if
494     /// it does.
495     /// This method is used by derived classes to pack option's
496     /// header into a buffer. This method should not be called
497     /// directly by other classes.
498     ///
499     /// @param [out] buf output buffer.
500     void packHeader(isc::util::OutputBuffer& buf) const;
501 
502     /// @brief Store sub options in a buffer.
503     ///
504     /// This method stores all sub-options defined for a particular
505     /// option in a on-wire format in output buffer provided.
506     /// This function is called by pack function in this class or
507     /// derived classes that override pack.
508     ///
509     /// @param [out] buf output buffer.
510     ///
511     /// @todo The set of exceptions thrown by this function depend on
512     /// exceptions thrown by pack methods invoked on objects
513     /// representing sub options. We should consider whether to aggregate
514     /// those into one exception which can be documented here.
515     void packOptions(isc::util::OutputBuffer& buf) const;
516 
517     /// @brief Builds a collection of sub options from the buffer.
518     ///
519     /// This method parses the provided buffer and builds a collection
520     /// of objects representing sub options. This function may throw
521     /// different exceptions when option assembly fails.
522     ///
523     /// @param buf buffer to be parsed.
524     ///
525     /// @todo The set of exceptions thrown by this function depend on
526     /// exceptions thrown by unpack methods invoked on objects
527     /// representing sub options. We should consider whether to aggregate
528     /// those into one exception which can be documented here.
529     void unpackOptions(const OptionBuffer& buf);
530 
531     /// @brief Returns option header in the textual format.
532     ///
533     /// This protected method should be called by the derived classes in
534     /// their respective @c toText implementations.
535     ///
536     /// @param indent Number of spaces to insert before the text.
537     /// @param type_name Option type name. If empty, the option name
538     /// is omitted.
539     ///
540     /// @return Option header in the textual format.
541     std::string headerToText(const int indent = 0,
542                              const std::string& type_name = "") const;
543 
544     /// @brief Returns collection of suboptions in the textual format.
545     ///
546     /// This protected method should be called by the derived classes
547     /// in their respective @c toText implementations to append the
548     /// suboptions held by this option. Note that there are some
549     /// option types which don't have suboptions because they contain
550     /// variable length fields. For such options this method is not
551     /// called.
552     ///
553     /// @param indent Number of spaces to insert before the text.
554     ///
555     //// @return Suboptions in the textual format.
556     std::string suboptionsToText(const int indent = 0) const;
557 
558     /// @brief A protected method used for option correctness.
559     ///
560     /// It is used in constructors. In there are any problems detected
561     /// (like specifying type > 255 for DHCPv4 option), it will throw
562     /// BadValue or OutOfRange exceptions.
563     void check() const;
564 
565     /// option universe (V4 or V6)
566     Universe universe_;
567 
568     /// option type (0-255 for DHCPv4, 0-65535 for DHCPv6)
569     uint16_t type_;
570 
571     /// contains content of this data
572     OptionBuffer data_;
573 
574     /// collection for storing suboptions
575     OptionCollection options_;
576 
577     /// Name of the option space being encapsulated by this option.
578     std::string encapsulated_space_;
579 
580     /// @todo probably 2 different containers have to be used for v4 (unique
581     /// options) and v6 (options with the same type can repeat)
582 };
583 
584 } // namespace isc::dhcp
585 } // namespace isc
586 
587 #endif // OPTION_H
588