Browse Source

[master] Merge branch 'trac2597' (server-id is now stored by dhcpv{4,6})

Conflicts:
	ChangeLog
	src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
Tomek Mrugalski 12 years ago
parent
commit
6167f31e01

+ 8 - 0
ChangeLog

@@ -1,3 +1,11 @@
+5XX.	[func]		tomek
+	b10-dhcp4: The DHCPv4 server now generates a server identifier
+	the first time it is run. The identifier is preserved in a file
+	across server restarts.
+	b10-dhcp6: The server identifier is now preserved in a file across
+	server restarts.
+	(Trac #2597, git fa342a994de5dbefe32996be7eebe58f6304cff7)
+
 549.	[func]		tomek
 	b10-dhcp6: It is now possible to specify that a configured subnet
 	is reachable locally over specified interface (see "interface"

+ 46 - 5
doc/guide/bind10-guide.xml

@@ -3485,18 +3485,34 @@ Dhcp4/subnet4	         []     list    (default)</screen>
         src/bin/dhcp6/dhcp4_srv.cc file, modify the following parameters and
         recompile:
         <screen>
-const std::string HARDCODED_LEASE = "192.0.2.222"; // assigned lease
-const std::string HARDCODED_NETMASK = "255.255.255.0";
-const uint32_t    HARDCODED_LEASE_TIME = 60; // in seconds
 const std::string HARDCODED_GATEWAY = "192.0.2.1";
 const std::string HARDCODED_DNS_SERVER = "192.0.2.2";
-const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";
-const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
+const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";</screen>
 
         Lease database and configuration support is planned for end of 2012.
       </para>
     </section>
 
+    <section id="dhcp4-serverid">
+      <title>Server Identifier in DHCPv4</title>
+      <para>The DHCPv4 protocol uses a "server identifier" for clients to be able
+      to discriminate between several servers present on the same link: this
+      value is an IPv4 address of the server. When started for the first time,
+      the DHCPv4 server will choose one of its IPv4 addresses as its server-id,
+      and store the chosen value to a file. (The file is named b10-dhcp4-serverid and is
+      stored in the "local state directory".  This is set during installation
+      when "configure" is run, and can be changed by using "--localstatedir"
+      on the "configure" command line.)  That file will be read by the server
+      and the contained value used whenever the server is subsequently started.
+      </para>
+      <para>
+        It is unlikely that this parameter needs to be changed. If such a need
+        arises, please stop the server, edit the file and restart the server.
+        It is a text file that should contain an IPv4 address. Spaces are
+        ignored.  No extra characters are allowed in this file.
+      </para>
+    </section>
+
     <section id="dhcp4-std">
       <title>Supported standards</title>
       <para>The following standards and draft standards are currently
@@ -3841,6 +3857,31 @@ Dhcp6/subnet6	         []     list    (default)</screen>
       </note>
     </section>
 
+    <section id="dhcp6-serverid">
+      <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 RFC 3315 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 (The file is named b10-dhcp6-serverid and is stored in the
+      "local state directory".  This is set during installation when
+      "configure" is run, and can be changed by using "--localstatedir"
+      on the "configure" command line.)  That file will be read by the server
+      and the contained value used whenever the server is subsequently started.
+      </para>
+      <para>
+        It is unlikely that this parameter needs to be changed. If such a need
+        arises, please stop the server, edit the file and start the server
+        again. It 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.
+      </para>
+    </section>
+
     <section id="dhcp6-std">
       <title>Supported DHCPv6 Standards</title>
       <para>The following standards and draft standards are currently

+ 20 - 0
src/bin/dhcp4/dhcp4_messages.mes

@@ -162,6 +162,26 @@ A debug message listing the data returned to the client.
 The IPv4 DHCP server has encountered a fatal error and is terminating.
 The reason for the failure is included in the message.
 
+% DHCP4_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 and has generated a new one. This server-id
+will be stored in a file and will be read (and used) whenever the server
+is restarted. This is normal behavior when the server is started for the
+first time. If this message is printed every time the server is started,
+please check that the server has sufficient permission to write its
+server-id file and that the file is not corrupt.
+
+% DHCP4_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.
+
+% DHCP4_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 to a file. The most likely cause is is that the server
+does not have permissions to write the server id file.
+
 % DHCP4_SESSION_FAIL failed to establish BIND 10 session (%1), running stand-alone
 The server has failed to establish communication with the rest of BIND
 10 and is running in stand-alone mode.  (This behavior will change once

+ 117 - 9
src/bin/dhcp4/dhcp4_srv.cc

@@ -30,6 +30,11 @@
 #include <dhcpsrv/utils.h>
 #include <dhcpsrv/addr_utilities.h>
 
+#include <boost/algorithm/string/erase.hpp>
+
+#include <iomanip>
+#include <fstream>
+
 using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
@@ -41,7 +46,6 @@ using namespace std;
 const std::string HARDCODED_GATEWAY = "192.0.2.1";
 const std::string HARDCODED_DNS_SERVER = "192.0.2.2";
 const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";
-const std::string HARDCODED_SERVER_ID = "192.0.2.1";
 
 Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig) {
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
@@ -56,7 +60,22 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig) {
             IfaceMgr::instance().openSockets4(port);
         }
 
-        setServerID();
+        string srvid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_ID_FILE);
+        if (loadServerID(srvid_file)) {
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_SERVERID_LOADED)
+                .arg(srvid_file);
+        } else {
+            generateServerID();
+            LOG_INFO(dhcp4_logger, DHCP4_SERVERID_GENERATED)
+                .arg(srvidToString(getServerID()))
+                .arg(srvid_file);
+
+            if (!writeServerID(srvid_file)) {
+                LOG_WARN(dhcp4_logger, DHCP4_SERVERID_WRITE_FAIL)
+                    .arg(srvid_file);
+            }
+
+        }
 
         // Instantiate LeaseMgr
         LeaseMgrFactory::create(dbconfig);
@@ -176,19 +195,108 @@ Dhcpv4Srv::run() {
                 }
             }
         }
+    }
+
+    return (true);
+}
+
+bool Dhcpv4Srv::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, " ");
+
+    try {
+        IOAddress addr(hex_string);
+
+        if (!addr.isV4()) {
+            return (false);
+        }
+
+        // Now create server-id option
+        serverid_.reset(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER, addr));
 
-        // TODO add support for config session (see src/bin/auth/main.cc)
-        //      so this daemon can be controlled from bob
+    } catch(...) {
+        // any kind of malformed input (empty string, IPv6 address, complete
+        // garbate etc.)
+        return (false);
     }
 
     return (true);
 }
 
-void
-Dhcpv4Srv::setServerID() {
-    /// @todo: implement this for real (see ticket #2588)
-    serverid_ = OptionPtr(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
-                                             IOAddress(HARDCODED_SERVER_ID)));
+void Dhcpv4Srv::generateServerID() {
+
+    const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+
+    // Let's find suitable interface.
+    for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
+         iface != ifaces.end(); ++iface) {
+
+        // 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;
+        }
+
+        const IfaceMgr::AddressCollection addrs = iface->getAddresses();
+
+        for (IfaceMgr::AddressCollection::const_iterator addr = addrs.begin();
+             addr != addrs.end(); ++addr) {
+            if (addr->getFamily() != AF_INET) {
+                continue;
+            }
+
+            serverid_ = OptionPtr(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
+                                                     *addr));
+            return;
+        }
+
+
+    }
+
+    isc_throw(BadValue, "No suitable interfaces for server-identifier found");
+}
+
+bool Dhcpv4Srv::writeServerID(const std::string& file_name) {
+    fstream f(file_name.c_str(), ios::out | ios::trunc);
+    if (!f.good()) {
+        return (false);
+    }
+    f << srvidToString(getServerID());
+    f.close();
+}
+
+string Dhcpv4Srv::srvidToString(const OptionPtr& srvid) {
+    if (!srvid) {
+        isc_throw(BadValue, "NULL pointer passed to srvidToString()");
+    }
+    boost::shared_ptr<Option4AddrLst> generated =
+        boost::dynamic_pointer_cast<Option4AddrLst>(srvid);
+    if (!srvid) {
+        isc_throw(BadValue, "Pointer to invalid option passed to srvidToString()");
+    }
+
+    Option4AddrLst::AddressContainer addrs = generated->getAddresses();
+    if (addrs.size() != 1) {
+        isc_throw(BadValue, "Malformed option passed to srvidToString(). "
+                  << "Expected to contain a single IPv4 address.");
+    }
+
+    return (addrs[0].toText());
 }
 
 void Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {

+ 35 - 1
src/bin/dhcp4/dhcp4_srv.h

@@ -28,6 +28,15 @@
 namespace isc {
 namespace dhcp {
 
+/// @brief file name of a server-id file
+///
+/// Server must store its server identifier in persistent storage that must not
+/// change between restarts. This is name of the file that is created in dataDir
+/// (see isc::dhcp::CfgMgr::getDataDir()). It is a text file that uses
+/// regular IPv4 address, e.g. 192.0.2.1. Server will create it during
+/// first run and then use it afterwards.
+static const char* SERVER_ID_FILE = "b10-dhcp4-serverid";
+
 /// @brief DHCPv4 server service.
 ///
 /// This singleton class represents DHCPv4 server. It contains all
@@ -216,7 +225,32 @@ protected:
     ///
     /// @throws isc::Unexpected Failed to obtain server identifier (i.e. no
     //          previously stored configuration and no network interfaces available)
-    void setServerID();
+    void generateServerID();
+
+    /// @brief attempts to load server-id 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 server-id file to load
+    /// @return true if load was successful, false otherwise
+    bool loadServerID(const std::string& file_name);
+
+    /// @brief attempts to write server-id to a file
+    /// Tries to write server-id content (stored in serverid_) to a text file.
+    ///
+    /// @param file_name name of the server-id file to write
+    /// @return true if write was successful, false otherwise
+    bool writeServerID(const std::string& file_name);
+
+    /// @brief converts server-id to text
+    /// Converts content of server-id option to a text representation, e.g.
+    /// "192.0.2.1"
+    ///
+    /// @param opt option that contains server-id
+    /// @return string representation
+    static std::string srvidToString(const OptionPtr& opt);
 
     /// @brief Selects a subnet for a given client's packet.
     ///

+ 43 - 1
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -49,9 +49,15 @@ public:
     using Dhcpv4Srv::processDecline;
     using Dhcpv4Srv::processInform;
     using Dhcpv4Srv::getServerID;
+    using Dhcpv4Srv::loadServerID;
+    using Dhcpv4Srv::generateServerID;
+    using Dhcpv4Srv::writeServerID;
     using Dhcpv4Srv::sanityCheck;
+    using Dhcpv4Srv::srvidToString;
 };
 
+static const char* SRVID_FILE = "server-id-test.txt";
+
 class Dhcpv4SrvTest : public ::testing::Test {
 public:
 
@@ -67,6 +73,9 @@ public:
 
         CfgMgr::instance().deleteSubnets4();
         CfgMgr::instance().addSubnet4(subnet_);
+
+        // it's ok if that fails. There should not be such a file anyway
+        unlink(SRVID_FILE);
     }
 
     /// @brief checks that the response matches request
@@ -245,6 +254,9 @@ public:
 
     ~Dhcpv4SrvTest() {
         CfgMgr::instance().deleteSubnets4();
+
+        // Let's clean up if there is such a file.
+        unlink(SRVID_FILE);
     };
 
     /// @brief A subnet used in most tests
@@ -691,7 +703,7 @@ TEST_F(Dhcpv4SrvTest, ManyDiscovers) {
     checkAddressParams(offer2, subnet_);
     checkAddressParams(offer3, subnet_);
 
-    // Check DUIDs
+    // Check server-ids
     checkServerId(offer1, srv->getServerID());
     checkServerId(offer2, srv->getServerID());
     checkServerId(offer3, srv->getServerID());
@@ -1126,4 +1138,34 @@ TEST_F(Dhcpv4SrvTest, ReleaseReject) {
     EXPECT_FALSE(l);
 }
 
+// This test verifies if the server-id disk operations (read, write) are
+// working properly.
+TEST_F(Dhcpv4SrvTest, ServerID) {
+    NakedDhcpv4Srv srv(0);
+
+    string srvid_text = "192.0.2.100";
+    IOAddress srvid(srvid_text);
+
+    fstream file1(SRVID_FILE, ios::out | ios::trunc);
+    file1 << srvid_text;
+    file1.close();
+
+    // Test reading from a file
+    EXPECT_TRUE(srv.loadServerID(SRVID_FILE));
+    ASSERT_TRUE(srv.getServerID());
+    EXPECT_EQ(srvid_text, srv.srvidToString(srv.getServerID()));
+
+    // Now test writing to a file
+    EXPECT_EQ(0, unlink(SRVID_FILE));
+    EXPECT_NO_THROW(srv.writeServerID(SRVID_FILE));
+
+    fstream file2(SRVID_FILE, ios::in);
+    ASSERT_TRUE(file2.good());
+    string text;
+    file2 >> text;
+    file2.close();
+
+    EXPECT_EQ(srvid_text, text);
+}
+
 } // end of anonymous namespace

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

@@ -42,7 +42,6 @@ using namespace std;
 namespace isc {
 namespace dhcp {
 
-
 ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL;
 
 ConstElementPtr

+ 28 - 0
src/bin/dhcp6/dhcp6_messages.mes

@@ -194,6 +194,34 @@ A debug message listing the data returned to the client.
 The IPv6 DHCP server has encountered a fatal error and is terminating.
 The reason for the failure is included in the message.
 
+% 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 cricital 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_SESSION_FAIL failed to establish BIND 10 session (%1), running stand-alone
 The server has failed to establish communication with the rest of BIND
 10 and is running in stand-alone mode.  (This behavior will change once

+ 81 - 4
src/bin/dhcp6/dhcp6_srv.cc

@@ -36,11 +36,16 @@
 #include <exceptions/exceptions.h>
 #include <util/io_utilities.h>
 #include <util/range_utilities.h>
+#include <util/encode/hex.h>
 
 #include <boost/foreach.hpp>
+#include <boost/tokenizer.hpp>
+#include <boost/algorithm/string/erase.hpp>
 
 #include <stdlib.h>
 #include <time.h>
+#include <iomanip>
+#include <fstream>
 
 using namespace isc;
 using namespace isc::asiolink;
@@ -70,7 +75,22 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port, const char* dbconfig)
             IfaceMgr::instance().openSockets6(port);
         }
 
-        setServerID();
+        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(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);
+            }
+
+        }
 
         // Instantiate LeaseMgr
         LeaseMgrFactory::create(dbconfig);
@@ -209,10 +229,67 @@ bool Dhcpv6Srv::run() {
     return (true);
 }
 
-void Dhcpv6Srv::setServerID() {
+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;
+
+    OptionBuffer data = opt->getData();
+
+    bool colon = false;
+    for (OptionBufferConstIter it = data.begin(); it != data.end(); ++it) {
+        if (colon) {
+            tmp << ":";
+        }
+        tmp << hex << setw(2) << setfill('0') << static_cast<uint16_t>(*it);
+        if (!colon) {
+            colon = true;
+        }
+    }
+
+    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();
+}
 
-    /// @todo: DUID should be generated once and then stored, rather
-    /// than generated each time
+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

+ 39 - 5
src/bin/dhcp6/dhcp6_srv.h

@@ -31,6 +31,16 @@
 namespace isc {
 namespace dhcp {
 
+/// @brief file name of a server-id file
+///
+/// Server must store its duid in persistent storage that must not change
+/// between restarts. This is name of the file that is created in dataDir
+/// (see isc::dhcp::CfgMgr::getDataDir()). It is a text file that uses
+/// double digit hex values separated by colons format, e.g.
+/// 01:ff:02:03:06:80:90:ab:cd:ef. Server will create it during first
+/// run and then use it afterwards.
+static const char* SERVER_DUID_FILE = "b10-dhcp6-serverid";
+
 /// @brief DHCPv6 server service.
 ///
 /// This class represents DHCPv6 server. It contains all
@@ -290,15 +300,39 @@ protected:
 
     /// @brief Sets server-identifier.
     ///
-    /// This method attempts to set server-identifier DUID. It loads it
-    /// from a file. If file load fails, it generates new DUID using
-    /// interface link-layer addresses (EUI-64) + timestamp (DUID type
-    /// duid-llt, see RFC3315, section 9.2). If there are no suitable
+    /// 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 setServerID();
+    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
+    ///
+    /// @param opt option that contains DUID
+    /// @return string representation
+    static std::string duidToString(const OptionPtr& opt);
 
 private:
     /// @brief Allocation Engine.

+ 44 - 1
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -35,7 +35,7 @@
 
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
-
+#include <unistd.h>
 #include <fstream>
 #include <iostream>
 #include <sstream>
@@ -64,10 +64,16 @@ public:
     using Dhcpv6Srv::createStatusCode;
     using Dhcpv6Srv::selectSubnet;
     using Dhcpv6Srv::sanityCheck;
+    using Dhcpv6Srv::loadServerID;
+    using Dhcpv6Srv::writeServerID;
 };
 
+static const char* DUID_FILE = "server-id-test.txt";
+
 class Dhcpv6SrvTest : public ::testing::Test {
 public:
+    /// Name of the server-id file (used in server-id tests)
+
     // these are empty for now, but let's keep them around
     Dhcpv6SrvTest() : rcode_(-1) {
         subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 48, 1000,
@@ -77,6 +83,9 @@ public:
 
         CfgMgr::instance().deleteSubnets6();
         CfgMgr::instance().addSubnet6(subnet_);
+
+        // it's ok if that fails. There should not be such a file anyway
+        unlink(DUID_FILE);
     }
 
     // Generate IA_NA option with specified parameters
@@ -246,6 +255,9 @@ public:
 
     ~Dhcpv6SrvTest() {
         CfgMgr::instance().deleteSubnets6();
+
+        // Let's clean up if there is such a file.
+        unlink(DUID_FILE);
     };
 
     // A subnet used in most tests
@@ -1407,7 +1419,38 @@ TEST_F(Dhcpv6SrvTest, selectSubnetIface) {
 
     pkt->setIface("wifi1");
     EXPECT_EQ(subnet3, 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, unlink(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);
 }
 
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test

+ 1 - 1
src/lib/dhcpsrv/Makefile.am

@@ -1,6 +1,6 @@
 SUBDIRS = . tests
 
-AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib -DDHCP_DATA_DIR="\"$(localstatedir)\""
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 if HAVE_MYSQL
 AM_CPPFLAGS += $(MYSQL_CPPFLAGS)

+ 10 - 1
src/lib/dhcpsrv/cfgmgr.cc

@@ -248,7 +248,16 @@ void CfgMgr::deleteSubnets6() {
     subnets6_.clear();
 }
 
-CfgMgr::CfgMgr() {
+std::string CfgMgr::getDataDir() {
+    return (datadir_);
+}
+
+
+CfgMgr::CfgMgr()
+    :datadir_(DHCP_DATA_DIR) {
+    // DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
+    // Note: the definition of DHCP_DATA_DIR needs to include quotation marks
+    // See AM_CPPFLAGS definition in Makefile.am
 }
 
 CfgMgr::~CfgMgr() {

+ 10 - 0
src/lib/dhcpsrv/cfgmgr.h

@@ -230,6 +230,14 @@ public:
     /// completely new?
     void deleteSubnets4();
 
+
+    /// @brief returns path do the data directory
+    ///
+    /// This method returns a path to writeable directory that DHCP servers
+    /// can store data in.
+    /// @return data directory
+    std::string getDataDir();
+
 protected:
 
     /// @brief Protected constructor.
@@ -274,6 +282,8 @@ private:
     /// @brief Container for defined DHCPv4 option spaces.
     OptionSpaceCollection spaces4_;
 
+    /// @brief directory where data files (e.g. server-id) are stored
+    std::string datadir_;
 };
 
 } // namespace isc::dhcp