Browse Source

Merge branch 'trac3576' Add PXE options

Add several options PXE uses, also add the OptionOpaqueDataTuples
class to provide support for the parameter list option.

Conflicts:
	ChangeLog
Shawn Routhier 9 years ago
parent
commit
fdcc73afe7

+ 8 - 0
ChangeLog

@@ -1,3 +1,11 @@
+1018.   [func]		sar
+	Added support for several options for use by PXE.
+	From RFC4578 (for DHCPv4) these are: 93 - client-system,
+	94 - client-ndi, 97 - uuid-guid.
+	From RFC5970 (for DHCPv6) these are: 59 - bootfile-url,
+	60 - bootfile-param, 61 - client-arch-type, 62 - nii.
+	(trac #3576, git <TBD>)
+
 1017.	[func]		tmark
 	Lease dump SQL logic, used by kea-admin, is now supplied via stored
 	procedures rather than external text files.  Files of the form

+ 3 - 0
doc/guide/dhcp4-srv.xml

@@ -1085,6 +1085,9 @@ It is merely echoed by the server
 <row><entry>client-last-transaction-time</entry><entry>91</entry><entry>uint32</entry><entry>false</entry><entry>false</entry></row>
 <row><entry>associated-ip</entry><entry>92</entry><entry>ipv4-address</entry><entry>true</entry><entry>false</entry></row>
 -->
+<row><entry>client-system</entry><entry>93</entry><entry>uint16</entry><entry>true</entry><entry>false</entry></row>
+<row><entry>client-ndi</entry><entry>94</entry><entry>record</entry><entry>false</entry><entry>false</entry></row>
+<row><entry>uuid-guid</entry><entry>97</entry><entry>record</entry><entry>false</entry><entry>false</entry></row>
 <row><entry>subnet-selection</entry><entry>118</entry><entry>ipv4-address</entry><entry>false</entry><entry>false</entry></row>
 <row><entry>domain-search</entry><entry>119</entry><entry>binary</entry><entry>false</entry><entry>false</entry></row>
 <row><entry>vivco-suboptions</entry><entry>124</entry><entry>binary</entry><entry>false</entry><entry>false</entry></row>

+ 4 - 0
doc/guide/dhcp6-srv.xml

@@ -954,6 +954,10 @@ temporarily override a list of interface names and listen on all interfaces.
 <row><entry>clt-time</entry><entry>46</entry><entry>uint32</entry><entry>false</entry></row>
 <row><entry>lq-relay-data</entry><entry>47</entry><entry>record</entry><entry>false</entry></row>
 <row><entry>lq-client-link</entry><entry>48</entry><entry>ipv6-address</entry><entry>true</entry></row>
+<row><entry>bootfile-url</entry><entry>59</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>bootfile-param</entry><entry>60</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>client-arch-type</entry><entry>61</entry><entry>uint16</entry><entry>true</entry></row>
+<row><entry>nii</entry><entry>62</entry><entry>record</entry><entry>false</entry></row>
 <row><entry>erp-local-domain-name</entry><entry>65</entry><entry>fqdn</entry><entry>false</entry></row>
 <row><entry>rsoo</entry><entry>66</entry><entry>empty</entry><entry>false</entry></row>
 <row><entry>client-linklayer-addr</entry><entry>79</entry><entry>binary</entry><entry>false</entry></row>

+ 6 - 6
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -2067,15 +2067,15 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
     checkResult(status, 1);
     EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 
-    /// @todo The option 59 is a standard DHCPv6 option. However, at this point
+    /// @todo The option 63 is a standard DHCPv6 option. However, at this point
     /// there is no definition for this option in libdhcp++, so it should be
     /// allowed to define it from the configuration interface. This test will
     /// have to be removed once definitions for remaining standard options are
     /// created.
     config =
         "{ \"option-def\": [ {"
-        "      \"name\": \"boot-file-name\","
-        "      \"code\": 59,"
+        "      \"name\": \"geolocation\","
+        "      \"code\": 63,"
         "      \"type\": \"string\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
@@ -2092,12 +2092,12 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
     checkResult(status, 0);
 
     def = CfgMgr::instance().getStagingCfg()->
-        getCfgOptionDef()->get("dhcp6", 59);
+        getCfgOptionDef()->get("dhcp6", 63);
     ASSERT_TRUE(def);
 
     // Check the option data.
-    EXPECT_EQ("boot-file-name", def->getName());
-    EXPECT_EQ(59, def->getCode());
+    EXPECT_EQ("geolocation", def->getName());
+    EXPECT_EQ(63, def->getCode());
     EXPECT_EQ(OPT_STRING_TYPE, def->getType());
     EXPECT_FALSE(def->getArrayType());
 }

+ 1 - 0
src/lib/dhcp/Makefile.am

@@ -41,6 +41,7 @@ libkea_dhcp___la_SOURCES += option.cc option.h
 libkea_dhcp___la_SOURCES += option_custom.cc option_custom.h
 libkea_dhcp___la_SOURCES += option_data_types.cc option_data_types.h
 libkea_dhcp___la_SOURCES += option_definition.cc option_definition.h
+libkea_dhcp___la_SOURCES += option_opaque_data_tuples.cc option_opqaue_data_tuples.h
 libkea_dhcp___la_SOURCES += option_space.cc option_space.h
 libkea_dhcp___la_SOURCES += option_string.cc option_string.h
 libkea_dhcp___la_SOURCES += protocol_util.cc protocol_util.h

+ 3 - 3
src/lib/dhcp/dhcp4.h

@@ -165,11 +165,11 @@ enum DHCPOptionType {
     DHO_AUTHENTICATE                 = 90,  /* RFC3118, was 210 */
     DHO_CLIENT_LAST_TRANSACTION_TIME = 91,
     DHO_ASSOCIATED_IP                = 92,
-//  DHO_SYSTEM                       = 93,
-//  DHO_NDI                          = 94,
+    DHO_SYSTEM                       = 93, /* RFC4578 */
+    DHO_NDI                          = 94, /* RFC4578 */
 //  DHO_LDAP                         = 95,
     // 96 is removed/unassigned
-//  DHO_UUID_GUID                    = 97,
+    DHO_UUID_GUID                    = 97, /* RFC4578 */
 //  DHO_USER_AUTH                    = 98,
 //  DHO_GEOCONF_CIVIC                = 99,
 //  DHO_PCODE                        = 100,

+ 4 - 4
src/lib/dhcp/dhcp6.h

@@ -82,10 +82,10 @@
 //#define D6O_NTP_SERVER                          56 /* RFC5908 */
 //#define D6O_V6_ACCESS_DOMAIN                    57 /* RFC5986 */
 //#define D6O_SIP_UA_CS_LIST                      58 /* RFC6011 */
-//#define D6O_BOOTFILE_URL                        59 /* RFC5970 */
-//#define D6O_BOOTFILE_PARAM                      60 /* RFC5970 */
-//#define D6O_CLIENT_ARCH_TYPE                    61 /* RFC5970 */
-//#define D6O_NII                                 62 /* RFC5970 */
+#define D6O_BOOTFILE_URL                        59 /* RFC5970 */
+#define D6O_BOOTFILE_PARAM                      60 /* RFC5970 */
+#define D6O_CLIENT_ARCH_TYPE                    61 /* RFC5970 */
+#define D6O_NII                                 62 /* RFC5970 */
 //#define D6O_GEOLOCATION                         63 /* RFC6225 */
 //#define D6O_AFTR_NAME                           64 /* RFC6334 */
 #define D6O_ERP_LOCAL_DOMAIN_NAME               65 /* RFC6440 */

+ 9 - 0
src/lib/dhcp/option_definition.cc

@@ -27,6 +27,7 @@
 #include <dhcp/option_definition.h>
 #include <dhcp/option_int.h>
 #include <dhcp/option_int_array.h>
+#include <dhcp/option_opaque_data_tuples.h>
 #include <dhcp/option_space.h>
 #include <dhcp/option_string.h>
 #include <dhcp/option_vendor.h>
@@ -437,6 +438,11 @@ OptionDefinition::haveStatusCodeFormat() const {
 }
 
 bool
+OptionDefinition::haveOpaqueDataTuplesFormat() const {
+    return (getType() == OPT_BINARY_TYPE);
+}
+
+bool
 OptionDefinition::convertToBool(const std::string& value_str) const {
     // Case insensitve check that the input is one of: "true" or "false".
     if (boost::iequals(value_str, "true")) {
@@ -696,6 +702,9 @@ OptionDefinition::factorySpecialFormatOption(Option::Universe u,
         } else if (getCode() == D6O_STATUS_CODE && haveStatusCodeFormat()) {
             // Status Code (option code 13)
             return (OptionPtr(new Option6StatusCode(begin, end)));
+        } else if (getCode() == D6O_BOOTFILE_PARAM && haveOpaqueDataTuplesFormat()) {
+            // Bootfile params (option code 60)
+            return (OptionPtr(new OptionOpaqueDataTuples(Option::V6, getCode(), begin, end)));
         }
     } else {
         if ((getCode() == DHO_FQDN) && haveFqdn4Format()) {

+ 5 - 0
src/lib/dhcp/option_definition.h

@@ -370,6 +370,11 @@ public:
     /// @return true if option has the format of DHCPv6 Status code option.
     bool haveStatusCodeFormat() const;
 
+    /// @brief Check if the option has format of OpaqueDataTuples type options.
+    ///
+    /// @return true if option has the format of OpaqueDataTuples type options.
+    bool haveOpaqueDataTuplesFormat() const;
+
     /// @brief Option factory.
     ///
     /// This function creates an instance of DHCP option using

+ 150 - 0
src/lib/dhcp/option_opaque_data_tuples.cc

@@ -0,0 +1,150 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+#include <dhcp/opaque_data_tuple.h>
+#include <dhcp/option_opaque_data_tuples.h>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+
+OptionOpaqueDataTuples::OptionOpaqueDataTuples(Option::Universe u,
+                                               const uint16_t type)
+    : Option(u, type) {
+}
+
+OptionOpaqueDataTuples::OptionOpaqueDataTuples(Option::Universe u,
+                                               const uint16_t type,
+                                               OptionBufferConstIter begin,
+                                               OptionBufferConstIter end)
+    : Option(u, type) {
+    unpack(begin, end);
+}
+
+void
+OptionOpaqueDataTuples::pack(isc::util::OutputBuffer& buf) {
+    packHeader(buf);
+
+    for (TuplesCollection::const_iterator it = tuples_.begin();
+         it != tuples_.end(); ++it) {
+        it->pack(buf);
+    }
+}
+
+void
+OptionOpaqueDataTuples::unpack(OptionBufferConstIter begin,
+                               OptionBufferConstIter end) {
+    if (std::distance(begin, end) < getMinimalLength() - getHeaderLen()) {
+        isc_throw(OutOfRange, "parsed data tuples option data truncated to"
+                  " size " << std::distance(begin, end));
+    }
+
+    // Start reading opaque data.
+    size_t offset = 0;
+    while (offset < std::distance(begin, end)) {
+        // Parse a tuple.
+        OpaqueDataTuple tuple(getLengthFieldType(), begin + offset, end);
+        addTuple(tuple);
+        // The tuple has been parsed correctly which implies that it is safe to
+        // advance the offset by its total length.
+        offset += tuple.getTotalLength();
+    }
+}
+
+void
+OptionOpaqueDataTuples::addTuple(const OpaqueDataTuple& tuple) {
+    if (tuple.getLengthFieldType() != getLengthFieldType()) {
+        isc_throw(isc::BadValue, "attempted to add opaque data tuple having"
+                  " invalid size of the length field "
+                  << tuple.getDataFieldSize() << " to opaque data tuple option");
+    }
+
+    tuples_.push_back(tuple);
+}
+
+
+void
+OptionOpaqueDataTuples::setTuple(const size_t at, const OpaqueDataTuple& tuple) {
+    if (at >= getTuplesNum()) {
+        isc_throw(isc::OutOfRange, "attempted to set an opaque data for the"
+                  " opaque data tuple option at position " << at << " which"
+                  " is out of range");
+
+    } else if (tuple.getLengthFieldType() != getLengthFieldType()) {
+        isc_throw(isc::BadValue, "attempted to set opaque data tuple having"
+                  " invalid size of the length field "
+                  << tuple.getDataFieldSize() << " to opaque data tuple option");
+    }
+
+    tuples_[at] = tuple;
+}
+
+OpaqueDataTuple
+OptionOpaqueDataTuples::getTuple(const size_t at) const {
+    if (at >= getTuplesNum()) {
+        isc_throw(isc::OutOfRange, "attempted to get an opaque data for the"
+                  " opaque data tuple option at position " << at << " which is"
+                  " out of range. There are only " << getTuplesNum() << " tuples");
+    }
+    return (tuples_[at]);
+}
+
+bool
+OptionOpaqueDataTuples::hasTuple(const std::string& tuple_str) const {
+    // Iterate over existing tuples (there shouldn't be many of them),
+    // and try to match the searched one.
+    for (TuplesCollection::const_iterator it = tuples_.begin();
+         it != tuples_.end(); ++it) {
+        if (*it == tuple_str) {
+            return (true);
+        }
+    }
+    return (false);
+}
+
+uint16_t
+OptionOpaqueDataTuples::len() {
+    // The option starts with the header.
+    uint16_t length = getHeaderLen();
+    // Now iterate over existing tuples and add their size.
+    for (TuplesCollection::const_iterator it = tuples_.begin();
+         it != tuples_.end(); ++it) {
+        length += it->getTotalLength();
+    }
+
+    return (length);
+}
+
+std::string
+OptionOpaqueDataTuples::toText(int indent) {
+    std::ostringstream s;
+
+    // Apply indentation
+    s << std::string(indent, ' ');
+
+    // Print type and length
+    s << "type=" << getType() << ", len=" << len() - getHeaderLen() << std::dec;
+    // Iterate over all tuples and print their size and contents.
+    for (unsigned i = 0; i < getTuplesNum(); ++i) {
+        // Print the tuple.
+        s << ", data-len" << i << "=" << getTuple(i).getLength();
+        s << ", data" << i << "='" << getTuple(i) << "'";
+    }
+
+    return (s.str());
+}
+
+} // namespace isc::dhcp
+} // namespace isc

+ 174 - 0
src/lib/dhcp/option_opaque_data_tuples.h

@@ -0,0 +1,174 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef OPTION_OPAQUE_DATA_TUPLES_H
+#define OPTION_OPAQUE_DATA_TUPLES_H
+
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/opaque_data_tuple.h>
+#include <dhcp/option.h>
+#include <util/buffer.h>
+#include <boost/shared_ptr.hpp>
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief This class encapsulates a collection of data tuples and could be
+/// used by multiple options.  It is tailored for use with the DHCPv6
+/// Bootfile-param option (option 60).
+///
+/// The format of the option is described in section 3.2 of RFC5970.
+/// This option may carry an arbitrary number of tuples carrying opaque data.
+/// Each tuple consists of a field holding the length of the opaque data
+/// followed by a string containing the data itself.  For option 60 each
+/// length field is 2 bytes long and the data is a UTF-8 string that is not
+/// null terminated.
+///
+/// @todo The class is similar to the class used by the DHCPv6 Vendor Class
+/// (16) and DHCPv4 V-I Vendor Class (124) options, though they include an
+/// enterprise (or vendor) ID in the option.  In the future it may
+/// make sense to rewrite the OptionVendorClass to derive from this class.
+class OptionOpaqueDataTuples : public Option {
+public:
+
+    /// @brief Collection of opaque data tuples carried by the option.
+    typedef std::vector<OpaqueDataTuple> TuplesCollection;
+
+    /// @brief Constructor.
+    ///
+    /// This constructor creates an instance of an OpaqueDataTuple that can
+    /// be used for an option such as DHCPv6 Bootfile Parameters (60).
+    ///
+    /// @param u universe (v4 or v6).
+    /// @param type option type
+    OptionOpaqueDataTuples(Option::Universe u, const uint16_t type);
+
+    /// @brief Constructor.
+    ///
+    /// This constructor creates an instance of the option using a buffer with
+    /// on-wire data. It may throw an exception if the @c unpack method throws.
+    ///
+    /// @param u universe (v4 or v6)
+    /// @param type option type
+    /// @param begin Iterator pointing to the beginning of the buffer holding an
+    /// option.
+    /// @param end Iterator pointing to the end of the buffer holding an option.
+    OptionOpaqueDataTuples(Option::Universe u, const uint16_t type,
+                           OptionBufferConstIter begin,
+                           OptionBufferConstIter end);
+
+    /// @brief Renders option into the buffer in the wire format.
+    ///
+    /// @param [out] buf Buffer to which the option is rendered.
+    virtual void pack(isc::util::OutputBuffer& buf);
+
+    /// @brief Parses buffer holding an option.
+    ///
+    /// This function parses the buffer holding an option and initializes option
+    /// properties: the collection of tuples.
+    ///
+    /// @param begin Iterator pointing to the beginning of the buffer holding an
+    /// option.
+    /// @param end Iterator pointing to the end of the buffer holding an option.
+    virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+    /// @brief Adds a new opaque data tuple to the option.
+    ///
+    /// @param tuple Tuple to be added.
+    /// @throw isc::BadValue if the type of the tuple doesn't match the
+    /// universe this option belongs to.
+    void addTuple(const OpaqueDataTuple& tuple);
+
+    /// @brief Replaces tuple at the specified index with a new tuple.
+    ///
+    /// This function replaces an opaque data tuple at the specified position
+    /// with the new tuple. If the specified index is out of range an exception
+    /// is thrown.
+    ///
+    /// @param at Index at which the tuple should be replaced.
+    /// @param tuple Tuple to be set.
+    /// @throw isc::OutOfRange if the tuple position is out of range.
+    /// @throw isc::BadValue if the type of the tuple doesn't match the
+    /// universe this option belongs to.
+    void setTuple(const size_t at, const OpaqueDataTuple& tuple);
+
+    /// @brief Returns opaque data tuple at the specified position.
+    ///
+    /// If the specified position is out of range an exception is thrown.
+    ///
+    /// @param at Index for which tuple to get.
+    /// @throw isc::OutOfRange if the tuple position is out of range.
+    OpaqueDataTuple getTuple(const size_t at) const;
+
+    /// @brief Returns the number of opaque data tuples added to the option.
+    size_t getTuplesNum() const {
+        return (tuples_.size());
+    }
+
+    /// @brief Returns collection of opaque data tuples carried in the option.
+    const TuplesCollection& getTuples() const {
+        return (tuples_);
+    }
+
+    /// @brief Checks if the object holds the opaque data tuple with the
+    /// specified string.
+    ///
+    /// @param tuple_str String representation of the tuple being searched.
+    /// @return true if the specified tuple exists for this option.
+    bool hasTuple(const std::string& tuple_str) const;
+
+    /// @brief Returns the full length of the option, including option header.
+    virtual uint16_t len();
+
+    /// @brief Returns text representation of the option.
+    ///
+    /// @param indent Number of space characters before text.
+    /// @return Text representation of the option.
+    virtual std::string toText(int indent = 0);
+
+private:
+
+    /// @brief Returns the tuple length field type for the given universe.
+    ///
+    /// This function returns the length field type which should be used
+    /// for the opaque data tuples being added to this option.
+    /// Currently this class is only used for a DHCPv6 option it may be expanded
+    /// for DHCPv4 in the future.
+    ///
+    /// @return Tuple length field type for the universe this option belongs to.
+    OpaqueDataTuple::LengthFieldType getLengthFieldType() const {
+        return (OpaqueDataTuple::LENGTH_2_BYTES);
+    }
+
+    /// @brief Returns minimal length of the option for the given universe.
+    /// Currently this class is only used for a DHCPv6 option it may be expanded
+    /// for DHCPv4 in the future.
+    uint16_t getMinimalLength() const {
+        return (4);
+    }
+
+    /// @brief Collection of opaque data tuples carried by the option.
+    TuplesCollection tuples_;
+
+};
+
+/// @brief Defines a pointer to the @c OptionOpaqueDataTuples.
+typedef boost::shared_ptr<OptionOpaqueDataTuples> OptionOpaqueDataTuplesPtr;
+
+}
+}
+
+#endif // OPTION_OPAQUE_DATA_TUPLES_H

+ 4 - 4
src/lib/dhcp/option_vendor_class.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -119,9 +119,9 @@ public:
 
     /// @brief Returns opaque data tuple at the specified position.
     ///
-    ///  If the specified position is out of range an exception is thrown.
+    /// If the specified position is out of range an exception is thrown.
     ///
-    /// @param at Index at which the tuple should be replaced.
+    /// @param at Index for which tuple to get.
     /// @throw isc::OutOfRange if the tuple position is out of range.
     OpaqueDataTuple getTuple(const size_t at) const;
 
@@ -177,7 +177,7 @@ private:
 
     /// @brief Returns minimal length of the option for the given universe.
     uint16_t getMinimalLength() const {
-        return (getUniverse() == Option::V4 ? 7 : 6);
+        return (getUniverse() == Option::V4 ? 7 : 8);
     }
 
     /// @brief Enterprise ID.

+ 19 - 0
src/lib/dhcp/std_option_defs.h

@@ -72,6 +72,13 @@ RECORD_DECL(FQDN_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE,
 // Opaque data is represented here by the binary data field.
 RECORD_DECL(VIVCO_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
 
+// RFC4578 (PXE) record fields
+//
+// Three 1 byte fields to describe a network interface: type, major and minor
+RECORD_DECL(CLIENT_NDI_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE);
+// A client identifer: a 1 byte type field followed by opaque data depending on the type
+RECORD_DECL(UUID_GUID_RECORDS, OPT_UINT8_TYPE, OPT_BINARY_TYPE);
+
 /// @brief Definitions of standard DHCPv4 options.
 const OptionDefParams OPTION_DEF_PARAMS4[] = {
     { "subnet-mask", DHO_SUBNET_MASK, OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
@@ -188,6 +195,9 @@ const OptionDefParams OPTION_DEF_PARAMS4[] = {
     { "client-last-transaction-time", DHO_CLIENT_LAST_TRANSACTION_TIME,
       OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
     { "associated-ip", DHO_ASSOCIATED_IP, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+    { "client-system", DHO_SYSTEM, OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" },
+    { "client-ndi", DHO_NDI, OPT_RECORD_TYPE, false, RECORD_DEF(CLIENT_NDI_RECORDS), "" },
+    { "uuid-guid", DHO_UUID_GUID, OPT_RECORD_TYPE, false, RECORD_DEF(UUID_GUID_RECORDS), "" },
     { "subnet-selection", DHO_SUBNET_SELECTION,
       OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
     // The following options need a special encoding of data
@@ -240,6 +250,11 @@ RECORD_DECL(VENDOR_CLASS_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
 RECORD_DECL(SIGNATURE_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE,
             OPT_BINARY_TYPE);
 
+// RFC5970 (PXE) Class record fields
+//
+// Three 1 byte fileds to describe a network interface: type, major and minor
+RECORD_DECL(CLIENT_NII_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE);
+
 /// Standard DHCPv6 option definitions.
 ///
 /// @warning in this array, the initializers are provided for all
@@ -329,6 +344,10 @@ const OptionDefParams OPTION_DEF_PARAMS6[] = {
       RECORD_DEF(LQ_RELAY_DATA_RECORDS), "" },
     { "lq-client-link", D6O_LQ_CLIENT_LINK, OPT_IPV6_ADDRESS_TYPE, true,
       NO_RECORD_DEF, "" },
+    { "bootfile-url", D6O_BOOTFILE_URL, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+    { "bootfile-param", D6O_BOOTFILE_PARAM, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+    { "client-arch-type", D6O_CLIENT_ARCH_TYPE, OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" },
+    { "nii", D6O_NII, OPT_RECORD_TYPE, false, RECORD_DEF(CLIENT_NII_RECORDS), "" },
     { "erp-local-domain-name", D6O_ERP_LOCAL_DOMAIN_NAME, OPT_FQDN_TYPE, false,
       NO_RECORD_DEF, "" },
     { "rsoo", D6O_RSOO, OPT_EMPTY_TYPE, false, NO_RECORD_DEF, "rsoo-opts" },

+ 1 - 0
src/lib/dhcp/tests/Makefile.am

@@ -65,6 +65,7 @@ libdhcp___unittests_SOURCES += option_int_array_unittest.cc
 libdhcp___unittests_SOURCES += option_data_types_unittest.cc
 libdhcp___unittests_SOURCES += option_definition_unittest.cc
 libdhcp___unittests_SOURCES += option_custom_unittest.cc
+libdhcp___unittests_SOURCES += option_opaque_data_tuples_unittest.cc
 libdhcp___unittests_SOURCES += option_unittest.cc
 libdhcp___unittests_SOURCES += option_space_unittest.cc
 libdhcp___unittests_SOURCES += option_string_unittest.cc

+ 30 - 0
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -29,6 +29,7 @@
 #include <dhcp/option_custom.h>
 #include <dhcp/option_int.h>
 #include <dhcp/option_int_array.h>
+#include <dhcp/option_opaque_data_tuples.h>
 #include <dhcp/option_string.h>
 #include <dhcp/option_vendor.h>
 #include <dhcp/option_vendor_class.h>
@@ -942,6 +943,15 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
     LibDhcpTest::testStdOptionDefs4(DHO_SUBNET_SELECTION, begin, end,
                                     typeid(OptionCustom));
 
+    LibDhcpTest::testStdOptionDefs4(DHO_SYSTEM, begin, end,
+                                    typeid(OptionIntArray<uint16_t>));
+
+    LibDhcpTest::testStdOptionDefs4(DHO_NDI, begin, begin + 3,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs4(DHO_UUID_GUID, begin, begin + 17,
+                                    typeid(OptionCustom));
+
     LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_SEARCH, begin, end,
                                     typeid(Option));
 
@@ -1017,6 +1027,13 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
     std::vector<uint8_t> vclass_buf(vclass_data,
                                     vclass_data + sizeof(vclass_data));;
 
+    // Initialize test buffer for Bootfile Param option.
+    const char bparam_data[] = {
+        0x00, 0x01, 0x02
+    };
+    std::vector<uint8_t> bparam_buf(bparam_data,
+                                    bparam_data + sizeof(bparam_data));;
+
     // The actual test starts here for all supported option codes.
     LibDhcpTest::testStdOptionDefs6(D6O_CLIENTID, begin, end,
                                     typeid(Option));
@@ -1162,6 +1179,19 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
     LibDhcpTest::testStdOptionDefs6(D6O_LQ_CLIENT_LINK, begin, end,
                                     typeid(Option6AddrLst));
 
+    LibDhcpTest::testStdOptionDefs6(D6O_BOOTFILE_URL, begin, end,
+                                    typeid(OptionString));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_BOOTFILE_PARAM, bparam_buf.begin(),
+                                    bparam_buf.end(),
+                                    typeid(OptionOpaqueDataTuples));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_ARCH_TYPE, begin, end,
+                                    typeid(OptionIntArray<uint16_t>));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_NII, begin, begin + 3,
+                                    typeid(OptionCustom));
+
     LibDhcpTest::testStdOptionDefs6(D6O_RSOO, begin, end,
                                     typeid(OptionCustom),
                                     "rsoo-opts");

+ 255 - 0
src/lib/dhcp/tests/option_opaque_data_tuples_unittest.cc

@@ -0,0 +1,255 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <dhcp/option_opaque_data_tuples.h>
+#include <util/buffer.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+// This test checks that the DHCPv6 option constructor sets the default
+// properties to the expected values.
+TEST(OptionOpaqueDataTuples, constructor6) {
+    OptionOpaqueDataTuples data_tuple(Option::V6, 60);
+    // Option length is 2 bytes for option code + 2 bytes for option size
+    EXPECT_EQ(4, data_tuple.len());
+    // There should be no tuples.
+    EXPECT_EQ(0, data_tuple.getTuplesNum());
+}
+
+// This test verifies that it is possible to append the opaque data tuple
+// to the option and then retrieve it.
+TEST(OptionOpaqueDataTuples, addTuple) {
+    OptionOpaqueDataTuples data_tuple(Option::V6, 60);
+    // Initially there should be no tuples (for DHCPv6).
+    ASSERT_EQ(0, data_tuple.getTuplesNum());
+    // Create a new tuple and add it to the option.
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    tuple = "xyz";
+    data_tuple.addTuple(tuple);
+    // The option should now hold one tuple.
+    ASSERT_EQ(1, data_tuple.getTuplesNum());
+    EXPECT_EQ("xyz", data_tuple.getTuple(0).getText());
+    // Add another tuple.
+    tuple = "abc";
+    data_tuple.addTuple(tuple);
+    // The option should now hold exactly two tuples in the order in which
+    // they were added.
+    ASSERT_EQ(2, data_tuple.getTuplesNum());
+    EXPECT_EQ("xyz", data_tuple.getTuple(0).getText());
+    EXPECT_EQ("abc", data_tuple.getTuple(1).getText());
+
+    // Check that hasTuple correctly identifies existing tuples.
+    EXPECT_TRUE(data_tuple.hasTuple("xyz"));
+    EXPECT_TRUE(data_tuple.hasTuple("abc"));
+    EXPECT_FALSE(data_tuple.hasTuple("other"));
+
+    // Attempt to add the tuple with 1 byte long length field should fail
+    // for DHCPv6 option.
+    OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_1_BYTE);
+    EXPECT_THROW(data_tuple.addTuple(tuple2), isc::BadValue);
+}
+
+// This test checks that it is possible to replace existing tuple.
+TEST(OptionOpaqueDataTuples, setTuple) {
+    OptionOpaqueDataTuples data_tuple(Option::V6, 60);
+    // Initially there should be no tuples (for DHCPv6).
+    ASSERT_EQ(0, data_tuple.getTuplesNum());
+    // Add a tuple
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    tuple = "xyz";
+    data_tuple.addTuple(tuple);
+
+    // Add another one.
+    tuple = "abc";
+    data_tuple.addTuple(tuple);
+    ASSERT_EQ(2, data_tuple.getTuplesNum());
+    ASSERT_EQ("abc", data_tuple.getTuple(1).getText());
+
+    // Try to replace them with new tuples.
+    tuple = "new_xyz";
+    ASSERT_NO_THROW(data_tuple.setTuple(0, tuple));
+    ASSERT_EQ(2, data_tuple.getTuplesNum());
+    EXPECT_EQ("new_xyz", data_tuple.getTuple(0).getText());
+
+    tuple = "new_abc";
+    ASSERT_NO_THROW(data_tuple.setTuple(1, tuple));
+    ASSERT_EQ(2, data_tuple.getTuplesNum());
+    EXPECT_EQ("new_abc", data_tuple.getTuple(1).getText());
+
+    // For out of range position, exception should be thrown.
+    tuple = "foo";
+    EXPECT_THROW(data_tuple.setTuple(2, tuple), isc::OutOfRange);
+}
+
+// Check that the returned length of the DHCPv6 option is correct.
+TEST(OptionOpaqueDataTuples, len6) {
+    OptionOpaqueDataTuples data_tuple(Option::V6, 60);
+    ASSERT_EQ(4, data_tuple.len());
+    // Add first tuple.
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    tuple = "xyz";
+    ASSERT_NO_THROW(data_tuple.addTuple(tuple));
+    // The total length grows by 2 bytes of the length field and 3 bytes
+    // consumed by 'xyz'.
+    EXPECT_EQ(9, data_tuple.len());
+    // Add another tuple and check that the total size gets increased.
+    tuple = "abc";
+    data_tuple.addTuple(tuple);
+    EXPECT_EQ(14, data_tuple.len());
+}
+
+// Check that the DHCPv6 option is rendered to the buffer in wire format.
+TEST(OptionOpaqueDataTuples, pack6) {
+    OptionOpaqueDataTuples data_tuple(Option::V6, 60);
+    ASSERT_EQ(0, data_tuple.getTuplesNum());
+    // Add tuple.
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    tuple = "Hello world";
+    data_tuple.addTuple(tuple);
+    // And add another tuple so as resulting option is a bit more complex.
+    tuple = "foo";
+    data_tuple.addTuple(tuple);
+
+    // Render the data to the buffer.
+    OutputBuffer buf(10);
+    ASSERT_NO_THROW(data_tuple.pack(buf));
+    ASSERT_EQ(22, buf.getLength());
+
+    // Prepare reference data.
+    const uint8_t ref[] = {
+        0x00, 0x3C, 0x00, 0x12,             // option 60, length 18
+        0x00, 0x0B,                         // tuple length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64,       // world
+        0x00, 0x03,                         // tuple length is 3
+        0x66, 0x6F, 0x6F                    // foo
+    };
+    // Compare the buffer with reference data.
+    EXPECT_EQ(0, memcmp(static_cast<const void*>(ref),
+                        static_cast<const void*>(buf.getData()),
+                        buf.getLength()));
+}
+
+// This function checks that the DHCPv6 option with two opaque data tuples
+// is parsed correctly.
+TEST(OptionOpaqueDataTuples, unpack6) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {
+        0x00, 0x0B,                         // tuple length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64,       // world
+        0x00, 0x03,                         // tuple length is 3
+        0x66, 0x6F, 0x6F                    // foo
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    OptionOpaqueDataTuplesPtr data_tuple;
+    ASSERT_NO_THROW(
+        data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V6,
+                                                                          60,
+                                                                          buf.begin(),
+                                                                          buf.end()));
+    );
+    EXPECT_EQ(D6O_BOOTFILE_PARAM, data_tuple->getType());
+    ASSERT_EQ(2, data_tuple->getTuplesNum());
+    EXPECT_EQ("Hello world", data_tuple->getTuple(0).getText());
+    EXPECT_EQ("foo", data_tuple->getTuple(1).getText());
+}
+
+// This test checks that the DHCPv6 option with opaque data of size 0
+// is correctly parsed.
+TEST(OptionOpaqueDataTuples, unpack6EmptyTuple) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {0x00, 0x00}; // tuple length is 0
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    OptionOpaqueDataTuplesPtr data_tuple;
+    ASSERT_NO_THROW(
+        data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V6,
+                                                                          60,
+                                                                          buf.begin(),
+                                                                          buf.end()));
+    );
+    EXPECT_EQ(D6O_BOOTFILE_PARAM, data_tuple->getType());
+    ASSERT_EQ(1, data_tuple->getTuplesNum());
+    EXPECT_TRUE(data_tuple->getTuple(0).getText().empty());
+}
+
+// This test checks that exception is thrown when parsing truncated DHCPv6
+// bootfile-param option
+TEST(OptionOpaqueDataTuples, unpack6Truncated) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {
+        0x00, 0x0B,                         // tuple length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C              // worl (truncated d!)
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    EXPECT_THROW(OptionOpaqueDataTuples (Option::V6, 60, buf.begin(), buf.end()),
+                 isc::dhcp::OpaqueDataTupleError);
+}
+
+// This test checks that the DHCPv6 bootfile-param option containing no opaque
+// data is parsed correctly.
+TEST(OptionOpaqueDataTuples, unpack6NoTuple) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    OptionOpaqueDataTuplesPtr data_tuple;
+    ASSERT_NO_THROW(
+        data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V6,
+                                                                          60,
+                                                                          buf.begin(),
+                                                                          buf.end()));
+    );
+    EXPECT_EQ(D6O_BOOTFILE_PARAM, data_tuple->getType());
+    EXPECT_EQ(0, data_tuple->getTuplesNum());
+}
+
+// Verifies correctness of the text representation of the DHCPv6 option.
+TEST(OptionOpaqueDataTuples, toText6) {
+    OptionOpaqueDataTuples data_tuple(Option::V6, 60);
+    ASSERT_EQ(0, data_tuple.getTuplesNum());
+    // Lets add a tuple
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    tuple = "Hello world";
+    data_tuple.addTuple(tuple);
+    // And add another tuple so as resulting option is a bit more complex.
+    tuple = "foo";
+    data_tuple.addTuple(tuple);
+    // Check that the text representation of the option is as expected.
+    EXPECT_EQ("type=60, len=18,"
+              " data-len0=11, data0='Hello world',"
+              " data-len1=3, data1='foo'",
+              data_tuple.toText());
+
+    // Check that indentation works.
+    EXPECT_EQ("  type=60, len=18,"
+              " data-len0=11, data0='Hello world',"
+              " data-len1=3, data1='foo'",
+              data_tuple.toText(2));
+}
+
+} // end of anonymous namespace

+ 3 - 3
src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -231,9 +231,9 @@ TEST(CfgOptionDefTest, overrideStdOptionDef) {
     def.reset(new OptionDefinition("sntp-servers", D6O_SNTP_SERVERS,
                                    "ipv4-address"));
     EXPECT_THROW(cfg.add(def, DHCP6_OPTION_SPACE), isc::BadValue);
-    // There is no definition for option 59 in libdhcp++ yet, so it should
+    // There is no definition for option 63 in libdhcp++ yet, so it should
     // be possible provide a custom definition.
-    def.reset(new OptionDefinition("bootfile-url", 59, "uint32"));
+    def.reset(new OptionDefinition("geolocation", 63, "uint32"));
     EXPECT_NO_THROW(cfg.add(def, DHCP6_OPTION_SPACE));
 }