Browse Source

[master] Merge branch 'trac5376'

Marcin Siodelski 7 years ago
parent
commit
aae2d91f10

+ 26 - 4
doc/guide/dhcp4-srv.xml

@@ -1328,9 +1328,11 @@ This rather belong to the DDNS configuration
 <row><entry>dhcp-lease-time</entry><entry>51</entry><entry>uint32</entry><entry>false</entry><entry>true</entry></row>
 -->
 <row><entry>dhcp-option-overload</entry><entry>52</entry><entry>uint8</entry><entry>false</entry><entry>false</entry></row>
-<!-- Message Type, Server Identifier and Parameter Request List should not be configured by a user.
+<!-- Message Type  should not be configured by a user.
 <row><entry>dhcp-message-type</entry><entry>53</entry><entry>uint8</entry><entry>false</entry><entry>false</entry></row>
+-->
 <row><entry>dhcp-server-identifier</entry><entry>54</entry><entry>ipv4-address</entry><entry>false</entry><entry>true</entry></row>
+<!-- Parameter Request List should not be configured by a user.
 <row><entry>dhcp-parameter-request-list</entry><entry>55</entry><entry>uint8</entry><entry>true</entry><entry>true</entry></row>
 -->
 <row><entry>dhcp-message</entry><entry>56</entry><entry>string</entry><entry>false</entry><entry>false</entry></row>
@@ -3847,11 +3849,31 @@ i.e. it will assign increasing integer values starting from 1.</para>
         received. A single server instance will use multiple server identifiers
         if it is receiving queries on multiple interfaces.
       </para>
+
       <para>
-        Currently there is no mechanism to override the default server identifiers
-        by an administrator. In the future, the configuration mechanism will be used
-        to specify the custom server identifier.
+        It is possible to override default server identifier values by specifying
+        "dhcp-server-identifier" option. This option is only supported on the
+        global, shared network and subnet level. It must not be specified
+        on client class and host reservation level.
       </para>
+
+      <para>
+        The following example demonstrates how to override server identifier for
+        a subnet:
+<screen>
+"subnet4": [
+    {
+        "subnet": "192.0.2.0/24",
+        "option-data": [
+            {
+                "name": "dhcp-server-identifier",
+                "data": "10.2.5.76"
+            }
+        ],
+        ...
+    }
+]</screen>
+</para>
     </section>
 
     <section id="dhcp4-subnet-selection">

+ 58 - 2
src/bin/dhcp4/dhcp4_srv.cc

@@ -11,6 +11,7 @@
 #include <dhcp/iface_mgr.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option4_addrlst.h>
+#include <dhcp/option_custom.h>
 #include <dhcp/option_int.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_vendor.h>
@@ -27,6 +28,7 @@
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfg_host_operations.h>
 #include <dhcpsrv/cfg_iface.h>
+#include <dhcpsrv/cfg_shared_networks.h>
 #include <dhcpsrv/cfg_subnets4.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
@@ -62,6 +64,7 @@
 #include <boost/algorithm/string/join.hpp>
 #include <boost/bind.hpp>
 #include <boost/foreach.hpp>
+#include <boost/pointer_cast.hpp>
 #include <boost/shared_ptr.hpp>
 
 #include <iomanip>
@@ -1142,6 +1145,12 @@ Dhcpv4Srv::srvidToString(const OptionPtr& srvid) {
 void
 Dhcpv4Srv::appendServerID(Dhcpv4Exchange& ex) {
 
+    // Do not append generated server identifier if there is one appended already.
+    // This is when explicitly configured server identifier option is present.
+    if (ex.getResponse()->getOption(DHO_DHCP_SERVER_IDENTIFIER)) {
+        return;
+    }
+
     // Use local address on which the packet has been received as a
     // server identifier. In some cases it may be a different address,
     // e.g. broadcast packet or DHCPv4o6 packet.
@@ -1386,7 +1395,8 @@ Dhcpv4Srv::appendBasicOptions(Dhcpv4Exchange& ex) {
     static const uint16_t required_options[] = {
         DHO_ROUTERS,
         DHO_DOMAIN_NAME_SERVERS,
-        DHO_DOMAIN_NAME };
+        DHO_DOMAIN_NAME,
+        DHO_DHCP_SERVER_IDENTIFIER };
 
     static size_t required_options_size =
         sizeof(required_options) / sizeof(required_options[0]);
@@ -2817,7 +2827,53 @@ Dhcpv4Srv::acceptServerId(const Pkt4Ptr& query) const {
     // performance hit should be acceptable. If it turns out to
     // be significant, we will have to cache server identifiers
     // when sockets are opened.
-    return (IfaceMgr::instance().hasOpenSocket(server_id));
+    if (IfaceMgr::instance().hasOpenSocket(server_id)) {
+        return (true);
+    }
+
+    // There are some cases when an administrator explicitly sets server
+    // identifier (option 54) that should be used for a given, subnet,
+    // network etc. It doesn't have to be an address assigned to any of
+    // the server interfaces. Thus, we have to check if the server
+    // identifier received is the one that we explicitly set in the
+    // server configuration. At this point, we don't know which subnet
+    // the client belongs to so we can't match the server id with any
+    // subnet. We simply check if this server identifier is configured
+    // anywhere. This should be good enough to eliminate exchanges
+    // with other servers in the same network.
+
+    /// @todo Currently we only check subnet identifiers configured on the
+    /// subnet level, shared network level and global level. This should
+    /// be sufficient for most of cases. At this point, trying to support
+    /// server identifiers on the class level seems to be an overkill and
+    /// is probably not needed. Same with host reservations. In fact,
+    /// at this point we don't know the reservations for the client
+    /// communicating with the server. We may revise some of these choices
+    /// in the future.
+
+    SrvConfigPtr cfg = CfgMgr::instance().getCurrentCfg();
+
+    // Check if there is at least one subnet configured with this server
+    // identifier.
+    ConstCfgSubnets4Ptr cfg_subnets = cfg->getCfgSubnets4();
+    if (cfg_subnets->hasSubnetWithServerId(server_id)) {
+        return (true);
+    }
+
+    // This server identifier is not configured for any of the subnets, so
+    // check on the shared network level.
+    CfgSharedNetworks4Ptr cfg_networks = cfg->getCfgSharedNetworks4();
+    if (cfg_networks->hasNetworkWithServerId(server_id)) {
+        return (true);
+    }
+
+    // Finally, it is possible that the server identifier is specified
+    // on the global level.
+    ConstCfgOptionPtr cfg_global_options = cfg->getCfgOption();
+    OptionCustomPtr opt_server_id = boost::dynamic_pointer_cast<OptionCustom>
+        (cfg_global_options->get(DHCP4_OPTION_SPACE, DHO_DHCP_SERVER_IDENTIFIER).option_);
+
+    return (opt_server_id && (opt_server_id->readAddress() == server_id));
 }
 
 void

+ 7 - 5
src/bin/dhcp4/dhcp4_srv.h

@@ -525,7 +525,8 @@ protected:
     /// - Subnet Mask,
     /// - Router,
     /// - Name Server,
-    /// - Domain Name.
+    /// - Domain Name,
+    /// - Server Identifier.
     ///
     /// @param ex DHCPv4 exchange holding the client's message to be checked.
     void appendBasicOptions(Dhcpv4Exchange& ex);
@@ -681,10 +682,11 @@ protected:
 
     /// @brief Adds server identifier option to the server's response.
     ///
-    /// This method adds a server identifier to the DHCPv4 message. This is set
-    /// to the local address on which the client's query has been received with
-    /// the exception of broadcast traffic and DHCPv4o6 query for which a socket
-    /// on the particular interface is found and its address is used as server id.
+    /// This method adds a server identifier to the DHCPv4 message if it doesn't
+    /// exist yet. This is set to the local address on which the client's query has
+    /// been received with the exception of broadcast traffic and DHCPv4o6 query for
+    /// which a socket on the particular interface is found and its address is used
+    /// as server id.
     ///
     /// @note This method doesn't throw exceptions by itself but the underlying
     /// classes being used my throw. The reason for this method to not sanity

+ 100 - 10
src/bin/dhcp4/tests/dora_unittest.cc

@@ -87,14 +87,20 @@ namespace {
 ///     - boot-file-name = "bootfile.efi"
 ///
 /// - Configuration 7:
+///   - Used for testing custom value of dhcp-server-identifier option.
+///   - 3 subnets: 10.0.0.0/24, 192.0.2.0/26 and 192.0.2.64/26
+///   - Custom server identifier specified for 2 subnets subnet.
+///   - Custom server identifier specified at global level.
+///
+/// - Configuration 8:
 ///   - Simple configuration with a single subnet and single pool
 ///   - Using MySQL lease database backend to store leases
 ///
-/// - Configuration 8:
+/// - Configuration 9:
 ///   - Simple configuration with a single subnet and single pool
 ///   - Using PostgreSQL lease database backend to store leases
 ///
-/// - Configuration 9:
+/// - Configuration 10:
 ///   - Simple configuration with a single subnet and single pool
 ///   - Using Cassandra lease database backend to store leases
 const char* DORA_CONFIGS[] = {
@@ -283,6 +289,50 @@ const char* DORA_CONFIGS[] = {
 
 // Configuration 7
     "{ \"interfaces-config\": {"
+        "      \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"valid-lifetime\": 600,"
+        "\"option-data\": ["
+        "    {"
+        "        \"name\": \"dhcp-server-identifier\","
+        "        \"data\": \"3.4.5.6\""
+        "    }"
+        "],"
+        "\"subnet4\": ["
+        "    {"
+        "        \"subnet\": \"10.0.0.0/24\", "
+        "        \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+        "        \"interface\": \"eth0\","
+        "        \"option-data\": ["
+        "            {"
+        "                \"name\": \"dhcp-server-identifier\","
+        "                \"data\": \"1.2.3.4\""
+        "            }"
+        "        ]"
+        "    },"
+        "    {"
+        "        \"subnet\": \"192.0.2.0/26\", "
+        "        \"pools\": [ { \"pool\": \"192.0.2.10-192.0.2.63\" } ],"
+        "        \"interface\": \"eth1\","
+        "        \"option-data\": ["
+        "            {"
+        "                \"name\": \"dhcp-server-identifier\","
+        "                \"data\": \"2.3.4.5\""
+        "            }"
+        "        ]"
+        "    },"
+        "    {"
+        "        \"subnet\": \"192.0.2.64/26\", "
+        "        \"pools\": [ { \"pool\": \"192.0.2.65-192.0.2.100\" } ],"
+        "        \"relay\": {"
+        "            \"ip-address\": \"10.2.3.4\""
+        "        }"
+        "    }"
+        "]"
+    "}",
+
+// Configuration 8
+    "{ \"interfaces-config\": {"
         "   \"interfaces\": [ \"*\" ]"
         "},"
         "\"lease-database\": {"
@@ -299,7 +349,7 @@ const char* DORA_CONFIGS[] = {
         " } ]"
     "}",
 
-// Configuration 8
+// Configuration 9
     "{ \"interfaces-config\": {"
         "   \"interfaces\": [ \"*\" ]"
         "},"
@@ -317,7 +367,7 @@ const char* DORA_CONFIGS[] = {
         " } ]"
     "}",
 
-// Configuration 9
+// Configuration 10
     "{ \"interfaces-config\": {"
         "   \"interfaces\": [ \"*\" ]"
         "},"
@@ -1568,6 +1618,46 @@ TEST_F(DORATest, multiStageBoot) {
     testMultiStageBoot(0);
 }
 
+// This test verifies that custom server identifier can be specified for
+// a subnet.
+TEST_F(DORATest, customServerIdentifier) {
+    Dhcp4Client client1(Dhcp4Client::SELECTING);
+    // Configure DHCP server.
+    ASSERT_NO_THROW(configure(DORA_CONFIGS[7], *client1.getServer()));
+
+    ASSERT_NO_THROW(client1.doDORA());
+    // Make sure that the server responded.
+    ASSERT_TRUE(client1.getContext().response_);
+    Pkt4Ptr resp = client1.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // The explicitly configured server identifier should take precedence
+    // over generated server identifier.
+    EXPECT_EQ("1.2.3.4", client1.config_.serverid_.toText());
+
+    // Repeat the test for different subnet.
+    Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+    client2.setIfaceName("eth1");
+
+    ASSERT_NO_THROW(client2.doDORA());
+    ASSERT_TRUE(client2.getContext().response_);
+    resp = client2.getContext().response_;
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    EXPECT_EQ("2.3.4.5", client2.config_.serverid_.toText());
+
+    // Create relayed client which will be assigned a lease from the third
+    // subnet. This subnet inherits server identifier value from the global
+    // scope.
+    Dhcp4Client client3(client1.getServer(), Dhcp4Client::SELECTING);
+    client3.useRelay(true, IOAddress("10.2.3.4"));
+
+    ASSERT_NO_THROW(client3.doDORA());
+    ASSERT_TRUE(client3.getContext().response_);
+    resp = client3.getContext().response_;
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    EXPECT_EQ("3.4.5.6", client3.config_.serverid_.toText());
+}
+
 // Starting tests which require MySQL backend availability. Those tests
 // will not be executed if Kea has been compiled without the
 // --with-dhcp-mysql.
@@ -1595,8 +1685,8 @@ public:
 // Test that the client using the same hardware address but multiple
 // client identifiers will obtain multiple leases (MySQL lease database).
 TEST_F(DORAMySQLTest, multiStageBoot) {
-    // DORA_CONFIGS[7] to be used for server configuration.
-    testMultiStageBoot(7);
+    // DORA_CONFIGS[9] to be used for server configuration.
+    testMultiStageBoot(8);
 }
 
 #endif
@@ -1628,8 +1718,8 @@ public:
 // Test that the client using the same hardware address but multiple
 // client identifiers will obtain multiple leases (PostgreSQL lease database).
 TEST_F(DORAPgSQLTest, multiStageBoot) {
-    // DORA_CONFIGS[8] to be used for server configuration.
-    testMultiStageBoot(8);
+    // DORA_CONFIGS[9] to be used for server configuration.
+    testMultiStageBoot(9);
 }
 
 #endif
@@ -1658,8 +1748,8 @@ public:
 // Test that the client using the same hardware address but multiple
 // client identifiers will obtain multiple leases (CQL lease database).
 TEST_F(DORACQLTest, multiStageBoot) {
-    // DORA_CONFIGS[9] to be used for server configuration.
-    testMultiStageBoot(9);
+    // DORA_CONFIGS[10] to be used for server configuration.
+    testMultiStageBoot(10);
 }
 
 #endif

+ 93 - 0
src/bin/dhcp4/tests/shared_network_unittest.cc

@@ -808,6 +808,59 @@ const char* NETWORKS_CONFIG[] = {
     "    ]"
     "}",
 
+// Configuration #15
+// - two shared networks, each comes with its own server identifier.
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [ \"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 600,"
+    "    \"shared-networks\": ["
+    "        {"
+    "            \"name\": \"frog\","
+    "            \"interface\": \"eth1\","
+    "            \"option-data\": ["
+    "                {"
+    "                    \"name\": \"dhcp-server-identifier\","
+    "                    \"data\": \"1.2.3.4\""
+    "                }"
+    "            ],"
+    "            \"subnet4\": ["
+    "                {"
+    "                    \"subnet\": \"192.0.2.0/26\","
+    "                    \"id\": 10,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"192.0.2.1 - 192.0.2.63\""
+    "                        }"
+    "                    ]"
+    "                }"
+    "            ]"
+    "        },"
+    "        {"
+    "            \"name\": \"dog\","
+    "            \"interface\": \"eth0\","
+    "            \"option-data\": ["
+    "                {"
+    "                    \"name\": \"dhcp-server-identifier\","
+    "                    \"data\": \"2.3.4.5\""
+    "                }"
+    "            ],"
+    "            \"subnet4\": ["
+    "                {"
+    "                    \"subnet\": \"10.0.0.0/26\","
+    "                    \"id\": 1000,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"10.0.0.1 - 10.0.0.63\""
+    "                        }"
+    "                    ]"
+    "                }"
+    "            ]"
+    "        }"
+    "    ]"
+    "}"
+
 };
 
 /// @Brief Test fixture class for DHCPv4 server using shared networks.
@@ -1686,4 +1739,44 @@ TEST_F(Dhcpv4SharedNetworkTest, sharedNetworkSelectedByClass) {
     });
 }
 
+// This test verifies that custom server identifier can be specified for a
+// shared network.
+TEST_F(Dhcpv4SharedNetworkTest, customServerIdentifier) {
+    Dhcp4Client client1(Dhcp4Client::SELECTING);
+    client1.setIfaceName("eth1");
+
+    // Configure DHCP server.
+    ASSERT_NO_THROW(configure(NETWORKS_CONFIG[15], *client1.getServer()));
+
+    testAssigned([this, &client1] {
+        ASSERT_NO_THROW(client1.doDORA());
+    });
+
+    // Make sure that the server responded.
+    ASSERT_TRUE(client1.getContext().response_);
+    Pkt4Ptr resp = client1.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // The explicitly configured server identifier should take precedence
+    // over generated server identifier.
+    EXPECT_EQ("1.2.3.4", client1.config_.serverid_.toText());
+
+    // Create another client using different interface.
+    Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+    client2.setIfaceName("eth0");
+
+    testAssigned([this, &client2] {
+        ASSERT_NO_THROW(client2.doDORA());
+    });
+
+    // Make sure that the server responded.
+    ASSERT_TRUE(client2.getContext().response_);
+    resp = client2.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // The explicitly configured server identifier should take precedence
+    // over generated server identifier.
+    EXPECT_EQ("2.3.4.5", client2.config_.serverid_.toText());
+}
+
 } // end of anonymous namespace

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

@@ -101,7 +101,7 @@ libkea_dhcpsrv_la_SOURCES += cfg_host_operations.cc cfg_host_operations.h
 libkea_dhcpsrv_la_SOURCES += cfg_option.cc cfg_option.h
 libkea_dhcpsrv_la_SOURCES += cfg_option_def.cc cfg_option_def.h
 libkea_dhcpsrv_la_SOURCES += cfg_rsoo.cc cfg_rsoo.h
-libkea_dhcpsrv_la_SOURCES += cfg_shared_networks.h
+libkea_dhcpsrv_la_SOURCES += cfg_shared_networks.cc cfg_shared_networks.h
 libkea_dhcpsrv_la_SOURCES += cfg_subnets4.cc cfg_subnets4.h
 libkea_dhcpsrv_la_SOURCES += cfg_subnets6.cc cfg_subnets6.h
 libkea_dhcpsrv_la_SOURCES += cfg_mac_source.cc cfg_mac_source.h

+ 24 - 0
src/lib/dhcpsrv/cfg_shared_networks.cc

@@ -0,0 +1,24 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <dhcpsrv/cfg_shared_networks.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+bool
+CfgSharedNetworks4::hasNetworkWithServerId(const IOAddress& server_id) const {
+    const auto& index = networks_.get<SharedNetworkServerIdIndexTag>();
+    auto network_it = index.find(server_id);
+    return (network_it != index.cend());
+}
+
+
+} // end of namespace isc::dhcp
+} // end of namespace isc

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

@@ -7,6 +7,7 @@
 #ifndef CFG_SHARED_NETWORKS_H
 #define CFG_SHARED_NETWORKS_H
 
+#include <asiolink/io_address.h>
 #include <cc/cfg_to_element.h>
 #include <cc/data.h>
 #include <exceptions/exceptions.h>
@@ -112,6 +113,15 @@ public:
         return (&networks_);
     }
 
+    /// @brief Checks if specified server identifier has been specified for
+    /// any network.
+    ///
+    /// @param server_id Server identifier.
+    ///
+    /// @return true if there is a network with a specified server identifier.
+    bool hasNetworkWithServerId(const asiolink::IOAddress& server_id) const;
+
+
 };
 
 /// @brief Pointer to the configuration of IPv4 shared networks.

+ 7 - 0
src/lib/dhcpsrv/cfg_subnets4.cc

@@ -68,6 +68,13 @@ CfgSubnets4::getByPrefix(const std::string& subnet_text) const {
     return ((subnet_it != index.cend()) ? (*subnet_it) : ConstSubnet4Ptr());
 }
 
+bool
+CfgSubnets4::hasSubnetWithServerId(const asiolink::IOAddress& server_id) const {
+    const auto& index = subnets_.get<SubnetServerIdIndexTag>();
+    auto subnet_it = index.find(server_id);
+    return (subnet_it != index.cend());
+}
+
 Subnet4Ptr
 CfgSubnets4::selectSubnet4o6(const SubnetSelector& selector) const {
 

+ 8 - 0
src/lib/dhcpsrv/cfg_subnets4.h

@@ -91,6 +91,14 @@ public:
     /// subnet doesn't exist.
     ConstSubnet4Ptr getByPrefix(const std::string& subnet_prefix) const;
 
+    /// @brief Checks if specified server identifier has been specified for
+    /// any subnet.
+    ///
+    /// @param server_id Server identifier.
+    ///
+    /// @return true if there is a subnet with a specified server identifier.
+    bool hasSubnetWithServerId(const asiolink::IOAddress& server_id) const;
+
     /// @brief Returns a pointer to the selected subnet.
     ///
     /// This method tries to retrieve the subnet for the client using various

+ 20 - 0
src/lib/dhcpsrv/network.cc

@@ -4,8 +4,13 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+#include <dhcp/dhcp4.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_space.h>
 #include <dhcpsrv/network.h>
+#include <boost/pointer_cast.hpp>
 
+using namespace isc::asiolink;
 using namespace isc::data;
 
 namespace isc {
@@ -112,6 +117,21 @@ Network4::toElement() const {
     return (map);
 }
 
+IOAddress
+Network4::getServerId() const {
+    try {
+        OptionCustomPtr opt_server_id = boost::dynamic_pointer_cast<OptionCustom>
+            (cfg_option_->get(DHCP4_OPTION_SPACE, DHO_DHCP_SERVER_IDENTIFIER).option_);
+        if (opt_server_id) {
+            return (opt_server_id->readAddress());
+        }
+    } catch (const std::exception&) {
+        // Ignore any exceptions and simply return empty buffer.
+    }
+
+    return (IOAddress::IPV4_ZERO_ADDRESS());
+}
+
 ElementPtr
 Network6::toElement() const {
     ElementPtr map = Network::toElement();

+ 6 - 0
src/lib/dhcpsrv/network.h

@@ -337,6 +337,12 @@ public:
     /// @return A pointer to unparsed network configuration.
     virtual data::ElementPtr toElement() const;
 
+    /// @brief Returns binary representation of the dhcp-server-identifier option (54).
+    ///
+    /// @return Server identifier option as IPv4 address. Zero IPv4 address
+    /// indicates that server identifier hasn't been specified.
+    virtual asiolink::IOAddress getServerId() const;
+
 private:
 
     /// @brief Should server use client identifiers for client lease

+ 12 - 0
src/lib/dhcpsrv/shared_network.h

@@ -7,6 +7,7 @@
 #ifndef SHARED_NETWORK_H
 #define SHARED_NETWORK_H
 
+#include <asiolink/io_address.h>
 #include <cc/data.h>
 #include <exceptions/exceptions.h>
 #include <dhcpsrv/assignable_network.h>
@@ -30,6 +31,9 @@ struct SharedNetworkRandomAccessIndexTag { };
 /// @brief A tag for accessing index by shared network name.
 struct SharedNetworkNameIndexTag { };
 
+/// @brief A tag for accessing index by server identifier.
+struct SharedNetworkServerIdIndexTag { };
+
 /// @brief Shared network holding IPv4 subnets.
 ///
 /// Specialization of the @ref Network4 class for IPv4 shared networks.
@@ -149,7 +153,15 @@ typedef boost::multi_index_container<
             boost::multi_index::tag<SharedNetworkNameIndexTag>,
             boost::multi_index::const_mem_fun<SharedNetwork4, std::string,
                                               &SharedNetwork4::getName>
+        >,
+        // Third index allows for access by server identifier specified for the
+        // network.
+        boost::multi_index::ordered_non_unique<
+            boost::multi_index::tag<SharedNetworkServerIdIndexTag>,
+            boost::multi_index::const_mem_fun<Network4, asiolink::IOAddress,
+                                              &Network4::getServerId>
         >
+
     >
 > SharedNetwork4Collection;
 

+ 63 - 19
src/lib/dhcpsrv/subnet.h

@@ -613,13 +613,20 @@ struct SubnetSubnetIdIndexTag { };
 /// @brief Tag for the index for searching by subnet prefix.
 struct SubnetPrefixIndexTag { };
 
-/// @brief Multi index container holding subnets.
+/// @brief Tag for the index for searching by server identifier.
+struct SubnetServerIdIndexTag { };
+
+/// @brief A collection of @c Subnet4 objects
+///
+/// This container provides a set of indexes which can be used to retrieve
+/// subnets by various properties.
 ///
-/// This multi index container can hold pointers to @ref Subnet4 or
-/// @ref Subnet6 objects representing subnets. It provides indexes for
-/// subnet lookups using subnet properties such as: subnet identifier
-/// or subnet prefix. It also provides a random access index which
-/// allows for using the container like a vector.
+/// This multi index container can hold pointers to @ref Subnet4
+/// objects representing subnets. It provides indexes for subnet lookups
+/// using subnet properties such as: subnet identifier,
+/// subnet prefix or server identifier specified for a subnet. It also
+/// provides a random access index which allows for using the container
+/// like a vector.
 ///
 /// The random access index is used by the DHCP servers which perform
 /// a full scan on subnets to find the one that matches some specific
@@ -632,12 +639,9 @@ struct SubnetPrefixIndexTag { };
 /// @todo We should consider optimizing subnet selection by leveraging
 /// the indexing capabilities of this container, e.g. searching for
 /// a subnet by interface name, relay address etc.
-///
-/// @tparam SubnetType Type of the subnet: @ref Subnet4 or @ref Subnet6.
-template<typename SubnetType>
-using SubnetCollection = boost::multi_index_container<
+typedef boost::multi_index_container<
     // Multi index container holds pointers to the subnets.
-    boost::shared_ptr<SubnetType>,
+    Subnet4Ptr,
     // The following holds all indexes.
     boost::multi_index::indexed_by<
         // First is the random access index allowing for accessing
@@ -654,21 +658,61 @@ using SubnetCollection = boost::multi_index_container<
         boost::multi_index::ordered_unique<
             boost::multi_index::tag<SubnetPrefixIndexTag>,
             boost::multi_index::const_mem_fun<Subnet, std::string, &Subnet::toText>
+        >,
+
+        // Fourth index allows for searching using an output from getServerId
+        boost::multi_index::ordered_non_unique<
+            boost::multi_index::tag<SubnetServerIdIndexTag>,
+            boost::multi_index::const_mem_fun<Network4, asiolink::IOAddress,
+                                              &Network4::getServerId>
         >
     >
->;
-
-/// @brief A collection of @c Subnet4 objects
-///
-/// This container provides a set of indexes which can be used to retrieve
-/// subnets by various properties.
-typedef SubnetCollection<Subnet4> Subnet4Collection;
+> Subnet4Collection;
 
 /// @brief A collection of @c Subnet6 objects
 ///
 /// This container provides a set of indexes which can be used to retrieve
 /// subnets by various properties.
-typedef SubnetCollection<Subnet6> Subnet6Collection;
+///
+/// This multi index container can hold pointers to @ref Subnet6 objects
+/// representing subnets. It provides indexes for subnet lookups using
+/// subnet properties such as: subnet identifier or subnet prefix. It
+/// also provides a random access index which allows for using the
+/// container like a vector.
+///
+/// The random access index is used by the DHCP servers which perform
+/// a full scan on subnets to find the one that matches some specific
+/// criteria for subnet selection.
+///
+/// The remaining indexes are used for searching for a specific subnet
+/// as a result of receiving a command over the control API, e.g.
+/// when 'subnet-get' command is received.
+///
+/// @todo We should consider optimizing subnet selection by leveraging
+/// the indexing capabilities of this container, e.g. searching for
+/// a subnet by interface name, relay address etc.
+typedef boost::multi_index_container<
+    // Multi index container holds pointers to the subnets.
+    Subnet6Ptr,
+    // The following holds all indexes.
+    boost::multi_index::indexed_by<
+        // First is the random access index allowing for accessing
+        // objects just like we'd do with a vector.
+        boost::multi_index::random_access<
+            boost::multi_index::tag<SubnetRandomAccessIndexTag>
+        >,
+        // Second index allows for searching using subnet identifier.
+        boost::multi_index::ordered_unique<
+            boost::multi_index::tag<SubnetSubnetIdIndexTag>,
+            boost::multi_index::const_mem_fun<Subnet, SubnetID, &Subnet::getID>
+        >,
+        // Third index allows for searching using an output from toText function.
+        boost::multi_index::ordered_unique<
+            boost::multi_index::tag<SubnetPrefixIndexTag>,
+            boost::multi_index::const_mem_fun<Subnet, std::string, &Subnet::toText>
+        >
+    >
+> Subnet6Collection;
 
 //@}
 

+ 23 - 0
src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc

@@ -6,6 +6,10 @@
 
 #include <config.h>
 #include <dhcp/classify.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_space.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/shared_network.h>
 #include <dhcpsrv/cfg_subnets4.h>
@@ -856,4 +860,23 @@ TEST(CfgSubnets4Test, getSubnet) {
     EXPECT_EQ(Subnet4Ptr(), cfg.getSubnet(400)); // no such subnet
 }
 
+// This test verifies that hasSubnetWithServerId returns correct value.
+TEST(CfgSubnets4Test, hasSubnetWithServerId) {
+    CfgSubnets4 cfg;
+
+    // Initially, there is no server identifier option present.
+    EXPECT_FALSE(cfg.hasSubnetWithServerId(IOAddress("1.2.3.4")));
+
+    OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+                                                    DHO_DHCP_SERVER_IDENTIFIER);
+    OptionCustomPtr opt_server_id(new OptionCustom(*def, Option::V4));
+    opt_server_id->writeAddress(IOAddress("1.2.3.4"));
+    Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3, 100));
+    subnet->getCfgOption()->add(opt_server_id, false, DHCP4_OPTION_SPACE);
+    cfg.add(subnet);
+
+    EXPECT_TRUE(cfg.hasSubnetWithServerId(IOAddress("1.2.3.4")));
+    EXPECT_FALSE(cfg.hasSubnetWithServerId(IOAddress("2.3.4.5")));
+}
+
 } // end of anonymous namespace

+ 28 - 0
src/lib/dhcpsrv/tests/subnet_unittest.cc

@@ -7,13 +7,19 @@
 #include <config.h>
 
 #include <asiolink/io_address.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
 #include <dhcp/option.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_definition.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/option_space.h>
 #include <dhcpsrv/shared_network.h>
 #include <dhcpsrv/subnet.h>
 #include <exceptions/exceptions.h>
 
+#include <boost/pointer_cast.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 #include <limits>
@@ -497,6 +503,28 @@ TEST(Subnet4Test, PoolType) {
     EXPECT_THROW(subnet->addPool(pool5), BadValue);
 }
 
+// Tests if correct value of server identifier is returned when getServerId is
+// called.
+TEST(Subnet4Test, getServerId) {
+    // Initially, the subnet has no server identifier.
+    Subnet4 subnet(IOAddress("192.2.0.0"), 16, 1, 2, 3);
+    EXPECT_TRUE(subnet.getServerId().isV4Zero());
+
+    // Add server identifier.
+    OptionDefinitionPtr option_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+                                                         DHO_DHCP_SERVER_IDENTIFIER);
+    OptionCustomPtr option_server_id(new OptionCustom(*option_def, Option::V4));
+    option_server_id->writeAddress(IOAddress("1.2.3.4"));
+
+    CfgOptionPtr cfg_option = subnet.getCfgOption();
+    cfg_option->add(option_server_id, false, DHCP4_OPTION_SPACE);
+
+    // Verify that the server identifier returned by the Subnet4 object is
+    // correct.
+    OptionBuffer server_id_buf = { 1, 2, 3, 4 };
+    EXPECT_EQ("1.2.3.4", subnet.getServerId().toText());
+}
+
 // Tests for Subnet6
 
 TEST(Subnet6Test, constructor) {