Browse Source

[3874] Implemented generation of DUID-EN and DUID-LL.

Marcin Siodelski 9 years ago
parent
commit
8f09433904

+ 193 - 14
src/lib/dhcp/duid_factory.cc

@@ -14,11 +14,14 @@
 
 #include <dhcp/duid_factory.h>
 #include <dhcp/iface_mgr.h>
+#include <exceptions/exceptions.h>
 #include <util/io_utilities.h>
 #include <util/range_utilities.h>
 #include <util/strutil.h>
 #include <boost/foreach.hpp>
 #include <ctime>
+#include <fstream>
+#include <stdlib.h>
 #include <string>
 #include <vector>
 
@@ -27,8 +30,18 @@ using namespace isc::util::str;
 
 namespace {
 
+/// @brief Length of the DUID type field.
+const size_t DUID_TYPE_LEN = 2;
+
+/// @brief Minimal length of the MAC address.
 const size_t MIN_MAC_LEN = 6;
 
+/// @brief Length of the enterprise if field.
+const size_t ENTERPRISE_ID_LEN = 4;
+
+/// @brief Default length of the variable length identifier in the DUID-EN.
+const size_t DUID_EN_IDENTIFIER_LEN = 6;
+
 }
 
 namespace isc {
@@ -47,41 +60,107 @@ void
 DUIDFactory::createLLT(const uint16_t htype, const uint32_t time_in,
                        const std::vector<uint8_t>& ll_identifier) {
     uint32_t time_out = time_in;
+    // If time unspecified, use current time.
     if (time_out == 0) {
         time_out = static_cast<uint32_t>(time(NULL) - DUID_TIME_EPOCH);
     }
 
+    std::vector<uint8_t> ll_identifier_out = ll_identifier;
     uint16_t htype_out = htype;
-    if (htype_out == 0) {
-        htype_out = HTYPE_ETHER;
-    }
 
-    std::vector<uint8_t> ll_identifier_out = ll_identifier;
+    // If link layer address unspecified, use address of one of the
+    // interfaces present in the system. Also, update the link
+    // layer type accordingly.
     if (ll_identifier_out.empty()) {
-        createLinkLayerId(ll_identifier_out);
+        createLinkLayerId(ll_identifier_out, htype_out);
+
+    } else if (htype_out == 0) {
+        // If link layer type unspecified and link layer adddress
+        // is specified, use HTYPE_ETHER.
+        htype_out = HTYPE_ETHER;
+
     }
 
-    std::vector<uint8_t> duid_out(2 + sizeof(time_out) + sizeof(htype_out));
+    // Render DUID.
+    std::vector<uint8_t> duid_out(DUID_TYPE_LEN + sizeof(time_out) +
+                                  sizeof(htype_out));
     writeUint16(DUID::DUID_LLT, &duid_out[0], 2);
     writeUint16(htype_out, &duid_out[2], 2);
     writeUint32(time_out, &duid_out[4], 4);
     duid_out.insert(duid_out.end(), ll_identifier_out.begin(),
                     ll_identifier_out.end());
 
-    duid_.reset(new DUID(duid_out));
+    // Set new DUID and persist in a file.
+    set(duid_out);
 }
 
-/*void
+void
 DUIDFactory::createEN(const uint32_t enterprise_id,
-                          const std::vector<uint8_t>& identifier) {
-}*/
+                      const std::vector<uint8_t>& identifier) {
+    // Enterprise id 0 means "unspecified". In this case use the ISC
+    // enterprise id.
+    uint32_t enterprise_id_out = enterprise_id != 0 ?
+        enterprise_id : ENTERPRISE_ID_ISC;
+
+    // Render DUID.
+    std::vector<uint8_t> duid_out(DUID_TYPE_LEN + ENTERPRISE_ID_LEN);
+    writeUint16(DUID::DUID_EN, &duid_out[0], 2);
+    writeUint32(enterprise_id_out, &duid_out[2], ENTERPRISE_ID_LEN);
+
+    if (identifier.empty()) {
+        // Identifier is empty, so we have to extend the DUID by 6 bytes
+        // to fit the random identifier.
+        duid_out.resize(DUID_TYPE_LEN + ENTERPRISE_ID_LEN +
+                        DUID_EN_IDENTIFIER_LEN);
+        // Variable length identifier consists of random numbers. The generated
+        // identifier is always 6 bytes long.
+        ::srandom(time(NULL));
+        fillRandom(&duid_out[DUID_TYPE_LEN + ENTERPRISE_ID_LEN],
+                   &duid_out[DUID_TYPE_LEN + ENTERPRISE_ID_LEN +
+                             DUID_EN_IDENTIFIER_LEN]);
+
+    } else {
+        // Append the specified identifier to the end of DUID.
+        duid_out.insert(duid_out.end(), identifier.begin(), identifier.end());
+    }
+
+    // Set new DUID and persist in a file.
+    set(duid_out);
+}
+
+void
+DUIDFactory::createLL(const uint16_t htype,
+                      const std::vector<uint8_t>& ll_identifier) {
+    std::vector<uint8_t> ll_identifier_out = ll_identifier;
+    uint16_t htype_out = htype;
+
+    // If link layer address unspecified, use address of one of the
+    // interfaces present in the system. Also, update the link
+    // layer type accordingly.
+    if (ll_identifier_out.empty()) {
+        createLinkLayerId(ll_identifier_out, htype_out);
+
+    } else if (htype_out == 0) {
+        // If link layer type unspecified and link layer adddress
+        // is specified, use HTYPE_ETHER.
+        htype_out = HTYPE_ETHER;
+
+    }
+
+    // Render DUID.
+    std::vector<uint8_t> duid_out(DUID_TYPE_LEN + sizeof(htype_out));
+    writeUint16(DUID::DUID_LL, &duid_out[0], 2);
+    writeUint16(htype_out, &duid_out[2], 2);
+    duid_out.insert(duid_out.end(), ll_identifier_out.begin(),
+                    ll_identifier_out.end());
 
-/*void
-DUIDFactory::createLL(const uint16_t htype, const std::vector<uint8_t>& ll_identifier) {
-} */
+    // Set new DUID and persist in a file.
+    set(duid_out);
+}
 
 void
-DUIDFactory::createLinkLayerId(std::vector<uint8_t>& identifier) const {
+DUIDFactory::createLinkLayerId(std::vector<uint8_t>& identifier,
+                               uint16_t& htype) const {
     const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
 
     // Let's find suitable interface.
@@ -121,12 +200,112 @@ DUIDFactory::createLinkLayerId(std::vector<uint8_t>& identifier) const {
             continue;
         }
 
+        // Assign link layer address and type.
         identifier.assign(iface->getMac(), iface->getMac() + iface->getMacLen());
+        htype = iface->getHWType();
+    }
+
+    // We failed to find an interface which link layer address could be
+    // used for generating DUID-LLT.
+    if (identifier.empty()) {
+        isc_throw(Unexpected, "unable to find suitable interface for "
+                  " generating a DUID-LLT");
     }
 }
 
+void
+DUIDFactory::set(const std::vector<uint8_t>& duid_vector) {
+    // Check the minimal length.
+    if (duid_vector.size() < DUID::MIN_DUID_LEN) {
+        isc_throw(BadValue, "generated DUID must have at least "
+                  << DUID::MIN_DUID_LEN << " bytes");
+    }
+
+    // Persist DUID in a file if file location specified.
+    if (isPersisted()) {
+        std::ofstream ofs;
+        try {
+            ofs.open(storage_location_, std::ofstream::out |
+                     std::ofstream::trunc);
+            if (!ofs.good()) {
+                isc_throw(InvalidOperation, "unable to open DUID file "
+                          << storage_location_ << " for writing");
+            }
+
+            // Create temporary DUID object.
+            DUID duid(duid_vector);
+
+            // Write DUID to file.
+            ofs << duid.toText();
+            if (!ofs.good()) {
+                isc_throw(InvalidOperation, "unable to write to DUID file "
+                          << storage_location_);
+            }
+        } catch (...) {
+            // Close stream before leaving the function.
+            ofs.close();
+            throw;
+        }
+        ofs.close();
+    }
+
+    duid_.reset(new DUID(duid_vector));
+}
+
 DuidPtr
 DUIDFactory::get() {
+    // If DUID is initialized, return it.
+    if (duid_) {
+        return (duid_);
+    }
+
+    // If DUID object hasn't been initialized then we need to retrieve a
+    // DUID from the file or create one.
+    std::ostringstream duid_str;
+    if (isPersisted()) {
+        std::ifstream ifs;
+        ifs.open(storage_location_, std::ifstream::in);
+        if (ifs.good()) {
+            std::string read_contents;
+            while (!ifs.eof() && ifs.good()) {
+                ifs >> read_contents;
+                duid_str << read_contents;
+            }
+        }
+        ifs.close();
+
+        // If we have read anything from the file, let's try to use it to
+        // create a DUID.
+        if (duid_str.tellp() != 0) {
+            try {
+                duid_.reset(new DUID(DUID::fromText(duid_str.str())));
+                return (duid_);
+
+            } catch (...) {
+                // The contents of this file don't represent a valid DUID.
+                // We'll need to generate it.
+            }
+        }
+
+    }
+
+    try {
+        // There is no file with a DUID or the DUID stored in the file is
+        // invalid. We need to generate a new DUID.
+        createLLT(0, 0, std::vector<uint8_t>());
+
+    } catch (...) {
+        // It is possible that the creation of the DUID-LLT failed if there
+        // are no suitable interfaces present in the system.
+    }
+
+    if (!duid_) {
+        // Fall back to creation of DUID enterprise. If that fails we allow
+        // for propagating exception to indicate a fatal error. This may
+        // be the case if we failed to write it to a file.
+        createEN(0, std::vector<uint8_t>());
+    }
+
     return (duid_);
 }
 

+ 130 - 7
src/lib/dhcp/duid_factory.h

@@ -16,6 +16,7 @@
 #define DUID_FACTORY_H
 
 #include <dhcp/duid.h>
+#include <boost/noncopyable.hpp>
 #include <stdint.h>
 #include <string>
 #include <vector>
@@ -23,28 +24,150 @@
 namespace isc {
 namespace dhcp {
 
-class DUIDFactory {
+/// @brief Factory for generating DUIDs (DHCP Unique Identifiers).
+///
+/// DHCPv6 clients and servers are identified by DUIDs (see RFC3315).
+/// DUIDs are unique identifiers carried in the appropriate DHCP
+/// options. RFC3315 defines 3 types of DUIDs:
+/// -# DUID-LLT
+/// -# DUID-EN
+/// -# DUID-LL
+///
+/// of which the DUID-LLT is recommended for all general purpose computing
+/// devices. RFC6355 defines new DUID-UUID and any future specifications may
+/// define new DUID types. The current implementation of the class only
+/// supports DUID types defined in RFC3315.
+///
+/// In most cases DUIDs can be generated automatically, i.e. no manual
+/// configuration is required. For example, DUID-LLT is composed of the
+/// current time and link layer address and type of one of the network
+/// interfaces. Once the DUID is generated it should be stored in the persistent
+/// storage and used by a server or client even when the network interface which
+/// address had been used to generate the DUID is removed.
+///
+/// In some cases administrators may elect to use other types of DUIDs, which
+/// are easier to generate (in case of lack of persistent storage or when
+/// specifics of the device favors some generation methods), e.g. DUID-EN
+/// doesn't rely on the link layer addresses of interfaces present in the
+/// system.
+///
+/// In some cases administrators may want to influence the value of the
+/// generated DUID. For example, DUID-EN includes enterprise identifier and
+/// the administrator may want to select this identifier.
+///
+/// This class allows for selecting a type of DUID to be generated. It also
+/// allows for setting desired values for the components of the DUIDs
+/// being generated, while leaving other components unspecified. For example
+/// an administrator may elect to set the enterprise id for the DUID-EN
+/// and leave the variable length identifier unspecified. The variable
+/// length identifier will be autogenerated.
+///
+/// This class is also responsible for storing the generated DUID in a
+/// file. The location of this file is specified in the class constructor.
+/// If this location is not specified the DUID is not persisted, i.e. is
+/// lost when the server or client shuts down. However, the DUID may be
+/// reconstructed according to the configuration of the client or server
+/// when they are back online.
+class DUIDFactory : public boost::noncopyable {
 public:
 
+    /// @brief Constructor.
+    ///
+    /// @param storage_location Absolute path to the file where DUID is
+    /// stored.
     DUIDFactory(const std::string& storage_location = "");
 
+    /// @brief Checks if generated DUID will be persisted in the file.
+    ///
+    /// @return true if generated DUIDs are persisted in a file, false
+    /// otherwise.
     bool isPersisted() const;
 
+    /// @brief Generates DUID-LLT.
+    ///
+    /// This method generates DUID-LLT.
+    ///
+    /// @param htype Link layer type. If this is set to 0 and link layer
+    /// address is empty a default value of @c HTYPE_ETHER is used.
+    /// Otherwise a link layer type of selected interface is used.
+    /// @param time_in Explicit value of time for the DUID. If this is
+    /// set to 0 a current time is used, otherwise a value specified is
+    /// used.
+    /// @param ll_identifier Data to be used as link layer address. If
+    /// this is an empty vector this method will iterate over all
+    /// active interfaces and will pick link layer address of one of them.
+    ///
+    /// @throw isc::Unexpected if none of the interfaces includes has a
+    /// suitable link layer address.
     void createLLT(const uint16_t htype, const uint32_t time_in,
                    const std::vector<uint8_t>& ll_identifier);
 
-    void createEN(const uint32_t enterprise_id, const std::vector<uint8_t>& identifier);
-
-    void createLL(const uint16_t htype, const std::vector<uint8_t>& ll_identifier);
-
+    /// @brief Generates DUID-EN.
+    ///
+    /// @param enterprise_id Enterprise id. If this value is 0, the ISC's
+    /// enterprise id is used.
+    /// @param identifier Data to be used as variable length identifier.
+    /// If this is an empty vector, the 6-bytes long vector with random
+    /// values is generated.
+    void createEN(const uint32_t enterprise_id,
+                  const std::vector<uint8_t>& identifier);
+
+    /// @brief Generates DUID-LL.
+    ///
+    /// @param htype Link layer type. If this is set to 0 and link layer
+    /// address is empty a default value of @c HTYPE_ETHER is used.
+    /// Otherwise a link layer type of selected interface is used.
+    /// @param ll_identifier Data to be used as link layer address. If
+    /// this is an empty vector this method will iterate over all
+    /// active interfaces and will pick link layer address of one of them.
+    ///
+    /// @throw isc::Unexpected if none of the interfaces includes has a
+    /// suitable link layer address.
+    void createLL(const uint16_t htype,
+                  const std::vector<uint8_t>& ll_identifier);
+
+    /// @brief Returns current DUID.
+    ///
+    /// This method first checks if the DUID has been generated, i.e. as a
+    /// result of calling DUIDFactory::createLLT. If the DUID hasn't been
+    /// generated, this method will try to read the DUID from the persistent
+    /// storage. If the DUID is found in persistent storage it is returned.
+    /// Otherwise, the DUID-LLT is generated and returned. In some cases the
+    /// generation of the DUID-LLT may fail, e.g. when there are no interfaces
+    /// with a suitable link layer address. In this case, this method will
+    /// generate DUID-EN, with the ISC enterprise id. If this fails, e.g. as a
+    /// result of error while persisting the generated DUID-EN, exception
+    /// is thrown.
+    ///
+    /// @return Instance of the DUID read from file, or generated.
     DuidPtr get();
 
 private:
 
-    void createLinkLayerId(std::vector<uint8_t>& identifier) const;
-
+    /// @brief Creates link layer identifier.
+    ///
+    /// This method iterates over existing network interfaces and finds the
+    /// one with a suitable link layer address to generate a DUID-LLT or
+    /// DUID-LL. It uses selected link layer address to generate identifier
+    /// held in those DUID types.
+    ///
+    /// @param [out] identifier Link layer address for the DUID.
+    /// @param [out] htype Link layer type to be included in the DUID.
+    void createLinkLayerId(std::vector<uint8_t>& identifier,
+                           uint16_t& htype) const;
+
+    /// @brief Sets a new DUID as current.
+    ///
+    /// The generated DUID is persisted in the file, if such file is specified.
+    /// The new DUID will be returned when @c DUIDFactory::get is called.
+    ///
+    /// @param duid_vector New DUID represented as vector of bytes.
+    void set(const std::vector<uint8_t>& duid_vector);
+
+    /// @brief Location of the file holding generated DUID (if specified).
     std::string storage_location_;
 
+    /// @brief Pointer to generated DUID.
     DuidPtr duid_;
 
 };

+ 212 - 4
src/lib/dhcp/tests/duid_factory_unittest.cc

@@ -17,9 +17,11 @@
 #include <dhcp/duid_factory.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <util/encode/hex.h>
+#include <util/range_utilities.h>
 #include <boost/algorithm/string.hpp>
 #include <gtest/gtest.h>
 #include <ctime>
+#include <fstream>
 #include <iomanip>
 #include <sstream>
 #include <stdio.h>
@@ -29,41 +31,97 @@
 using namespace isc;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
+using namespace isc::util;
 
 namespace {
 
+/// @brief Name of the file holding DUID generated during a test.
 const std::string DEFAULT_DUID_FILE = "duid-factory-test.duid";
 
+/// @brief Test fixture class for @c DUIDFactory.
 class DUIDFactoryTest : public ::testing::Test {
 public:
 
+    /// @brief Constructor.
+    ///
+    /// Creates fake interface configuration. It also creates an instance
+    /// of the @c DUIDFactory object used throughout the tests.
     DUIDFactoryTest();
 
+    /// @brief Destructor.
     virtual ~DUIDFactoryTest();
 
+    /// @brief Returns absolute path to a test DUID storage.
+    ///
+    /// @param duid_file_name Name of the file holding test DUID.
     std::string absolutePath(const std::string& duid_file_path) const;
 
+    /// @brief Removes default DUID file used in the tests.
+    ///
+    /// This method is called from both constructor and destructor.
     void removeDefaultFile() const;
 
+    /// @brief Returns contents of the DUID file.
+    std::string readDefaultFile() const;
+
+    /// @brief Converts string of hexadecimal digits to vector.
+    ///
+    /// @param hex String representation.
+    /// @return Vector created from the converted string.
     std::vector<uint8_t> toVector(const std::string& hex) const;
 
+    /// @brief Converts vector to string of hexadecimal digits.
+    ///
+    /// @param vec Input vector.
+    /// @return String of hexadecimal digits converted from vector.
     std::string toString(const std::vector<uint8_t>& vec) const;
 
+    /// @brief Converts current time to a string of hexadecimal digits.
+    ///
+    /// @return Time represented as text.
     std::string timeAsHexString() const;
 
+    /// @brief Tests creation of a DUID-LLT.
+    ///
+    /// @param expected_htype Expected link layer type as string.
+    /// @param expected_time Expected time as string.
+    /// @param time_equal Indicates if @c expected time should be
+    /// compared for equality with the time being part of a DUID
+    /// (if true), or the time being part of the DUID should be
+    /// less or equal current time (if false).
+    /// @param expected_hwaddr Expected link layer type as string.
     void testLLT(const std::string& expected_htype,
                  const std::string& expected_time,
                  const bool time_equal,
                  const std::string& expected_hwaddr);
 
+    /// @brief Tests creation of a DUID-EN.
+    ///
+    /// @param expected_enterprise_id Expected enterprise id as string.
+    /// @param expected_identifier Expected variable length identifier
+    /// as string.
+    void testEN(const std::string& expected_enterprise_id,
+                const std::string& expected_identifier = "");
+
+    /// @brief Tests creation of a DUID-LL.
+    ///
+    /// @param expected_htype Expected link layer type as string.
+    /// @param expected_hwaddr Expected link layer type as string.
+    void testLL(const std::string& expected_htype,
+                const std::string& expected_hwaddr);
+
+    /// @brief Returns reference to a default factory.
     DUIDFactory& factory() {
         return (factory_);
     }
 
 private:
 
+    /// @brief Creates fake interface configuration.
     IfaceMgrTestConfig iface_mgr_test_config_;
 
+    /// @brief Holds default instance of the @c DUIDFactory class, being
+    /// used throughout the tests.
     DUIDFactory factory_;
 
 };
@@ -90,6 +148,24 @@ DUIDFactoryTest::removeDefaultFile() const {
     static_cast<void>(remove(absolutePath(DEFAULT_DUID_FILE).c_str()));
 }
 
+std::string
+DUIDFactoryTest::readDefaultFile() const {
+    std::ifstream ifs;
+    ifs.open(absolutePath(DEFAULT_DUID_FILE), std::ifstream::in);
+    if (!ifs.good()) {
+        return (std::string());
+    }
+    std::string buf;
+    std::ostringstream output;
+    while (!ifs.eof() && ifs.good()) {
+        ifs >> buf;
+        output << buf;
+    }
+    ifs.close();
+
+    return (output.str());
+}
+
 std::vector<uint8_t>
 DUIDFactoryTest::toVector(const std::string& hex) const {
     std::vector<uint8_t> vec;
@@ -148,6 +224,56 @@ DUIDFactoryTest::testLLT(const std::string& expected_htype,
 
     // MAC address of the interface.
     EXPECT_EQ(expected_hwaddr, duid_text.substr(16));
+
+    // Compare DUID with the one stored in the file.
+    EXPECT_EQ(duid->toText(), readDefaultFile());
+}
+
+void
+DUIDFactoryTest::testEN(const std::string& expected_enterprise_id,
+                        const std::string& expected_identifier) {
+    DuidPtr duid = factory().get();
+    ASSERT_TRUE(duid);
+    ASSERT_GE(duid->getDuid().size(), 8);
+    std::string duid_text = toString(duid->getDuid());
+
+    // DUID type EN.
+    EXPECT_EQ("0002", duid_text.substr(0, 4));
+    // Verify enterprise ID.
+    EXPECT_EQ(expected_enterprise_id, duid_text.substr(4, 8));
+
+    // If no expecyed identifier, we should at least check that the
+    // generated identifier contains some random non-zero digits.
+    if (expected_identifier.empty()) {
+        EXPECT_FALSE(isRangeZero(duid->getDuid().begin(),
+                                 duid->getDuid().end()));
+    } else {
+        // Check if identifier matches.
+        EXPECT_EQ(expected_identifier, duid_text.substr(12));
+    }
+
+    // Compare DUID with the one stored in the file.
+    EXPECT_EQ(duid->toText(), readDefaultFile());
+}
+
+void
+DUIDFactoryTest::testLL(const std::string& expected_htype,
+                        const std::string& expected_hwaddr) {
+    DuidPtr duid = factory().get();
+    ASSERT_TRUE(duid);
+    ASSERT_GE(duid->getDuid().size(), 8);
+    std::string duid_text = toString(duid->getDuid());
+
+    // DUID type LL
+    EXPECT_EQ("0003", duid_text.substr(0, 4));
+    // Link layer type HTYPE_ETHER
+    EXPECT_EQ(expected_htype, duid_text.substr(4, 4));
+
+    // MAC address of the interface.
+    EXPECT_EQ(expected_hwaddr, duid_text.substr(8));
+
+    // Compare DUID with the one stored in the file.
+    EXPECT_EQ(duid->toText(), readDefaultFile());
 }
 
 
@@ -171,12 +297,12 @@ TEST_F(DUIDFactoryTest, createLLTExplicitTime) {
     testLLT("0001", "00ABCDEF", true, "010101010101");
 }
 
-// This test verifies that the factory class creates DUID-LLT from
-// the explcitly specified link layer type, when the time and link
-// layer address are generated.
+// This test verifies that the factory class creates DUID-LLT with
+// the link layer type of the interface which link layer address
+// is used to generate the DUID.
 TEST_F(DUIDFactoryTest, createLLTExplicitHtype) {
     ASSERT_NO_THROW(factory().createLLT(HTYPE_FDDI, 0, std::vector<uint8_t>()));
-    testLLT("0008", timeAsHexString(), false, "010101010101");
+    testLLT("0001", timeAsHexString(), false, "010101010101");
 }
 
 // This test verifies that the factory class creates DUID-LLT from
@@ -195,4 +321,86 @@ TEST_F(DUIDFactoryTest, createLLTAllExplcitParameters) {
     testLLT("0008", "FAFAFAFA", true, "24242424242424242424");
 }
 
+// This test verifies that the DUID-EN can be generated entirely. Such
+// generated DUID contains ISC enterprise id and the random identifier.
+TEST_F(DUIDFactoryTest, createEN) {
+    ASSERT_NO_THROW(factory().createEN(0, std::vector<uint8_t>()));
+    testEN("000009BF");
+}
+
+// This test verifies that the DUID-EN may contain custom enterprise id.
+TEST_F(DUIDFactoryTest, createENExplicitEnterpriseId) {
+    ASSERT_NO_THROW(factory().createEN(0xABCDEFAB, std::vector<uint8_t>()));
+    testEN("ABCDEFAB");
+}
+
+// This test verifies that DUID-EN may contain custom variable length
+// identifier and default enterprise id.
+TEST_F(DUIDFactoryTest, createENExplicitIdentifier) {
+    ASSERT_NO_THROW(factory().createEN(0, toVector("1212121212121212")));
+    testEN("000009BF", "1212121212121212");
+}
+
+// This test verifies that DUID-EN can be created from explicit enterprise id
+// and identifier.
+TEST_F(DUIDFactoryTest, createENAllExplicitParameters) {
+    ASSERT_NO_THROW(factory().createEN(0x01020304, toVector("ABCD")));
+    testEN("01020304", "ABCD");
+}
+
+// This test verifies that the DUID-LL is generated when neither link layer
+// type nor address is specified.
+TEST_F(DUIDFactoryTest, createLL) {
+    ASSERT_NO_THROW(factory().createLL(0, std::vector<uint8_t>()));
+    testLL("0001", "010101010101");
+}
+
+// This test verifies that the DUID-LL is generated and the link layer type
+// used is taken from the interface used to generate link layer address.
+TEST_F(DUIDFactoryTest, createLLExplicitHtype) {
+    ASSERT_NO_THROW(factory().createLL(HTYPE_FDDI, std::vector<uint8_t>()));
+    testLL("0001", "010101010101");
+}
+
+// This test verifies that DUID-LL is created from explicitly provided
+// link layer type and address.
+TEST_F(DUIDFactoryTest, createLLAllExplicitParameters) {
+    ASSERT_NO_THROW(factory().createLL(HTYPE_FDDI, toVector("242424242424")));
+    testLL("0008", "242424242424");
+}
+
+// This test verifies that DUID-LLT is created when caller wants to obtain
+// it and it doesn't exist.
+TEST_F(DUIDFactoryTest, createLLTIfNotExists) {
+    DuidPtr duid;
+    ASSERT_NO_THROW(duid = factory().get());
+    ASSERT_TRUE(duid);
+    EXPECT_EQ(DUID::DUID_LLT, duid->getType());
+}
+
+// This test verifies that DUID-EN when there is no suitable interface to
+// use to create DUID-LLT.
+TEST_F(DUIDFactoryTest, createENIfNotExists) {
+    // Remove interfaces. The DUID-LLT is a default type but it requires
+    // that an interface with a suitable link-layer address is present
+    // in the system. By removing the interfaces we cause the factory
+    // to fail to generate DUID-LLT. It should fall back to DUID-EN.
+    IfaceMgr::instance().clearIfaces();
+
+    DuidPtr duid;
+    ASSERT_NO_THROW(duid = factory().get());
+    ASSERT_TRUE(duid);
+    EXPECT_EQ(DUID::DUID_EN, duid->getType());
+}
+
+// This test verifies that it is possible to override a DUID.
+TEST_F(DUIDFactoryTest, override) {
+    // Create default DUID-LLT.
+    ASSERT_NO_THROW(static_cast<void>(factory().get()));
+    testLLT("0001", timeAsHexString(), false, "010101010101");
+
+    ASSERT_NO_THROW(factory().createEN(0, toVector("12131415")));
+    testEN("000009BF", "12131415");
+}
+
 } // End anonymous namespace

+ 1 - 0
src/lib/dhcp/tests/iface_mgr_test_config.cc

@@ -99,6 +99,7 @@ IfaceMgrTestConfig::createIface(const std::string &name, const int ifindex) {
     // Set MAC address to 01:01:01:01:01:01.
     std::vector<uint8_t> mac_vec(6, 1);
     iface->setMac(&mac_vec[0], mac_vec.size());
+    iface->setHWType(HTYPE_ETHER);
 
     return (iface);
 }