Browse Source

[master] Merge branch 'trac3874a'

Marcin Siodelski 9 years ago
parent
commit
faf52b069c

+ 80 - 0
doc/examples/kea6/duid.json

@@ -0,0 +1,80 @@
+# This is an example configuration file for DHCPv6 server in Kea.
+# It demonstrates how to configure Kea to use DUID-LLT with some
+# values specified explicitly.
+
+{ "Dhcp6":
+
+{
+
+# Configure server identifier (DUID-LLT). The hexadecimal value of the
+# identifier will be used as link layer address component of the DUID.
+# The link layer type will be ethernet. The value of time is set to 0
+# which indicates that the server must generate this value, i.e. use
+# current time. Note that it is easy to move from this configuration
+# to DUID-EN or DUID-LL. It would require changing the "type" value
+# to "EN" or "LL" respectively. The "identifier" would hold a
+# DUID-EN variable length identifier or DUID-LL link layer address.
+# The values of "time" and "htype" would be ignored for DUID-EN.
+# If one wanted to use a non-default enterprise-id for DUID-EN, the
+# "enterprise-id" parameter would need to be added. Note that only
+# a "type" parameter is mandatory while specifying "server-id" map.
+  "server-id": {
+    "type": "LLT",
+    "identifier": "12C4D5AF870C",
+    "time": 0,
+    "htype": 1
+  },
+
+# Kea is told to listen on ethX interface only.
+  "interfaces-config": {
+    "interfaces": [ "ethX" ]
+  },
+
+# We need to specify lease type. As of May 2014, three backends are supported:
+# memfile, mysql and pgsql. We'll just use memfile, because it doesn't require
+# any prior set up.
+  "lease-database": {
+    "type": "memfile"
+  },
+
+# Addresses will be assigned with preferred and valid lifetimes
+# being 3000 and 4000, respectively. Client is told to start
+# renewing after 1000 seconds. If the server does not respond
+# after 2000 seconds since the lease was granted, client is supposed
+# to start REBIND procedure (emergency renewal that allows switching
+# to a different server).
+  "preferred-lifetime": 3000,
+  "valid-lifetime": 4000,
+  "renew-timer": 1000,
+  "rebind-timer": 2000,
+
+# The following list defines subnets. Each subnet consists of at
+# least subnet and pool entries.
+  "subnet6": [ 
+    {
+      "pools": [ { "pool": "2001:db8:1::/80" } ],
+      "subnet": "2001:db8:1::/64",
+      "interface": "ethX"
+    }
+  ]
+},
+
+# The following configures logging. Kea will log all debug messages
+# to /var/log/kea-debug.log file.
+"Logging": {
+    "loggers": [
+        {
+            "name": "kea-dhcp6",
+            "output_options": [
+                {
+                    "output": "/var/log/kea-debug.log"
+                }
+            ], 
+            "debuglevel": 0,
+            "severity": "INFO"
+        }
+    ]
+}
+
+}
+

+ 204 - 19
doc/guide/dhcp6-srv.xml

@@ -2393,27 +2393,212 @@ should include options from the isc option space:
       <title>Server Identifier in DHCPv6</title>
       <para>The DHCPv6 protocol uses a "server identifier" (also known
       as a DUID) for clients to be able to discriminate between several
-      servers present on the same link.  There are several types of
-      DUIDs defined, but <ulink url="http://tools.ietf.org/html/rfc3315">RFC 3315</ulink> instructs servers to use DUID-LLT if
-      possible. This format consists of a link-layer (MAC) address and a
-      timestamp. When started for the first time, the DHCPv6 server will
-      automatically generate such a DUID and store the chosen value to
-      a file.  That file is read by the server
-      and the contained value used whenever the server is subsequently started.
-      </para>
-      <para>
-        It is unlikely that this parameter should ever need to be changed.
-        However, if such a need arises, stop the server, edit the file and restart
-        the server. (The file is named kea-dhcp6-serverid and by default is
-        stored in the "var" subdirectory of the directory in which Kea is installed.
-        This can be changed when Kea is built by using "--localstatedir"
-        on the "configure" command line.)  The file is a text file that contains
-        double digit hexadecimal values
-        separated by colons. This format is similar to typical MAC address
-        format. Spaces are ignored. No extra characters are allowed in this
-        file.
+      servers present on the same link.
+      <ulink url="http://tools.ietf.org/html/rfc3315">RFC 3315</ulink>
+      defines three DUID types: DUID-LLT, DUID-EN and DUID-LL.
+      <ulink url="http://tools.ietf.org/html/rfc6355">RFC 6355</ulink>
+      also defines DUID-UUID. Future specifications may introduce new
+      DUID types.</para>
+
+      <para>Kea DHCPv6 server generates a server identifier once, upon
+      the first startup, and stores it in a file. This identifier isn't
+      modified across restarts of the server (stable identifier).</para>
+
+      <para>Kea follows recommendation from
+      <ulink url="http://tools.ietf.org/html/rfc3315">RFC 3315</ulink>
+      to use DUID-LLT as a default server identifier. However, we have
+      received reports that some deployments require different DUID
+      types, and there is a need to administratively select both DUID
+      type and/or its contents.</para>
+
+      <para>The server identifier can be configured using parameters
+      within the <command>server-id</command> map element in the global
+      scope of the Kea configuration file. The following example
+      demonstrates how to select DUID-EN as a server identifier:
+
+<screen>
+"Dhcp6": {
+    "server-id": {
+        "type": "EN"
+    },
+    ...
+}
+</screen>
+
+      </para>
+
+      <para>Currently supported values for <command>type</command>
+      parameter are: "LLT", "EN" and "LL", for DUID-LLT, DUID-EN and
+      DUID-LL respectively.</para>
+
+      <para>When a new DUID type is selected the server will generate its
+      value and replace any existing DUID in the file. The server will
+      use the new server identifier in all future interactions with the
+      clients.</para>
+
+      <note><para>If the new server identifier is created after some clients
+      have obtained their leases, the clients using old identifier will not
+      be able to renew the leases. The server will ignore messages
+      containing the old server identifier. Clients will continue sending
+      Renew until they transition to rebinding state. In this state they
+      will start sending Rebind messages to multicast address and without
+      a server identifier. The server will respond to the Rebind messages
+      with a new server identifier and the clients will associate the
+      new server identifier with their leases. Although the clients will
+      be able to keep their leases and will eventually learn the new server
+      identifier, this will be at the cost of increased number of renewals
+      and multicast traffic due to a need to rebind. Therefore it is
+      recommended to avoid modification of the server identifier type
+      and its value if the server has already assigned leases and these
+      leases are still valid.</para></note>
+
+      <para>There are cases when an administrator needs to explicitly
+      specify a DUID value, rather than allow the server to generate it.
+      The following example demonstrates how to explicitly set all
+      components of a DUID-LLT.
+
+<screen>
+"Dhcp6": {
+    "server-id": {
+        "type": "LLT",
+        "htype": 8,
+        "identifier": "A65DC7410F05",
+        "time": 2518920166
+    },
+    ...
+}
+</screen>
+
+      where:
+      <itemizedlist>
+        <listitem><simpara><command>htype</command> is a 16-bit unsigned value
+        specifying hardware type,</simpara></listitem>
+        <listitem><simpara><command>identifier</command> is a link layer
+        address, specified as a string of hexadecimal digits,</simpara>
+        </listitem>
+        <listitem><simpara><command>time</command> is a 32-bit unsigned
+        time value.</simpara></listitem>
+      </itemizedlist>
+      </para>
+
+      <para>The hexadecimal representation of the DUID generated as a result
+      of the configuration specified above will be:
+<screen>
+00:01:00:08:96:23:AB:E6:A6:5D:C7:41:0F:05
+------------------------------------------
+|type|htype|   time    |   identifier    |
+</screen>
+      </para>
+
+      <para>It is allowed to use special value of 0 for "htype" and "time",
+      which indicates that the server should use ANY value for these
+      components. If the server already uses a DUID-LLT it will use the
+      values from this DUID. If the server uses a DUID of a different type
+      or doesn't use any DUID yet, it will generate these values.
+      Similarly, if the "identifier" is assigned an empty string, the
+      value of the identifier will be generated. Omitting any of these
+      parameters is equivalent to setting them to those special values.
+      </para>
+
+      <para>For example, the following configuration:
+<screen>
+"Dhcp6": {
+    "server-id": {
+        "type": "LLT",
+        "htype": 0,
+        "identifier": "",
+        "time": 2518920166
+    },
+    ...
+}
+</screen>
+
+      indicates that the server should use ANY link layer address and
+      hardware type. If the server is already using DUID-LLT it will
+      use link layer address and hardware type from the existing DUID.
+      If the server is not using any DUID yet, it will use link layer
+      address and hardware type from one of the available network
+      interfaces. The server will use explicit value of time. If it
+      is different than a time value present in the currently used
+      DUID, this value will be replaced. This will effectively cause
+      modification of the current server identifier.
+      </para>
+
+      <para>
+        The following example demonstrates an explicit configuration of
+        a DUID-EN:
+
+<screen>
+"Dhcp6": {
+    "server-id": {
+        "type": "EN",
+        "enterprise-id": 2495,
+        "identifier": "87ABEF7A5BB545",
+    },
+    ...
+}
+</screen>
+
+      where:
+      <itemizedlist>
+        <listitem><simpara><command>enterprise-id</command> is a 32-bit
+        unsigned value holding enterprise number,</simpara></listitem>
+        <listitem><simpara><command>identifier</command> is a variable
+        length identifier within DUID-EN.</simpara></listitem>
+      </itemizedlist>
+      </para>
+
+      <para>
+        The hexadecimal representation of the DUID-EN created accoring to
+        the configuration above is:
+<screen>
+00:02:00:00:09:BF:87:AB:EF:7A:5B:B5:45
+--------------------------------------
+|type|  ent-id   |    identifier     |
+</screen>
       </para>
 
+      <para>As in the case of the DUID-LLT, special values can be used for the
+      configuration of the DUID-EN. If the "enterprise-id" is 0, the server
+      will use a value from the existing DUID-EN. If the server is not using
+      any DUID or the existing DUID has a different type, the ISC enterprise
+      id will be used. When an empty string is used for "identifier", the
+      identifier from the existing DUID-EN will be used. If the server is
+      not using any DUID-EN the new 6-bytes long identifier will be generated.
+      </para>
+
+      <para>DUID-LL is configured in the same way as DUID-LLT with an exception
+      that the <command>time</command> parameter has no effect for DUID-LL,
+      because this DUID type only comprises a hardware type and link layer
+      address. The following example demonstrates how to configure DUID-LL:
+
+<screen>
+"Dhcp6": {
+    "server-id": {
+        "type": "LL",
+        "htype": 8,
+        "identifier": "A65DC7410F05",
+    },
+    ...
+}
+</screen>
+
+      </para>
+
+      <para>
+      which will result in the following server identifier:
+
+<screen>
+00:03:00:08:A6:5D:C7:41:0F:05
+------------------------------
+|type|htype|   identifier   |
+</screen>
+      </para>
+
+      <para>Server stores a generated server identifier in the following
+      location: <userinput>[kea-install-dir]/var/kea/kea-dhcp6-serverid
+      </userinput>.
+      </para>
     </section>
 
     <section id="stateless-dhcp6">

+ 25 - 0
src/bin/dhcp6/ctrl_dhcp6_srv.cc

@@ -28,6 +28,13 @@ using namespace isc::hooks;
 using namespace isc::stats;
 using namespace std;
 
+namespace {
+
+// Name of the file holding server identifier.
+static const char* SERVER_DUID_FILE = "kea-dhcp6-serverid";
+
+}
+
 namespace isc {
 namespace dhcp {
 
@@ -173,6 +180,24 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
                                           + string(ex.what())));
     }
 
+    // Regenerate server identifier if needed.
+    try {
+        const std::string duid_file = CfgMgr::instance().getDataDir() + "/" +
+            std::string(SERVER_DUID_FILE);
+        DuidPtr duid = CfgMgr::instance().getStagingCfg()->getCfgDUID()->create(duid_file);
+        server_->serverid_.reset(new Option(Option::V6, D6O_SERVERID, duid->getDuid()));
+        if (duid) {
+            LOG_INFO(dhcp6_logger, DHCP6_USING_SERVERID)
+                .arg(duid->toText())
+                .arg(duid_file);
+        }
+
+    } catch (const std::exception& ex) {
+        std::ostringstream err;
+        err << "unable to configure server identifier: " << ex.what();
+        return (isc::config::createAnswer(1, err.str()));
+    }
+
     // Server will start DDNS communications if its enabled.
     try {
         srv->startD2();

+ 38 - 0
src/bin/dhcp6/dhcp6.spec

@@ -4,6 +4,44 @@
     "module_description": "DHCPv6 server daemon",
     "config_data": [
       {
+        "item_name": "server-id",
+        "item_type": "map",
+        "item_optional": true,
+        "item_default": { "type": "LLT" },
+        "map_item_spec": [
+        {
+            "item_name": "type",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": ""
+        },
+        {
+            "item_name": "identifier",
+            "item_type": "string",
+            "item_optional": true,
+            "item_default": ""
+        },
+        {
+            "item_name": "htype",
+            "item_type": "integer",
+            "item_optional": true,
+            "item_default": 0
+        },
+        {
+            "item_name": "time",
+            "item_type": "integer",
+            "item_optional": true,
+            "item_default": 0
+        },
+        {
+            "item_name": "enterprise-id",
+            "item_type": "integer",
+            "item_optional": true,
+            "item_default": 0
+        }
+        ]
+      },
+      {
         "item_name": "hooks-libraries",
         "item_type": "list",
         "item_optional": true,

+ 10 - 27
src/bin/dhcp6/dhcp6_messages.mes

@@ -644,33 +644,16 @@ etc. The exact reason for rejecting the packet is included in the message.
 % DHCP6_RESPONSE_DATA responding with packet type %1 data is %2
 A debug message listing the data returned to the client.
 
-% DHCP6_SERVERID_GENERATED server-id %1 has been generated and will be stored in %2
-This informational messages indicates that the server was not able to read
-its server identifier (DUID) and has generated a new one. This server-id
-will be stored in a file and will be read and used during next restart. It
-is normal behavior when the server is started for the first time. If
-this message is printed every start, please check that the server have
-sufficient permission to write its server-id file and that the file is not
-corrupt.
-
-Changing the server identifier in a production environment is not
-recommended as existing clients will not recognize the server and may go
-through a rebind phase. However, they should be able to recover without
-losing their leases.
-
-% DHCP6_SERVERID_LOADED server-id %1 has been loaded from file %2
-This debug message indicates that the server loaded its server identifier.
-That value is sent in all server responses and clients use it to
-discriminate between servers. This is a part of normal startup or
-reconfiguration procedure.
-
-% DHCP6_SERVERID_WRITE_FAIL server was not able to write its ID to file %1
-This warning message indicates that server was not able to write its
-server identifier (DUID) to a file. This likely indicates lack of write
-permission to a given file or directory. This is not critical and the
-server will continue to operate, but server will generate different DUID
-during every start and clients will need to go through a rebind phase
-to recover.
+% DHCP6_USING_SERVERID server is using server-id %1 and stores in the the file %2
+This info message is logged when the server reads its server-id from a
+file or generates it. This message is a notification to the administrator
+what server-id will be used and where it is persisted. Typically, there is
+no need to modify the server id. However, it is possible to do it in the
+Kea configuration file. It is important to understand the implications of
+such modification. The clients will remember previous server-id, and will
+use it to extend their leases. As a result, they will have to go through
+a rebinding phase to re-acquire their leases and associate them with a
+new server id.
 
 % DHCP6_SERVER_FAILED server failed: %1
 The IPv6 DHCP server has encountered a fatal error and is terminating.

+ 5 - 144
src/bin/dhcp6/dhcp6_srv.cc

@@ -19,6 +19,7 @@
 #include <dhcp/dhcp6.h>
 #include <dhcp/docsis3_option_defs.h>
 #include <dhcp/duid.h>
+#include <dhcp/duid_factory.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option6_addrlst.h>
@@ -185,7 +186,7 @@ const std::string Dhcpv6Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
 static const char* SERVER_DUID_FILE = "kea-dhcp6-serverid";
 
 Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
-    : serverid_(), port_(port), shutdown_(true), alloc_engine_()
+    : port_(port), serverid_(), shutdown_(true), alloc_engine_()
 {
 
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
@@ -201,21 +202,9 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
         }
 
         string duid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_DUID_FILE);
-        if (loadServerID(duid_file)) {
-            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_SERVERID_LOADED)
-                .arg(duidToString(getServerID()))
-                .arg(duid_file);
-        } else {
-            generateServerID();
-            LOG_INFO(dhcp6_logger, DHCP6_SERVERID_GENERATED)
-                .arg(duidToString(getServerID()))
-                .arg(duid_file);
-
-            if (!writeServerID(duid_file)) {
-                LOG_WARN(dhcp6_logger, DHCP6_SERVERID_WRITE_FAIL)
-                    .arg(duid_file);
-            }
-        }
+        DUIDFactory duid_factory(duid_file);
+        DuidPtr duid = duid_factory.get();
+        serverid_.reset(new Option(Option::V6, D6O_SERVERID, duid->getDuid()));
 
         // Instantiate allocation engine. The number of allocation attempts equal
         // to zero indicates that the allocation engine will use the number of
@@ -719,38 +708,6 @@ bool Dhcpv6Srv::run() {
     return (true);
 }
 
-bool Dhcpv6Srv::loadServerID(const std::string& file_name) {
-
-    // load content of the file into a string
-    fstream f(file_name.c_str(), ios::in);
-    if (!f.is_open()) {
-        return (false);
-    }
-
-    string hex_string;
-    f >> hex_string;
-    f.close();
-
-    // remove any spaces
-    boost::algorithm::erase_all(hex_string, " ");
-
-    // now remove :
-    /// @todo: We should check first if the format is sane.
-    /// Otherwise 1:2:3:4 will be converted to 0x12, 0x34
-    boost::algorithm::erase_all(hex_string, ":");
-
-    std::vector<uint8_t> bin;
-
-    // Decode the hex string and store it in bin (which happens
-    // to be OptionBuffer format)
-    isc::util::encode::decodeHex(hex_string, bin);
-
-    // Now create server-id option
-    serverid_.reset(new Option(Option::V6, D6O_SERVERID, bin));
-
-    return (true);
-}
-
 std::string
 Dhcpv6Srv::duidToString(const OptionPtr& opt) {
     stringstream tmp;
@@ -771,102 +728,6 @@ Dhcpv6Srv::duidToString(const OptionPtr& opt) {
     return tmp.str();
 }
 
-bool
-Dhcpv6Srv::writeServerID(const std::string& file_name) {
-    fstream f(file_name.c_str(), ios::out | ios::trunc);
-    if (!f.good()) {
-        return (false);
-    }
-    f << duidToString(getServerID());
-    f.close();
-    return (true);
-}
-
-void
-Dhcpv6Srv::generateServerID() {
-
-    /// @todo: This code implements support for DUID-LLT (the recommended one).
-    /// We should eventually add support for other DUID types: DUID-LL, DUID-EN
-    /// and DUID-UUID
-
-    const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
-
-    // Let's find suitable interface.
-    BOOST_FOREACH(IfacePtr iface, ifaces) {
-        // All the following checks could be merged into one multi-condition
-        // statement, but let's keep them separated as perhaps one day
-        // we will grow knobs to selectively turn them on or off. Also,
-        // this code is used only *once* during first start on a new machine
-        // and then server-id is stored. (or at least it will be once
-        // DUID storage is implemented)
-
-        // I wish there was a this_is_a_real_physical_interface flag...
-
-        // MAC address should be at least 6 bytes. Although there is no such
-        // requirement in any RFC, all decent physical interfaces (Ethernet,
-        // WiFi, InfiniBand, etc.) have 6 bytes long MAC address. We want to
-        // base our DUID on real hardware address, rather than virtual
-        // interface that pretends that underlying IP address is its MAC.
-        if (iface->getMacLen() < MIN_MAC_LEN) {
-            continue;
-        }
-
-        // Let's don't use loopback.
-        if (iface->flag_loopback_) {
-            continue;
-        }
-
-        // Let's skip downed interfaces. It is better to use working ones.
-        if (!iface->flag_up_) {
-            continue;
-        }
-
-        // Some interfaces (like lo on Linux) report 6-bytes long
-        // MAC address 00:00:00:00:00:00. Let's not use such weird interfaces
-        // to generate DUID.
-        if (isRangeZero(iface->getMac(), iface->getMac() + iface->getMacLen())) {
-            continue;
-        }
-
-        // Ok, we have useful MAC. Let's generate DUID-LLT based on
-        // it. See RFC3315, Section 9.2 for details.
-
-        // DUID uses seconds since midnight of 01-01-2000, time() returns
-        // seconds since 01-01-1970. DUID_TIME_EPOCH substitution corrects
-        // that.
-        time_t seconds = time(NULL);
-        seconds -= DUID_TIME_EPOCH;
-
-        OptionBuffer srvid(8 + iface->getMacLen());
-        // We know that the buffer is more than 8 bytes long at this point.
-        writeUint16(DUID::DUID_LLT, &srvid[0], 2);
-        writeUint16(HWTYPE_ETHERNET, &srvid[2], 2);
-        writeUint32(static_cast<uint32_t>(seconds), &srvid[4], 4);
-        memcpy(&srvid[8], iface->getMac(), iface->getMacLen());
-
-        serverid_ = OptionPtr(new Option(Option::V6, D6O_SERVERID,
-                                         srvid.begin(), srvid.end()));
-        return;
-    }
-
-    // If we reached here, there are no suitable interfaces found.
-    // Either interface detection is not supported on this platform or
-    // this is really weird box. Let's use DUID-EN instead.
-    // See Section 9.3 of RFC3315 for details.
-
-    OptionBuffer srvid(12);
-    writeUint16(DUID::DUID_EN, &srvid[0], srvid.size());
-    writeUint32(ENTERPRISE_ID_ISC, &srvid[2], srvid.size() - 2);
-
-    // Length of the identifier is company specific. I hereby declare
-    // ISC "standard" of 6 bytes long pseudo-random numbers.
-    srandom(time(NULL));
-    fillRandom(&srvid[6], &srvid[12]);
-
-    serverid_ = OptionPtr(new Option(Option::V6, D6O_SERVERID,
-                                     srvid.begin(), srvid.end()));
-}
-
 void
 Dhcpv6Srv::copyClientOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
     // Add client-id.

+ 3 - 32
src/bin/dhcp6/dhcp6_srv.h

@@ -581,34 +581,6 @@ protected:
     void releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply,
                        AllocEngine::ClientContext6& ctx);
 
-    /// @brief Sets server-identifier.
-    ///
-    /// This method attempts to generate server-identifier DUID. It generates a
-    /// new DUID using interface link-layer addresses (EUI-64) + timestamp (DUID
-    /// type duid-llt, see RFC3315, section 9.2). If there are no suitable
-    /// interfaces present, exception it thrown
-    ///
-    /// @throws isc::Unexpected Failed to read DUID file and no suitable
-    ///         interfaces for new DUID generation are detected.
-    void generateServerID();
-
-    /// @brief attempts to load DUID from a file
-    ///
-    /// Tries to load duid from a text file. If the load is successful,
-    /// it creates server-id option and stores it in serverid_ (to be used
-    /// later by getServerID()).
-    ///
-    /// @param file_name name of the DUID file to load
-    /// @return true if load was successful, false otherwise
-    bool loadServerID(const std::string& file_name);
-
-    /// @brief attempts to write DUID to a file
-    /// Tries to write duid content (stored in serverid_) to a text file.
-    ///
-    /// @param file_name name of the DUID file to write
-    /// @return true if write was successful, false otherwise
-    bool writeServerID(const std::string& file_name);
-
     /// @brief converts DUID to text
     /// Converts content of DUID option to a text representation, e.g.
     /// 01:ff:02:03:06:80:90:ab:cd:ef
@@ -617,7 +589,6 @@ protected:
     /// @return string representation
     static std::string duidToString(const OptionPtr& opt);
 
-
     /// @brief dummy wrapper around IfaceMgr::receive6
     ///
     /// This method is useful for testing purposes, where its replacement
@@ -816,14 +787,14 @@ private:
     /// @param query packet transmitted
     static void processStatsSent(const Pkt6Ptr& response);
 
-    /// Server DUID (to be sent in server-identifier option)
-    OptionPtr serverid_;
-
     /// UDP port number on which server listens.
     uint16_t port_;
 
 protected:
 
+    /// Server DUID (to be sent in server-identifier option)
+    OptionPtr serverid_;
+
     /// Indicates if shutdown is in progress. Setting it to true will
     /// initiate server shutdown procedure.
     volatile bool shutdown_;

+ 3 - 0
src/bin/dhcp6/json_config_parser.cc

@@ -32,6 +32,7 @@
 #include <dhcpsrv/parsers/dbaccess_parser.h>
 #include <dhcpsrv/parsers/dhcp_config_parser.h>
 #include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/parsers/duid_config_parser.h>
 #include <dhcpsrv/parsers/expiration_config_parser.h>
 #include <dhcpsrv/parsers/host_reservation_parser.h>
 #include <dhcpsrv/parsers/host_reservations_list_parser.h>
@@ -704,6 +705,8 @@ DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id,
         parser = new ExpirationConfigParser();
     } else if (config_id.compare("client-classes") == 0) {
         parser = new ClientClassDefListParser(config_id, globalContext());
+    } else if (config_id.compare("server-id") == 0) {
+        parser = new DUIDConfigParser();
     } else {
         isc_throw(DhcpConfigError,
                 "unsupported global configuration parameter: "

+ 0 - 32
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -1432,38 +1432,6 @@ TEST_F(Dhcpv6SrvTest, selectSubnetRelayInterfaceId) {
     EXPECT_EQ(subnet2, srv.selectSubnet(pkt));
 }
 
-// This test verifies if the server-id disk operations (read, write) are
-// working properly.
-TEST_F(Dhcpv6SrvTest, ServerID) {
-    NakedDhcpv6Srv srv(0);
-
-    string duid1_text = "01:ff:02:03:06:80:90:ab:cd:ef";
-    uint8_t duid1[] = { 0x01, 0xff, 2, 3, 6, 0x80, 0x90, 0xab, 0xcd, 0xef };
-    OptionBuffer expected_duid1(duid1, duid1 + sizeof(duid1));
-
-    fstream file1(DUID_FILE, ios::out | ios::trunc);
-    file1 << duid1_text;
-    file1.close();
-
-    // Test reading from a file
-    EXPECT_TRUE(srv.loadServerID(DUID_FILE));
-    ASSERT_TRUE(srv.getServerID());
-    ASSERT_EQ(sizeof(duid1) + Option::OPTION6_HDR_LEN, srv.getServerID()->len());
-    ASSERT_TRUE(expected_duid1 == srv.getServerID()->getData());
-
-    // Now test writing to a file
-    EXPECT_EQ(0, remove(DUID_FILE));
-    EXPECT_NO_THROW(srv.writeServerID(DUID_FILE));
-
-    fstream file2(DUID_FILE, ios::in);
-    ASSERT_TRUE(file2.good());
-    string text;
-    file2 >> text;
-    file2.close();
-
-    EXPECT_EQ(duid1_text, text);
-}
-
 // Checks if server responses are sent to the proper port.
 TEST_F(Dhcpv6SrvTest, portsDirectTraffic) {
 

+ 0 - 2
src/bin/dhcp6/tests/dhcp6_test_utils.h

@@ -111,8 +111,6 @@ public:
     using Dhcpv6Srv::testUnicast;
     using Dhcpv6Srv::sanityCheck;
     using Dhcpv6Srv::classifyPacket;
-    using Dhcpv6Srv::loadServerID;
-    using Dhcpv6Srv::writeServerID;
     using Dhcpv6Srv::unpackOptions;
     using Dhcpv6Srv::shutdown_;
     using Dhcpv6Srv::name_change_reqs_;

+ 35 - 0
src/bin/dhcp6/tests/kea_controller_unittest.cc

@@ -349,4 +349,39 @@ TEST_F(JSONFileBackendTest, timers) {
     EXPECT_FALSE(lease_reclaimed);
 }
 
+// This test verifies that the DUID type can be selected.
+TEST_F(JSONFileBackendTest, serverId) {
+    string config =
+        "{ \"Dhcp6\": {"
+        "\"interfaces-config\": {"
+        "    \"interfaces\": [ ]"
+        "},"
+        "\"lease-database\": {"
+        "     \"type\": \"memfile\","
+        "     \"persist\": false"
+        "},"
+        "\"server-id\": {"
+        "     \"type\": \"EN\","
+        "     \"enterprise-id\": 1234"
+        "},"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ ],"
+        "\"preferred-lifetime\": 3000, "
+        "\"valid-lifetime\": 4000 }"
+        "}";
+    writeFile(TEST_FILE, config);
+
+    // Create an instance of the server and intialize it.
+    boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new ControlledDhcpv6Srv(0)));
+    ASSERT_NO_THROW(srv->init(TEST_FILE));
+
+    // Check that DUID configuration is affected.
+    ConstCfgDUIDPtr duid_cfg = CfgMgr::instance().getCurrentCfg()->getCfgDUID();
+    ASSERT_TRUE(duid_cfg);
+    EXPECT_EQ(DUID::DUID_EN, duid_cfg->getType());
+    EXPECT_EQ(1234, duid_cfg->getEnterpriseId());
+}
+
 } // End of anonymous namespace

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

@@ -17,6 +17,7 @@ libkea_dhcp___la_SOURCES  =
 libkea_dhcp___la_SOURCES += classify.cc classify.h
 libkea_dhcp___la_SOURCES += dhcp6.h dhcp4.h
 libkea_dhcp___la_SOURCES += duid.cc duid.h
+libkea_dhcp___la_SOURCES += duid_factory.cc duid_factory.h
 libkea_dhcp___la_SOURCES += hwaddr.cc hwaddr.h
 libkea_dhcp___la_SOURCES += iface_mgr.cc iface_mgr.h
 libkea_dhcp___la_SOURCES += iface_mgr_bsd.cc

+ 413 - 0
src/lib/dhcp/duid_factory.cc

@@ -0,0 +1,413 @@
+// 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 <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>
+
+using namespace isc::util;
+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 {
+namespace dhcp {
+
+DUIDFactory::DUIDFactory(const std::string& storage_location)
+    : storage_location_(trim(storage_location)), duid_() {
+}
+
+bool
+DUIDFactory::isStored() const {
+    return (!storage_location_.empty());
+}
+
+void
+DUIDFactory::createLLT(const uint16_t htype, const uint32_t time_in,
+                       const std::vector<uint8_t>& ll_identifier) {
+    // We'll need DUID stored in the file to compare it against the
+    // new configuration. If the new configuration indicates that some
+    // bits of the DUID should be generated we'll first try to use the
+    // values stored in the file to prevent DUID from changing if possible.
+    readFromFile();
+
+    uint16_t htype_current = 0;
+    uint32_t time_current = 0;
+    std::vector<uint8_t> identifier_current;
+
+    // If DUID exists in the file, try to use it as much as possible.
+    if (duid_) {
+        std::vector<uint8_t> duid_vec = duid_->getDuid();
+        if ((duid_->getType() == DUID::DUID_LLT) && (duid_vec.size() > 8)) {
+            htype_current = readUint16(&duid_vec[2], duid_vec.size() - 2);
+            time_current = readUint32(&duid_vec[4], duid_vec.size() - 4);
+            identifier_current.assign(duid_vec.begin() + 8, duid_vec.end());
+        }
+    }
+
+    uint32_t time_out = time_in;
+    // If time is unspecified (ANY), then use the time from current DUID or
+    // set it to current time.
+    if (time_out == 0) {
+        time_out = (time_current != 0 ? time_current :
+            static_cast<uint32_t>(time(NULL) - DUID_TIME_EPOCH));
+    }
+
+    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()) {
+        // If DUID doesn't exist yet, generate a new identifier.
+        if (identifier_current.empty()) {
+            createLinkLayerId(ll_identifier_out, htype_out);
+        } else {
+            // Use current identifier and hardware type.
+            ll_identifier_out = identifier_current;
+            htype_out = htype_current;
+        }
+
+    } else if (htype_out == 0) {
+        // If link layer type unspecified and link layer adddress
+        // is specified, use current type or HTYPE_ETHER.
+        htype_out = ((htype_current != 0) ? htype_current :
+                     static_cast<uint16_t>(HTYPE_ETHER));
+
+    }
+
+    // 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());
+
+    // Set new DUID and persist in a file.
+    set(duid_out);
+}
+
+void
+DUIDFactory::createEN(const uint32_t enterprise_id,
+                      const std::vector<uint8_t>& identifier) {
+    // We'll need DUID stored in the file to compare it against the
+    // new configuration. If the new configuration indicates that some
+    // bits of the DUID should be generated we'll first try to use the
+    // values stored in the file to prvent DUID from changing if possible.
+    readFromFile();
+
+    uint32_t enterprise_id_current = 0;
+    std::vector<uint8_t> identifier_current;
+
+    // If DUID exists in the file, try to use it as much as possible.
+    if (duid_) {
+        std::vector<uint8_t> duid_vec = duid_->getDuid();
+        if ((duid_->getType() == DUID::DUID_EN) && (duid_vec.size() > 6)) {
+            enterprise_id_current = readUint32(&duid_vec[2], duid_vec.size() - 2);
+            identifier_current.assign(duid_vec.begin() + 6, duid_vec.end());
+        }
+    }
+
+    // Enterprise id 0 means "unspecified". In this case, try to use existing
+    // DUID's enterprise id, or use ISC enterprise id.
+    uint32_t enterprise_id_out = enterprise_id;
+    if (enterprise_id_out == 0) {
+        if (enterprise_id_current != 0) {
+            enterprise_id_out = enterprise_id_current;
+        } else {
+            enterprise_id_out = 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 no identifier specified, we'll have to use the one from the
+    // DUID file or generate new.
+    if (identifier.empty()) {
+        // No DUID file, so generate new.
+        if (identifier_current.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 existing identifier.
+            duid_out.insert(duid_out.end(), identifier_current.begin(),
+                            identifier_current.end());
+        }
+
+    } 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) {
+    // We'll need DUID stored in the file to compare it against the
+    // new configuration. If the new configuration indicates that some
+    // bits of the DUID should be generated we'll first try to use the
+    // values stored in the file to prvent DUID from changing if possible.
+    readFromFile();
+
+    uint16_t htype_current = 0;
+    std::vector<uint8_t> identifier_current;
+
+    // If DUID exists in the file, try to use it as much as possible.
+    if (duid_) {
+        std::vector<uint8_t> duid_vec = duid_->getDuid();
+        if ((duid_->getType() == DUID::DUID_LL) && (duid_vec.size() > 4)) {
+            htype_current = readUint16(&duid_vec[2], duid_vec.size() - 2);
+            identifier_current.assign(duid_vec.begin() + 4, duid_vec.end());
+        }
+    }
+
+    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()) {
+        // If DUID doesn't exist yet, generate a new identifier.
+        if (identifier_current.empty()) {
+            createLinkLayerId(ll_identifier_out, htype_out);
+        } else {
+            // Use current identifier and hardware type.
+            ll_identifier_out = identifier_current;
+            htype_out = htype_current;
+        }
+
+    } else if (htype_out == 0) {
+        // If link layer type unspecified and link layer adddress
+        // is specified, use current type or HTYPE_ETHER.
+        htype_out = ((htype_current != 0) ? htype_current :
+            static_cast<uint16_t>(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());
+
+    // Set new DUID and persist in a file.
+    set(duid_out);
+}
+
+void
+DUIDFactory::createLinkLayerId(std::vector<uint8_t>& identifier,
+                               uint16_t& htype) const {
+    const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+
+    // Let's find suitable interface.
+    BOOST_FOREACH(IfacePtr iface, ifaces) {
+        // All the following checks could be merged into one multi-condition
+        // statement, but let's keep them separated as perhaps one day
+        // we will grow knobs to selectively turn them on or off. Also,
+        // this code is used only *once* during first start on a new machine
+        // and then server-id is stored. (or at least it will be once
+        // DUID storage is implemented)
+
+        // I wish there was a this_is_a_real_physical_interface flag...
+
+        // MAC address should be at least 6 bytes. Although there is no such
+        // requirement in any RFC, all decent physical interfaces (Ethernet,
+        // WiFi, InfiniBand, etc.) have at least 6 bytes long MAC address.
+        // We want to/ base our DUID on real hardware address, rather than
+        // virtual interface that pretends that underlying IP address is its
+        // MAC.
+        if (iface->getMacLen() < MIN_MAC_LEN) {
+            continue;
+        }
+
+        // Let's don't use loopback.
+        if (iface->flag_loopback_) {
+            continue;
+        }
+
+        // Let's skip downed interfaces. It is better to use working ones.
+        if (!iface->flag_up_) {
+            continue;
+        }
+
+        // Some interfaces (like lo on Linux) report 6-bytes long
+        // MAC address 00:00:00:00:00:00. Let's not use such weird interfaces
+        // to generate DUID.
+        if (isRangeZero(iface->getMac(), iface->getMac() + iface->getMacLen())) {
+            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");
+    }
+
+    // Store DUID in a file if file location specified.
+    if (isStored()) {
+        std::ofstream ofs;
+        try {
+            ofs.open(storage_location_.c_str(), 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_);
+    }
+
+    // Try to read DUID from file, if it exists.
+    readFromFile();
+    if (duid_) {
+        return (duid_);
+    }
+
+    // DUID doesn't exist, so we need to create it.
+    const std::vector<uint8_t> empty_vector;
+    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, empty_vector);
+
+    } 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, empty_vector);
+    }
+
+    return (duid_);
+}
+
+void
+DUIDFactory::readFromFile() {
+    duid_.reset();
+
+    std::ostringstream duid_str;
+   if (isStored()) {
+        std::ifstream ifs;
+        ifs.open(storage_location_.c_str(), 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() != std::streampos(0)) {
+            try {
+                duid_.reset(new DUID(DUID::fromText(duid_str.str())));
+
+            } catch (...) {
+                // The contents of this file don't represent a valid DUID.
+                // We'll need to generate it.
+            }
+        }
+   }
+}
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace

+ 193 - 0
src/lib/dhcp/duid_factory.h

@@ -0,0 +1,193 @@
+// 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 DUID_FACTORY_H
+#define DUID_FACTORY_H
+
+#include <dhcp/duid.h>
+#include <boost/noncopyable.hpp>
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @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 stored, 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 stored in the file.
+    ///
+    /// @return true if generated DUIDs are stored in a file, false
+    /// otherwise.
+    bool isStored() const;
+
+    /// @brief Generates DUID-LLT.
+    ///
+    /// This method generates DUID-LLT (Link Layer plus Time).
+    ///
+    /// @param htype Hardware type. If this is set to 0 and link layer
+    /// address is empty a value from existing DUID or 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 value from existing DUID or 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 try to use link layer
+    /// address from existing DUID. If there is no DUID yet, it 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);
+
+    /// @brief Generates DUID-EN.
+    ///
+    /// This method generates DUID-EN (DUID Enterprise).
+    ///
+    /// @param enterprise_id Enterprise id. If this value is 0, a value
+    /// from existing DUID is used or ISC's enterprise id if there is
+    /// no DUID yet.
+    /// @param identifier Data to be used as variable length identifier.
+    /// If this is an empty vector, an identifier from existing DUID is
+    /// used. If there is no DUID yet, 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.
+    ///
+    /// This method generates DUID-LL (Link Layer).
+    ///
+    /// @param htype Hardware type. If this is set to 0 and link layer
+    /// address is empty a value from existing DUID or 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 try to use link layer
+    /// address from existing DUID. If there is no DUID yet, it 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 storing the generated DUID-EN, exception
+    /// is thrown.
+    ///
+    /// @return Instance of the DUID read from file, or generated.
+    DuidPtr get();
+
+private:
+
+    /// @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 stored 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 Reads DUID from file, if file exists.
+    void readFromFile();
+
+    /// @brief Location of the file holding generated DUID (if specified).
+    std::string storage_location_;
+
+    /// @brief Pointer to generated DUID.
+    DuidPtr duid_;
+
+};
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif /* DUID_FACTORY_H */

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

@@ -47,6 +47,7 @@ TESTS += libdhcp++_unittests
 
 libdhcp___unittests_SOURCES  = run_unittests.cc
 libdhcp___unittests_SOURCES += classify_unittest.cc
+libdhcp___unittests_SOURCES += duid_factory_unittest.cc
 libdhcp___unittests_SOURCES += hwaddr_unittest.cc
 libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
 libdhcp___unittests_SOURCES += iface_mgr_test_config.cc iface_mgr_test_config.h

+ 549 - 0
src/lib/dhcp/tests/duid_factory_unittest.cc

@@ -0,0 +1,549 @@
+// 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 <dhcp/dhcp4.h>
+#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>
+#include <string>
+#include <vector>
+
+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_name) 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-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.
+    /// @param factory_ref Reference to DUID factory.
+    void testLLT(const std::string& expected_htype,
+                 const std::string& expected_time,
+                 const bool time_equal,
+                 const std::string& expected_hwaddr,
+                 DUIDFactory& factory_ref);
+
+    /// @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. If empty string specified the test method only checks
+    /// that generated identifier consists of some random values.
+    void testEN(const std::string& expected_enterprise_id,
+                const std::string& expected_identifier = "");
+
+    /// @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. If empty string specified the test method only checks
+    /// that generated identifier consists of some random values.
+    /// @param factory_ref Reference to DUID factory.
+    void testEN(const std::string& expected_enterprise_id,
+                const std::string& expected_identifier,
+                DUIDFactory& factory_ref);
+
+    /// @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 Tests creation of a DUID-LL.
+    ///
+    /// @param expected_htype Expected link layer type as string.
+    /// @param expected_hwaddr Expected link layer type as string.
+    /// @param factory_ref Reference to DUID factory.
+    void testLL(const std::string& expected_htype,
+                const std::string& expected_hwaddr,
+                DUIDFactory& factory_ref);
+
+    /// @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_;
+
+};
+
+DUIDFactoryTest::DUIDFactoryTest()
+    : iface_mgr_test_config_(true),
+      factory_(absolutePath(DEFAULT_DUID_FILE)) {
+    removeDefaultFile();
+}
+
+DUIDFactoryTest::~DUIDFactoryTest() {
+    removeDefaultFile();
+}
+
+std::string
+DUIDFactoryTest::absolutePath(const std::string& duid_file_name) const {
+    std::ostringstream s;
+    s << TEST_DATA_BUILDDIR << "/" << duid_file_name;
+    return (s.str());
+}
+
+void
+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).c_str(), 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;
+    try {
+        util::encode::decodeHex(hex, vec);
+    } catch (...) {
+        ADD_FAILURE() << "toVector: the following string " << hex
+            << " is not a valid hex string";
+    }
+
+    return (vec);
+}
+
+std::string
+DUIDFactoryTest::toString(const std::vector<uint8_t>& vec) const {
+    try {
+        return (util::encode::encodeHex(vec));
+    } catch (...) {
+        ADD_FAILURE() << "toString: unable to encode vector to"
+            " hexadecimal string";
+    }
+    return ("");
+}
+
+std::string
+DUIDFactoryTest::timeAsHexString() const {
+    time_t current_time = time(NULL) - DUID_TIME_EPOCH;
+    std::ostringstream s;
+    s << std::hex << std::setw(8) << std::setfill('0') << current_time;
+    return (boost::to_upper_copy<std::string>(s.str()));
+}
+
+void
+DUIDFactoryTest::testLLT(const std::string& expected_htype,
+                         const std::string& expected_time,
+                         const bool time_equal,
+                         const std::string& expected_hwaddr) {
+    testLLT(expected_htype, expected_time, time_equal, expected_hwaddr,
+            factory());
+}
+
+void
+DUIDFactoryTest::testLLT(const std::string& expected_htype,
+                         const std::string& expected_time,
+                         const bool time_equal,
+                         const std::string& expected_hwaddr,
+                         DUIDFactory& factory_ref) {
+    DuidPtr duid = factory_ref.get();
+    ASSERT_TRUE(duid);
+    ASSERT_GE(duid->getDuid().size(), 14);
+    std::string duid_text = toString(duid->getDuid());
+
+    // DUID type LLT
+    EXPECT_EQ("0001", duid_text.substr(0, 4));
+    // Link layer type HTYPE_ETHER
+    EXPECT_EQ(expected_htype, duid_text.substr(4, 4));
+
+    // Verify if time is correct.
+    if (time_equal) {
+        // Strict time check.
+        EXPECT_EQ(expected_time, duid_text.substr(8, 8));
+    } else {
+        // Timestamp equal or less current time.
+        EXPECT_LE(duid_text.substr(8, 8), expected_time);
+    }
+
+    // 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) {
+    testEN(expected_enterprise_id, expected_identifier, factory());
+}
+
+void
+DUIDFactoryTest::testEN(const std::string& expected_enterprise_id,
+                        const std::string& expected_identifier,
+                        DUIDFactory& factory_ref) {
+    DuidPtr duid = factory_ref.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 expected 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) {
+    testLL(expected_htype, expected_hwaddr, factory());
+}
+
+void
+DUIDFactoryTest::testLL(const std::string& expected_htype,
+                        const std::string& expected_hwaddr,
+                        DUIDFactory& factory_ref) {
+    DuidPtr duid = factory_ref.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.
+    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());
+}
+
+
+// This test verifies that the factory class will generate the entire
+// DUID-LLT if there are no explicit values specified for the
+// time, link layer type and link layer address.
+TEST_F(DUIDFactoryTest, createLLT) {
+    // Use 0 values for time and link layer type and empty vector for
+    // the link layer address. The createLLT function will need to
+    // use current time, HTYPE_ETHER and MAC address of one of the
+    // interfaces.
+    ASSERT_NO_THROW(factory().createLLT(0, 0, std::vector<uint8_t>()));
+    testLLT("0001", timeAsHexString(), false, "080808080808");
+}
+
+// This test verifies that the factory class creates a DUID-LLT from
+// the explicitly specified time, when link layer type and address are
+// generated.
+TEST_F(DUIDFactoryTest, createLLTExplicitTime) {
+    ASSERT_NO_THROW(factory().createLLT(0, 0xABCDEF, std::vector<uint8_t>()));
+    testLLT("0001", "00ABCDEF", true, "080808080808");
+}
+
+// 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("0001", timeAsHexString(), false, "080808080808");
+}
+
+// This test verifies that the factory class creates DUID-LLT from
+// explcitly specified link layer address, when other parameters
+// are generated.
+TEST_F(DUIDFactoryTest, createLLTExplicitLinkLayerAddress) {
+    ASSERT_NO_THROW(factory().createLLT(0, 0, toVector("121212121212")));
+    testLLT("0001", timeAsHexString(), false, "121212121212");
+}
+
+// This test verifies that the factory function creates DUID-LLT from
+// all values explicitly specified.
+TEST_F(DUIDFactoryTest, createLLTAllExplcitParameters) {
+    ASSERT_NO_THROW(factory().createLLT(HTYPE_FDDI, 0xFAFAFAFA,
+                                        toVector("24242424242424242424")));
+    testLLT("0008", "FAFAFAFA", true, "24242424242424242424");
+}
+
+// This test verifies that the createLLT function will try to reuse existing
+// DUID for the non-explicitly specified values.
+TEST_F(DUIDFactoryTest, createLLTReuse) {
+    // Create DUID-LLT and store it in a file.
+    ASSERT_NO_THROW(factory().createLLT(HTYPE_FDDI, 0xFAFAFAFA,
+                                        toVector("242424242424")));
+    // Create another factory class, which uses the same file.
+    DUIDFactory factory2(absolutePath(DEFAULT_DUID_FILE));
+    // Create DUID-LLT without specifying hardware type, time and
+    // link layer address. The factory function should use the
+    // values in the existing DUID.
+    ASSERT_NO_THROW(factory2.createLLT(0, 0, std::vector<uint8_t>()));
+    testLLT("0008", "FAFAFAFA", true, "242424242424", factory2);
+
+    // Try to reuse only a time value.
+    DUIDFactory factory3(absolutePath(DEFAULT_DUID_FILE));
+    ASSERT_NO_THROW(factory3.createLLT(HTYPE_ETHER, 0,
+                                       toVector("121212121212")));
+    testLLT("0001", "FAFAFAFA", true, "121212121212", factory3);
+
+    // Reuse only a hardware type.
+    DUIDFactory factory4(absolutePath(DEFAULT_DUID_FILE));
+    ASSERT_NO_THROW(factory4.createLLT(0, 0x23432343,
+                                       toVector("455445544554")));
+    testLLT("0001", "23432343", true, "455445544554", factory4);
+
+    // Reuse link layer address. Note that in this case the hardware
+    // type is set to the type of the interface from which hardware
+    // address is obtained and the explicit value is ignored.
+    DUIDFactory factory5(absolutePath(DEFAULT_DUID_FILE));
+    ASSERT_NO_THROW(factory5.createLLT(HTYPE_FDDI, 0x11111111,
+                                       std::vector<uint8_t>()));
+    testLLT("0001", "11111111", true, "455445544554", factory5);
+}
+
+// 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 createEN function will try to reuse existing
+// DUID for the non-explicitly specified values.
+TEST_F(DUIDFactoryTest, createENReuse) {
+    // Create DUID-EN and store it in a file.
+    ASSERT_NO_THROW(factory().createEN(0xFAFAFAFA, toVector("242424242424")));
+    // Create another factory class, which uses the same file.
+    DUIDFactory factory2(absolutePath(DEFAULT_DUID_FILE));
+    ASSERT_NO_THROW(factory2.createEN(0, std::vector<uint8_t>()));
+    testEN("FAFAFAFA", "242424242424", factory2);
+
+    // Reuse only enterprise id.
+    DUIDFactory factory3(absolutePath(DEFAULT_DUID_FILE));
+    ASSERT_NO_THROW(factory3.createEN(0, toVector("121212121212")));
+    testEN("FAFAFAFA", "121212121212", factory3);
+
+    // Reuse only variable length identifier.
+    DUIDFactory factory4(absolutePath(DEFAULT_DUID_FILE));
+    ASSERT_NO_THROW(factory4.createEN(0x1234, std::vector<uint8_t>()));
+    testEN("00001234", "121212121212", factory4);
+}
+
+// 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", "080808080808");
+}
+
+// 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", "080808080808");
+}
+
+// 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 the createLL function will try to reuse existing
+// DUID for the non-explicitly specified values.
+TEST_F(DUIDFactoryTest, createLLReuse) {
+    // Create DUID-EN and store it in a file.
+    ASSERT_NO_THROW(factory().createLL(HTYPE_FDDI, toVector("242424242424")));
+    // Create another factory class, which uses the same file.
+    DUIDFactory factory2(absolutePath(DEFAULT_DUID_FILE));
+    // Create DUID-LL without specifying hardware type, time and
+    // link layer address. The factory function should use the
+    // values in the existing DUID.
+    ASSERT_NO_THROW(factory2.createLL(0, std::vector<uint8_t>()));
+    testLL("0008", "242424242424", factory2);
+
+    // Reuse only hardware type
+    DUIDFactory factory3(absolutePath(DEFAULT_DUID_FILE));
+    ASSERT_NO_THROW(factory3.createLL(0, toVector("121212121212")));
+    testLL("0008", "121212121212", factory3);
+
+    // Reuse link layer address. Note that when the link layer address is
+    // reused, the explicit value of hardware type is reused too and the
+    // explicit value of hardware type is ignored.
+    DUIDFactory factory4(absolutePath(DEFAULT_DUID_FILE));
+    ASSERT_NO_THROW(factory4.createLL(HTYPE_ETHER, std::vector<uint8_t>()));
+    testLL("0008", "121212121212", factory4);
+}
+
+// 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, "080808080808");
+
+    ASSERT_NO_THROW(factory().createEN(0, toVector("12131415")));
+    testEN("000009BF", "12131415");
+}
+
+} // End anonymous namespace

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

@@ -95,6 +95,12 @@ IfaceMgrTestConfig::createIface(const std::string &name, const int ifindex) {
     iface->flag_broadcast_ = false;
     iface->flag_up_ = true;
     iface->flag_running_ = true;
+
+    // Set MAC address to 08:08:08:08:08:08.
+    std::vector<uint8_t> mac_vec(6, 8);
+    iface->setMac(&mac_vec[0], mac_vec.size());
+    iface->setHWType(HTYPE_ETHER);
+
     return (iface);
 }
 

+ 3 - 0
src/lib/dhcpsrv/Makefile.am

@@ -85,6 +85,7 @@ libkea_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
 libkea_dhcpsrv_la_SOURCES += alloc_engine_log.cc alloc_engine_log.h
 libkea_dhcpsrv_la_SOURCES += base_host_data_source.h
 libkea_dhcpsrv_la_SOURCES += callout_handle_store.h
+libkea_dhcpsrv_la_SOURCES += cfg_duid.cc cfg_duid.h
 libkea_dhcpsrv_la_SOURCES += cfg_hosts.cc cfg_hosts.h
 libkea_dhcpsrv_la_SOURCES += cfg_iface.cc cfg_iface.h
 libkea_dhcpsrv_la_SOURCES += cfg_expiration.cc cfg_expiration.h
@@ -147,6 +148,8 @@ libkea_dhcpsrv_la_SOURCES += parsers/dbaccess_parser.cc
 libkea_dhcpsrv_la_SOURCES += parsers/dbaccess_parser.h
 libkea_dhcpsrv_la_SOURCES += parsers/dhcp_parsers.cc
 libkea_dhcpsrv_la_SOURCES += parsers/dhcp_parsers.h
+libkea_dhcpsrv_la_SOURCES += parsers/duid_config_parser.cc
+libkea_dhcpsrv_la_SOURCES += parsers/duid_config_parser.h
 libkea_dhcpsrv_la_SOURCES += parsers/expiration_config_parser.cc
 libkea_dhcpsrv_la_SOURCES += parsers/expiration_config_parser.h
 libkea_dhcpsrv_la_SOURCES += parsers/host_reservation_parser.cc

+ 84 - 0
src/lib/dhcpsrv/cfg_duid.cc

@@ -0,0 +1,84 @@
+// 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 <dhcp/duid_factory.h>
+#include <dhcpsrv/cfg_duid.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+#include <iostream>
+
+using namespace isc;
+using namespace isc::util::encode;
+using namespace isc::util::str;
+
+namespace isc {
+namespace dhcp {
+
+CfgDUID::CfgDUID()
+    : type_(DUID::DUID_LLT), identifier_(), htype_(0), time_(0),
+      enterprise_id_(0) {
+}
+
+void
+CfgDUID::setIdentifier(const std::string& identifier_as_hex) {
+    // Remove whitespaces.
+    const std::string identifier = trim(identifier_as_hex);
+    // Temporary result of parsing.
+    std::vector<uint8_t> binary;
+    if (!identifier.empty()) {
+        try {
+            // Decode identifier specified as a string of hexadecimal digits.
+            decodeHex(identifier, binary);
+            // All went ok, so let's replace identifier with a new value.
+            identifier_.swap(binary);
+        } catch (const std::exception& ex) {
+            isc_throw(BadValue, "identifier specified in the DUID"
+                      " configuration '" << identifier
+                      << "' is not a valid string of hexadecimal digits");
+        }
+
+    } else {
+         // If specified identifier is empty, clear our internal identifier.
+        identifier_.clear();
+    }
+}
+
+DuidPtr
+CfgDUID::create(const std::string& duid_file_path) const {
+    // Use DUID factory to create a DUID instance.
+    DUIDFactory factory(duid_file_path);
+
+    switch (getType()) {
+    case DUID::DUID_LLT:
+        factory.createLLT(getHType(), getTime(), getIdentifier());
+        break;
+    case DUID::DUID_EN:
+        factory.createEN(getEnterpriseId(), getIdentifier());
+        break;
+    case DUID::DUID_LL:
+        factory.createLL(getHType(), getIdentifier());
+        break;
+    default:
+        // This should actually never happen.
+        isc_throw(Unexpected, "invalid DUID type used " << getType()
+                  << " to create a new DUID");
+    }
+
+    // Return generated DUID.
+    return (factory.get());
+}
+
+
+}
+}

+ 141 - 0
src/lib/dhcpsrv/cfg_duid.h

@@ -0,0 +1,141 @@
+// 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 CFG_DUID_H
+#define CFG_DUID_H
+
+#include <dhcp/duid.h>
+#include <boost/shared_ptr.hpp>
+#include <stdint.h>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Holds manual configuration of the server identifier (DUID).
+///
+/// The DHCPv6 server uses DHCPv6 Unique Identifier (DUID) to identify itself
+/// to the clients. Typically, the server generates the DUID on the first
+/// startup and writes it to the persistent storage so as it doesn't change
+/// across restarts of the server. RFC3315 and RFC6355 define different DUID
+/// types. Kea allows for selecting a type of DUID that the server should
+/// generate. It also allows for overriding entire default DUID or parts of
+/// it via configuration file. This class holds the DUID configuration
+/// specified in the server configuration file.
+class CfgDUID {
+public:
+
+    /// @brief Constructor.
+    CfgDUID();
+
+    /// @brief Returns DUID type.
+    DUID::DUIDType getType() const {
+        return (type_);
+    }
+
+    /// @brief Sets DUID type.
+    void setType(const DUID::DUIDType& type) {
+        type_ = type;
+    }
+
+    /// @brief Returns identifier.
+    ///
+    /// Identifier is a link layer address for the DUID-LLT and DUID-LL. It
+    /// is also a variable length identifier in DUID-EN. It may be used for
+    /// all other existing and future DUID types when there is a need to
+    /// represent some variable length identifier.
+    ///
+    /// @return Vector holding an identifier belonging to a particular
+    /// DUID type.
+    std::vector<uint8_t> getIdentifier() const {
+        return (identifier_);
+    }
+
+    /// @brief Sets new identifier as hex string.
+    ///
+    /// @param identifier_as_hex String of hexadecimal digits representing
+    /// variable length identifier within a DUID.
+    void setIdentifier(const std::string& identifier_as_hex);
+
+    /// @brief Returns hardware type for DUID-LLT and DUID-LL.
+    uint16_t getHType() const {
+        return (htype_);
+    }
+
+    /// @brief Sets new hardware type for DUID-LLT and DUID-LL.
+    void setHType(const uint16_t htype) {
+        htype_ = htype;
+    }
+
+    /// @brief Returns time for the DUID-LLT.
+    uint32_t getTime() const {
+        return (time_);
+    }
+
+    /// @brief Sets new time for DUID-LLT.
+    void setTime(const uint32_t new_time) {
+        time_ = new_time;
+    }
+
+    /// @brief Returns enterprise id for the DUID-EN.
+    uint32_t getEnterpriseId() const {
+        return (enterprise_id_);
+    }
+
+    /// @brief Sets new enterprise id.
+    ///
+    /// @param enterprise_id New enterprise id.
+    void setEnterpriseId(const uint32_t enterprise_id) {
+        enterprise_id_ = enterprise_id;
+    }
+
+    /// @brief Creates instance of a DUID from the current configuration.
+    ///
+    /// @param duid_file_path Absolute path to a DUID file.
+    /// @return Pointer to an instance of new DUID.
+    DuidPtr create(const std::string& duid_file_path) const;
+
+private:
+
+    /// @brief DUID type.
+    DUID::DUIDType type_;
+
+    /// @brief Variable length identifier in a DUID.
+    std::vector<uint8_t> identifier_;
+
+    /// @brief Hardware type.
+    uint16_t htype_;
+
+    /// @brief Time used for DUID-LLT.
+    uint32_t time_;
+
+    /// @brief Enterprise id used for DUID-EN.
+    uint32_t enterprise_id_;
+
+};
+
+/// @name Pointers to the @c CfgDUID objects.
+//@{
+/// @brief Pointer to the Non-const object.
+typedef boost::shared_ptr<CfgDUID> CfgDUIDPtr;
+
+/// @brief Pointer to the const object.
+typedef boost::shared_ptr<const CfgDUID> ConstCfgDUIDPtr;
+
+//@}
+
+}
+}
+
+#endif // CFG_DUID_H

+ 13 - 0
src/lib/dhcpsrv/dhcpsrv_messages.mes

@@ -39,6 +39,19 @@ of active interfaces. This doesn't prevent the server from listening to
 the DHCP traffic through open sockets, but will rather be used by Interface
 Manager to select active interfaces when sockets are re-opened.
 
+% DHCPSRV_CFGMGR_CONFIGURE_SERVERID server configuration includes specification of a server identifier
+This warning message is issued when the server specified configuration of
+a server identifier. If this new configuration overrides an existing
+server identifier, this will affect existing bindings of the clients.
+Clients will use old server identifier when they renew their bindings.
+The server will not respond to those renews, and the clients will
+eventually transition to rebinding state. The server should reassign
+existing bindings and the clients will subsequently use new server
+identifier. It is recommended to not modify the server identifier, unless
+there is a good reason for it, to avoid increased number of renewals and
+a need for rebinding (increase of multicast traffic, which may be received
+by multiple servers).
+
 % DHCPSRV_CFGMGR_NO_SUBNET4 no suitable subnet is defined for address hint %1
 This debug message is output when the DHCP configuration manager has received
 a request for an IPv4 subnet for the specified address, but no such

+ 136 - 0
src/lib/dhcpsrv/parsers/duid_config_parser.cc

@@ -0,0 +1,136 @@
+// 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 <cc/data.h>
+#include <dhcp/duid.h>
+#include <dhcpsrv/cfg_duid.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/parsers/duid_config_parser.h>
+#include <exceptions/exceptions.h>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <limits>
+#include <string>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+DUIDConfigParser::DUIDConfigParser()
+    : DhcpConfigParser() {
+}
+
+void
+DUIDConfigParser::build(isc::data::ConstElementPtr duid_configuration) {
+    bool type_present = false;
+    BOOST_FOREACH(ConfigPair element, duid_configuration->mapValue()) {
+        try {
+            if (element.first == "type") {
+                type_present = true;
+                setType(element.second->stringValue());
+            } else if (element.first == "identifier") {
+                setIdentifier(element.second->stringValue());
+            } else if (element.first == "htype") {
+                setHType(element.second->intValue());
+            } else if (element.first == "time") {
+                setTime(element.second->intValue());
+            } else if (element.first == "enterprise-id") {
+                setEnterpriseId(element.second->intValue());
+            } else {
+                isc_throw(DhcpConfigError, "unsupported configuration "
+                          "parameter '" << element.first << "'");
+            }
+        } catch (const std::exception& ex) {
+            // Append position.
+            isc_throw(DhcpConfigError, ex.what() << " ("
+                      << element.second->getPosition() << ")");
+        }
+    }
+
+    // "type" is mandatory
+    if (!type_present) {
+        isc_throw(DhcpConfigError, "mandatory parameter \"type\" not specified"
+                  " for the DUID configuration ("
+                  << duid_configuration->getPosition() << ")");
+    }
+
+    LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_CONFIGURE_SERVERID);
+}
+
+void
+DUIDConfigParser::setType(const std::string& duid_type) const {
+    // Map DUID type represented as text into numeric value.
+    DUID::DUIDType numeric_type = DUID::DUID_UNKNOWN;
+    if (duid_type == "LLT") {
+        numeric_type = DUID::DUID_LLT;
+    } else if (duid_type == "EN") {
+        numeric_type = DUID::DUID_EN;
+    } else if (duid_type == "LL") {
+        numeric_type = DUID::DUID_LL;
+    } else {
+        isc_throw(DhcpConfigError, "unsupported DUID type '"
+                  << duid_type << "'. Expected: LLT, EN or LL");
+    }
+
+    const CfgDUIDPtr& cfg = CfgMgr::instance().getStagingCfg()->getCfgDUID();
+    cfg->setType(static_cast<DUID::DUIDType>(numeric_type));
+}
+
+void
+DUIDConfigParser::setIdentifier(const std::string& identifier) const {
+    const CfgDUIDPtr& cfg = CfgMgr::instance().getStagingCfg()->getCfgDUID();
+    cfg->setIdentifier(identifier);
+}
+
+void
+DUIDConfigParser::setHType(const int64_t htype) const {
+    const CfgDUIDPtr& cfg = CfgMgr::instance().getStagingCfg()->getCfgDUID();
+    checkRange<uint16_t>("htype", htype);
+    cfg->setHType(static_cast<uint16_t>(htype));
+
+}
+
+void
+DUIDConfigParser::setTime(const int64_t new_time) const {
+    const CfgDUIDPtr& cfg = CfgMgr::instance().getStagingCfg()->getCfgDUID();
+    checkRange<uint32_t>("time", new_time);
+    cfg->setTime(static_cast<uint32_t>(new_time));
+}
+
+void
+DUIDConfigParser::setEnterpriseId(const int64_t enterprise_id) const {
+    const CfgDUIDPtr& cfg = CfgMgr::instance().getStagingCfg()->getCfgDUID();
+    checkRange<uint32_t>("enterprise-id", enterprise_id);
+    cfg->setEnterpriseId(static_cast<uint32_t>(enterprise_id));
+}
+
+template<typename NumericType>
+void
+DUIDConfigParser::checkRange(const std::string& parameter_name,
+                             const int64_t parameter_value) const {
+    if ((parameter_value < 0) ||
+        (parameter_value > std::numeric_limits<NumericType>::max())) {
+        isc_throw(DhcpConfigError, "out of range value '" << parameter_value
+                  << "' specified for parameter '" << parameter_name
+                  << "'; expected value in range of [0.."
+                  << std::numeric_limits<NumericType>::max() << "]");
+    }
+}
+
+
+} // end of namespace isc::dhcp
+} // end of namespace isc

+ 94 - 0
src/lib/dhcpsrv/parsers/duid_config_parser.h

@@ -0,0 +1,94 @@
+// 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 DUID_CONFIG_PARSER_H
+#define DUID_CONFIG_PARSER_H
+
+#include <cc/data.h>
+#include <dhcpsrv/parsers/dhcp_config_parser.h>
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Parser for server DUID configuration.
+///
+/// This parser currently supports the following DUID types:
+/// - DUID-LLT,
+/// - DUID-EN
+/// - DUID-LL
+///
+/// @todo Add support for DUID-UUID in the parser.
+class DUIDConfigParser : public DhcpConfigParser {
+public:
+
+    /// @brief Constructor.
+    DUIDConfigParser();
+
+    /// @brief Parses DUID configuration.
+    ///
+    /// @param duid_configuration Data element holding a map representing
+    /// DUID configuration.
+    ///
+    /// @throw DhcpConfigError If the configuration is invalid.
+    virtual void build(isc::data::ConstElementPtr duid_configuration);
+
+    /// @brief Commit, unused.
+    virtual void commit() { }
+
+private:
+
+    /// @brief Validate and set DUID type.
+    ///
+    /// @param duid_type DUID type in textfual format.
+    void setType(const std::string& duid_type) const;
+
+    /// @brief Validate and set identifier.
+    ///
+    /// @param identifier Identifier.
+    void setIdentifier(const std::string& identifier) const;
+
+    /// @brief Validate and set hardware type.
+    ///
+    /// @param htype Hardware type.
+    void setHType(const int64_t htype) const;
+
+    /// @brief Validate and set time value.
+    ///
+    /// @param new_time Time value to be used for DUID.
+    void setTime(const int64_t new_time) const;
+
+    /// @brief Validate and set enterprise id.
+    ///
+    /// @param enterprise_id Enterprise id.
+    void setEnterpriseId(const int64_t enterprise_id) const;
+
+    /// @brief Verifies if the specified parameter is in range.
+    ///
+    /// Each numeric value must be in range of [0 .. max_value], where
+    /// max_value is a maximum value for the numeric type used for this
+    /// parameter.
+    ///
+    /// @param parameter_name Parameter name.
+    /// @tparam Numeric type of the specified parameter.
+    template<typename NumericType>
+    void checkRange(const std::string& parameter_name,
+                    const int64_t parameter_value) const;
+};
+
+}
+} // end of namespace isc
+
+#endif // DUID_CONFIG_PARSER_H

+ 2 - 2
src/lib/dhcpsrv/srv_config.cc

@@ -31,7 +31,7 @@ SrvConfig::SrvConfig()
       cfg_option_def_(new CfgOptionDef()), cfg_option_(new CfgOption()),
       cfg_subnets4_(new CfgSubnets4()), cfg_subnets6_(new CfgSubnets6()),
       cfg_hosts_(new CfgHosts()), cfg_rsoo_(new CfgRSOO()),
-      cfg_expiration_(new CfgExpiration()),
+      cfg_expiration_(new CfgExpiration()), cfg_duid_(new CfgDUID()),
       class_dictionary_(new ClientClassDictionary()),
       decline_timer_(0) {
 }
@@ -41,7 +41,7 @@ SrvConfig::SrvConfig(const uint32_t sequence)
       cfg_option_def_(new CfgOptionDef()), cfg_option_(new CfgOption()),
       cfg_subnets4_(new CfgSubnets4()), cfg_subnets6_(new CfgSubnets6()),
       cfg_hosts_(new CfgHosts()), cfg_rsoo_(new CfgRSOO()),
-      cfg_expiration_(new CfgExpiration()),
+      cfg_expiration_(new CfgExpiration()), cfg_duid_(new CfgDUID()),
       class_dictionary_(new ClientClassDictionary()),
       decline_timer_(0) {
 }

+ 16 - 0
src/lib/dhcpsrv/srv_config.h

@@ -15,6 +15,7 @@
 #ifndef DHCPSRV_CONFIG_H
 #define DHCPSRV_CONFIG_H
 
+#include <dhcpsrv/cfg_duid.h>
 #include <dhcpsrv/cfg_expiration.h>
 #include <dhcpsrv/cfg_hosts.h>
 #include <dhcpsrv/cfg_iface.h>
@@ -273,6 +274,18 @@ public:
         return (cfg_expiration_);
     }
 
+    /// @brief Returns pointer to the object holding configuration of the
+    /// server identifier.
+    CfgDUIDPtr getCfgDUID() {
+        return (cfg_duid_);
+    }
+
+    /// @brief Returns const pointer to the object holding configuration
+    /// of the server identifier.
+    ConstCfgDUIDPtr getCfgDUID() const {
+        return (cfg_duid_);
+    }
+
     //@}
 
     /// @brief Returns non-const reference to an array that stores
@@ -477,6 +490,9 @@ private:
     /// expired leases.
     CfgExpirationPtr cfg_expiration_;
 
+    /// @brief Pointer to the configuration of the server identifier.
+    CfgDUIDPtr cfg_duid_;
+
     /// @brief Pointer to the control-socket information
     isc::data::ConstElementPtr control_socket_;
 

+ 2 - 0
src/lib/dhcpsrv/tests/Makefile.am

@@ -65,6 +65,7 @@ libdhcpsrv_unittests_SOURCES += alloc_engine_hooks_unittest.cc
 libdhcpsrv_unittests_SOURCES += alloc_engine4_unittest.cc
 libdhcpsrv_unittests_SOURCES += alloc_engine6_unittest.cc
 libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_duid_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_expiration_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_hosts_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_iface_unittest.cc
@@ -84,6 +85,7 @@ libdhcpsrv_unittests_SOURCES += d2_udp_unittest.cc
 libdhcpsrv_unittests_SOURCES += daemon_unittest.cc
 libdhcpsrv_unittests_SOURCES += database_connection_unittest.cc
 libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += duid_config_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += expiration_config_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += host_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += host_unittest.cc

+ 206 - 0
src/lib/dhcpsrv/tests/cfg_duid_unittest.cc

@@ -0,0 +1,206 @@
+// 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 <dhcp/dhcp4.h>
+#include <dhcp/duid.h>
+#include <dhcpsrv/cfg_duid.h>
+#include <exceptions/exceptions.h>
+#include <util/encode/hex.h>
+#include <gtest/gtest.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sstream>
+#include <string>
+#include <vector>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Specifies file name holding DUID.
+const std::string DUID_FILE_NAME = "test.duid";
+
+/// @brief Test fixture class for @c CfgDUID.
+class CfgDUIDTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Removes DUID file if present.
+    CfgDUIDTest() {
+        static_cast<void>(remove(absolutePath(DUID_FILE_NAME).c_str()));
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Removes DUID file if present.
+    virtual ~CfgDUIDTest() {
+        static_cast<void>(remove(absolutePath(DUID_FILE_NAME).c_str()));
+    }
+
+    /// @brief Returns absolute path to a file used by tests.
+    ///
+    /// @param filename File name.
+    std::string absolutePath(const std::string& filename) 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;
+};
+
+std::string
+CfgDUIDTest::absolutePath(const std::string& filename) const {
+    std::ostringstream s;
+    s << DHCP_DATA_DIR << "/" << filename;
+    return (s.str());
+}
+
+/// @brief Converts vector to string of hexadecimal digits.
+///
+/// @param vec Input vector.
+/// @return String of hexadecimal digits converted from vector.
+std::string
+CfgDUIDTest::toString(const std::vector<uint8_t>& vec) const {
+    try {
+        return (util::encode::encodeHex(vec));
+    } catch (...) {
+        ADD_FAILURE() << "toString: unable to encode vector to"
+            " hexadecimal string";
+    }
+    return ("");
+}
+
+
+// This test verifies default values of the DUID configuration.
+TEST_F(CfgDUIDTest, defaults) {
+    CfgDUID cfg_duid;
+    EXPECT_EQ(DUID::DUID_LLT, cfg_duid.getType());
+
+    EXPECT_TRUE(cfg_duid.getIdentifier().empty())
+        << "expected empty identifier, found: "
+        << toString(cfg_duid.getIdentifier());
+
+    EXPECT_EQ(0, cfg_duid.getHType());
+    EXPECT_EQ(0, cfg_duid.getTime());
+    EXPECT_EQ(0, cfg_duid.getEnterpriseId());
+}
+
+// This test verifies that it is possible to set values for the CfgDUID.
+TEST_F(CfgDUIDTest, setValues) {
+    CfgDUID cfg_duid;
+    // Set values.
+    ASSERT_NO_THROW(cfg_duid.setType(DUID::DUID_EN));
+    ASSERT_NO_THROW(cfg_duid.setIdentifier("ABCDEF"));
+    ASSERT_NO_THROW(cfg_duid.setHType(100));
+    ASSERT_NO_THROW(cfg_duid.setTime(32100));
+    ASSERT_NO_THROW(cfg_duid.setEnterpriseId(10));
+
+    // Check that values have been set correctly.
+    EXPECT_EQ(DUID::DUID_EN, cfg_duid.getType());
+    EXPECT_EQ("ABCDEF", toString(cfg_duid.getIdentifier()));
+    EXPECT_EQ(100, cfg_duid.getHType());
+    EXPECT_EQ(32100, cfg_duid.getTime());
+    EXPECT_EQ(10, cfg_duid.getEnterpriseId());
+}
+
+// This test checks positive scenarios for setIdentifier.
+TEST_F(CfgDUIDTest, setIdentifier) {
+    CfgDUID cfg_duid;
+    // Check that hexadecimal characters may be lower case.
+    ASSERT_NO_THROW(cfg_duid.setIdentifier("a1b2c3"));
+    EXPECT_EQ("A1B2C3", toString(cfg_duid.getIdentifier()));
+
+    // Check that whitespaces are allowed.
+    ASSERT_NO_THROW(cfg_duid.setIdentifier("  ABC  DEF    "));
+    EXPECT_EQ("ABCDEF", toString(cfg_duid.getIdentifier()));
+
+    // Check that identifier including only whitespaces is ignored.
+    ASSERT_NO_THROW(cfg_duid.setIdentifier("      "));
+    EXPECT_TRUE(cfg_duid.getIdentifier().empty())
+        << "expected empty identifier, found: "
+        << toString(cfg_duid.getIdentifier());
+}
+
+// This test verifies that the invalid identifier is rejected and
+// exception is thrown.
+TEST_F(CfgDUIDTest, setInvalidIdentifier) {
+    CfgDUID cfg_duid;
+    // Check that hexadecimal characters may be lower case.
+    ASSERT_NO_THROW(cfg_duid.setIdentifier("a1b2c3"));
+    EXPECT_EQ("A1B2C3", toString(cfg_duid.getIdentifier()));
+
+    // Try to set invalid value. This should not modify original
+    // value.
+    ASSERT_THROW(cfg_duid.setIdentifier("hola!"), isc::BadValue);
+    EXPECT_EQ("A1B2C3", toString(cfg_duid.getIdentifier()));
+}
+
+// This method checks that the DUID-LLT can be created from the
+// specified configuration.
+TEST_F(CfgDUIDTest, createLLT) {
+    CfgDUID cfg;
+    ASSERT_NO_THROW(cfg.setType(DUID::DUID_LLT));
+    ASSERT_NO_THROW(cfg.setTime(0x1123));
+    ASSERT_NO_THROW(cfg.setHType(8));
+    ASSERT_NO_THROW(cfg.setIdentifier("12564325A63F"));
+
+    // Generate DUID from this configuration.
+    DuidPtr duid;
+    ASSERT_NO_THROW(duid = cfg.create(absolutePath(DUID_FILE_NAME)));
+    ASSERT_TRUE(duid);
+
+    // Verify if the DUID is correct.
+    EXPECT_EQ("00:01:00:08:00:00:11:23:12:56:43:25:a6:3f",
+              duid->toText());
+}
+
+// This method checks that the DUID-EN can be created from the
+// specified configuration.
+TEST_F(CfgDUIDTest, createEN) {
+    CfgDUID cfg;
+    ASSERT_NO_THROW(cfg.setType(DUID::DUID_EN));
+    ASSERT_NO_THROW(cfg.setIdentifier("250F3E26A762"));
+    ASSERT_NO_THROW(cfg.setEnterpriseId(0x1010));
+
+    // Generate DUID from this configuration.
+    DuidPtr duid;
+    ASSERT_NO_THROW(duid = cfg.create(absolutePath(DUID_FILE_NAME)));
+    ASSERT_TRUE(duid);
+
+    // Verify if the DUID is correct.
+    EXPECT_EQ("00:02:00:00:10:10:25:0f:3e:26:a7:62", duid->toText());
+}
+
+// This method checks that the DUID-LL can be created from the
+// specified configuration.
+TEST_F(CfgDUIDTest, createLL) {
+    CfgDUID cfg;
+    ASSERT_NO_THROW(cfg.setType(DUID::DUID_LL));
+    ASSERT_NO_THROW(cfg.setIdentifier("124134A4B367"));
+    ASSERT_NO_THROW(cfg.setHType(2));
+
+    // Generate DUID from this configuration.
+    DuidPtr duid;
+    ASSERT_NO_THROW(duid = cfg.create(absolutePath(DUID_FILE_NAME)));
+    ASSERT_TRUE(duid);
+
+    // Verify if the DUID is correct.
+    EXPECT_EQ("00:03:00:02:12:41:34:a4:b3:67", duid->toText());
+}
+
+} // end of anonymous namespace

+ 214 - 0
src/lib/dhcpsrv/tests/duid_config_parser_unittest.cc

@@ -0,0 +1,214 @@
+// 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 <cc/data.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_duid.h>
+#include <dhcpsrv/parsers/duid_config_parser.h>
+#include <dhcpsrv/testutils/config_result_check.h>
+#include <util/encode/hex.h>
+#include <gtest/gtest.h>
+#include <limits>
+#include <sstream>
+#include <string>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test fixture class for @c DUIDConfigParser
+class DUIDConfigParserTest : public ::testing::Test {
+public:
+
+    /// @brief Creates simple configuration with DUID type only.
+    ///
+    /// @param duid_type DUID type in the textual format.
+    std::string createConfigWithType(const std::string& duid_type) const;
+
+    /// @brief Creates simple configuration with DUID type and one
+    /// numeric parameter.
+    ///
+    /// @param name Parameter name.
+    /// @param value Parameter value.
+    std::string createConfigWithInteger(const std::string& name,
+                                        const int64_t value) const;
+
+    /// @brief Parse configuration.
+    ///
+    /// @param config String representing DUID configuration.
+    void build(const std::string& config) const;
+
+    /// @brief Test that only a DUID type can be specified.
+    ///
+    /// @param duid_type DUID type in numeric format.
+    /// @param duid_type_text DUID type in textual format.
+    void testTypeOnly(const DUID::DUIDType& duid_type,
+                      const std::string duid_type_text) const;
+
+    /// @brief Test that invalid configuration is rejected.
+    ///
+    /// @param config Holds JSON configuration to be used.
+    void testInvalidConfig(const std::string& config) const;
+
+    /// @brief Test out of range numeric values.
+    ///
+    /// @param param_name Parameter name.
+    /// @tparam Type of the numeric parameter.
+    template<typename NumericType>
+    void testOutOfRange(const std::string& param_name) {
+        // Obtain maximum value for the specified numeric type.
+        const uint64_t max_value = std::numeric_limits<NumericType>::max();
+
+        // Negative values are not allowed.
+        EXPECT_THROW(build(createConfigWithInteger(param_name, -1)),
+                     DhcpConfigError);
+        // Zero is allowed.
+        EXPECT_NO_THROW(build(createConfigWithInteger(param_name, 0)));
+        // Maximum value.
+        EXPECT_NO_THROW(build(createConfigWithInteger(param_name, max_value)));
+        // Value greater than maximum should result in exception.
+        EXPECT_THROW(build(createConfigWithInteger(param_name, max_value + 1)),
+                     DhcpConfigError);
+    }
+
+    /// @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;
+};
+
+std::string 
+DUIDConfigParserTest::createConfigWithType(const std::string& duid_type) const {
+    std::ostringstream s;
+    s << "{ \"type\": \"" << duid_type << "\" }";
+    return (s.str());
+}
+
+std::string
+DUIDConfigParserTest::createConfigWithInteger(const std::string& name,
+                                              const int64_t value) const {
+    std::ostringstream s;
+    s << "{ \"type\": \"LLT\", \"" << name << "\": " << value << " }";
+    return (s.str());
+}
+
+void
+DUIDConfigParserTest::build(const std::string& config) const {
+    ElementPtr config_element = Element::fromJSON(config);
+    DUIDConfigParser parser;
+    parser.build(config_element);
+}
+
+
+void
+DUIDConfigParserTest::testTypeOnly(const DUID::DUIDType& duid_type,
+                                   const std::string duid_type_text) const {
+    // Use DUID configuration with only a "type".
+    ASSERT_NO_THROW(build(createConfigWithType(duid_type_text)));
+
+    // Make sure that the type is correct and that other parameters are set
+    // to their defaults.
+    CfgDUIDPtr cfg_duid = CfgMgr::instance().getStagingCfg()->getCfgDUID();
+    EXPECT_EQ(duid_type, cfg_duid->getType());
+    EXPECT_TRUE(cfg_duid->getIdentifier().empty());
+    EXPECT_EQ(0, cfg_duid->getHType());
+    EXPECT_EQ(0, cfg_duid->getTime());
+    EXPECT_EQ(0, cfg_duid->getEnterpriseId());
+}
+
+void
+DUIDConfigParserTest::testInvalidConfig(const std::string& config) const {
+    EXPECT_THROW(build(config), DhcpConfigError);
+}
+
+std::string
+DUIDConfigParserTest::toString(const std::vector<uint8_t>& vec) const {
+    try {
+        return (util::encode::encodeHex(vec));
+    } catch (...) {
+        ADD_FAILURE() << "toString: unable to encode vector to"
+            " hexadecimal string";
+    }
+    return ("");
+}
+
+// This test verifies that it is allowed to specify a DUID-LLT type.
+TEST_F(DUIDConfigParserTest, typeOnlyLLT) {
+    testTypeOnly(DUID::DUID_LLT, "LLT");
+}
+
+// This test verifies that it is allowed to specify a DUID-EN type.
+TEST_F(DUIDConfigParserTest, typeOnlyEN) {
+    testTypeOnly(DUID::DUID_EN, "EN");
+}
+
+// This test verifies that it is allowed to specify a DUID-LL type.
+TEST_F(DUIDConfigParserTest, typeOnlyLL) {
+    testTypeOnly(DUID::DUID_LL, "LL");
+}
+
+// This test verifies that using unsupported DUID type will result in
+// configuration error.
+TEST_F(DUIDConfigParserTest, typeInvalid) {
+    testInvalidConfig(createConfigWithType("WRONG"));
+}
+
+// This test verifies that DUID type is required.
+TEST_F(DUIDConfigParserTest, noType) {
+    // First check that the configuration with DUID type specified is
+    // accepted.
+    ASSERT_NO_THROW(build("{ \"type\": \"LLT\", \"time\": 1 }"));
+    // Now remove the type and expect an error.
+    testInvalidConfig("{ \"time\": 1 }");
+}
+
+// This test verifies that all parameters can be set.
+TEST_F(DUIDConfigParserTest, allParameters) {
+    // Set all parameters.
+    ASSERT_NO_THROW(build("{ \"type\": \"EN\","
+                          "  \"identifier\": \"ABCDEF\","
+                          "  \"time\": 100,"
+                          "  \"htype\": 8,"
+                          "  \"enterprise-id\": 2024"
+                          "}"));
+
+    // Verify that parameters have been set correctly.
+    CfgDUIDPtr cfg_duid = CfgMgr::instance().getStagingCfg()->getCfgDUID();
+    EXPECT_EQ(DUID::DUID_EN, cfg_duid->getType());
+    EXPECT_EQ("ABCDEF", toString(cfg_duid->getIdentifier()));
+    EXPECT_EQ(8, cfg_duid->getHType());
+    EXPECT_EQ(100, cfg_duid->getTime());
+    EXPECT_EQ(2024, cfg_duid->getEnterpriseId());
+}
+
+// Test out of range values for time.
+TEST_F(DUIDConfigParserTest, timeOutOfRange) {
+    testOutOfRange<uint32_t>("time");
+}
+
+// Test out of range values for hardware type.
+TEST_F(DUIDConfigParserTest, htypeOutOfRange) {
+    testOutOfRange<uint16_t>("htype");
+}
+
+// Test out of range values for enterprise id.
+TEST_F(DUIDConfigParserTest, enterpriseIdOutOfRange) {
+    testOutOfRange<uint32_t>("enterprise-id");
+}
+
+} // end of anonymous namespace