Browse Source

[master] Merge branch 'trac3572'

Marcin Siodelski 8 years ago
parent
commit
49f67aaf36

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

@@ -924,13 +924,21 @@ void
 Dhcpv4Srv::buildCfgOptionList(Dhcpv4Exchange& ex) {
     CfgOptionList& co_list = ex.getCfgOptionList();
 
-    // First subnet configured options
+    // Retrieve subnet.
     Subnet4Ptr subnet = ex.getContext()->subnet_;
     if (!subnet) {
         // All methods using the CfgOptionList object return soon when
         // there is no subnet so do the same
         return;
     }
+
+    // Firstly, host specific options.
+    const ConstHostPtr& host = ex.getContext()->host_;
+    if (host && !host->getCfgOption4()->empty()) {
+        co_list.push_back(host->getCfgOption4());
+    }
+
+    // Secondly, subnet configured options.
     if (!subnet->getCfgOption()->empty()) {
         co_list.push_back(subnet->getCfgOption());
     }

+ 1 - 0
src/bin/dhcp4/tests/Makefile.am

@@ -89,6 +89,7 @@ dhcp4_unittests_SOURCES += dhcp4_client.cc dhcp4_client.h
 dhcp4_unittests_SOURCES += hooks_unittest.cc
 dhcp4_unittests_SOURCES += inform_unittest.cc
 dhcp4_unittests_SOURCES += dora_unittest.cc
+dhcp4_unittests_SOURCES += host_options_unittest.cc
 dhcp4_unittests_SOURCES += release_unittest.cc
 dhcp4_unittests_SOURCES += out_of_range_unittest.cc
 dhcp4_unittests_SOURCES += decline_unittest.cc

+ 13 - 2
src/bin/dhcp4/tests/dhcp4_client.cc

@@ -8,6 +8,7 @@
 #include <dhcp/dhcp4.h>
 #include <dhcp/option.h>
 #include <dhcp/option_int_array.h>
+#include <dhcp/option_vendor.h>
 #include <dhcpsrv/lease.h>
 #include <dhcp4/tests/dhcp4_client.h>
 #include <util/range_utilities.h>
@@ -163,13 +164,19 @@ Dhcp4Client::applyConfiguration() {
     Option4AddrLstPtr opt_log_servers = boost::dynamic_pointer_cast<
         Option4AddrLst>(resp->getOption(DHO_LOG_SERVERS));
     if (opt_log_servers) {
-        config_.log_servers_ = opt_routers->getAddresses();
+        config_.log_servers_ = opt_log_servers->getAddresses();
     }
     // Quotes Servers
     Option4AddrLstPtr opt_quotes_servers = boost::dynamic_pointer_cast<
         Option4AddrLst>(resp->getOption(DHO_COOKIE_SERVERS));
     if (opt_quotes_servers) {
-        config_.quotes_servers_ = opt_dns_servers->getAddresses();
+        config_.quotes_servers_ = opt_quotes_servers->getAddresses();
+    }
+    // Vendor Specific options
+    OptionVendorPtr opt_vendor = boost::dynamic_pointer_cast<
+        OptionVendor>(resp->getOption(DHO_VIVSO_SUBOPTIONS));
+    if (opt_vendor) {
+        config_.vendor_suboptions_ = opt_vendor->getOptions();
     }
     // Server Identifier
     OptionCustomPtr opt_serverid = boost::dynamic_pointer_cast<
@@ -254,6 +261,8 @@ Dhcp4Client::doInform(const bool set_ciaddr) {
     context_.query_ = createMsg(DHCPINFORM);
     // Request options if any.
     appendPRL();
+    // Any other options to be sent by a client.
+    appendExtraOptions();
     // The client sending a DHCPINFORM message has an IP address obtained
     // by some other means, e.g. static configuration. The lease which we
     // are using here is most likely set by the createLease method.
@@ -368,6 +377,8 @@ Dhcp4Client::doRequest() {
     appendName();
     // Include Client Identifier
     appendClientId();
+    // Any other options to be sent by a client.
+    appendExtraOptions();
     // Send the message to the server.
     sendMsg(context_.query_);
     // Expect response.

+ 3 - 0
src/bin/dhcp4/tests/dhcp4_client.h

@@ -9,6 +9,7 @@
 
 #include <asiolink/io_address.h>
 #include <dhcp/hwaddr.h>
+#include <dhcp/option.h>
 #include <dhcp/pkt4.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <util/optional_value.h>
@@ -73,6 +74,8 @@ public:
         Option4AddrLst::AddressContainer log_servers_;
         /// @brief Holds IP addresses received in the Quotes Servers option.
         Option4AddrLst::AddressContainer quotes_servers_;
+        /// @brief Vendor Specific options
+        OptionCollection vendor_suboptions_;
         /// @brief Holds a lease obtained by the client.
         Lease4 lease_;
         /// @brief Holds server id of the server which responded to the client's

+ 555 - 0
src/bin/dhcp4/tests/host_options_unittest.cc

@@ -0,0 +1,555 @@
+// Copyright (C) 2016 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 <asiolink/io_address.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/tests/dhcp4_client.h>
+#include <stats/stats_mgr.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Boolean value used to signal stateless configuration test.
+const bool STATELESS = true;
+
+/// @brief Boolean value used to signal stateful configuration test.
+const bool STATEFUL = false;
+
+/// @brief Set of JSON configurations used throughout the tests.
+///
+/// - Configuration 0:
+///   - Used to test that host specific options override subnet specific
+///     options when these options are requested with PRL option.
+///   - Single subnet 10.0.0.0/24 with a pool of 10.0.0.10-10.0.0.100
+///   - 4 options configured within subnet scope
+///     - routers: 10.0.0.200,10.0.0.201,
+///     - domain-name-servers: 10.0.0.202,10.0.0.203,
+///     - log-servers: 10.0.0.200,10.0.0.201,
+///     - cookie-servers: 10.0.0.202,10.0.0.203
+///   - Single reservation within the subnet:
+///     - HW address: aa:bb:cc:dd:ee:ff
+///     - ip-address: 10.0.0.7
+///     - Two options overriding subnet specific options:
+///       - cookie-servers: 10.1.1.202, 10.1.1.203
+///       - log-servers: 10.1.1.200, 10.1.1.201
+///
+/// - Configuration 1:
+///   - Used to test that host specific options override subnet specific
+///     default options. Default options are those that are sent even when
+///     not requested by a client.
+///   - Single subnet 10.0.0.0/24 with a pool of 10.0.0.10-10.0.0.100
+///   - 4 options configured within subnet scope
+///     - routers: 10.0.0.200,10.0.0.201,
+///     - domain-name-servers: 10.0.0.202,10.0.0.203,
+///     - log-servers: 10.0.0.200,10.0.0.201,
+///     - cookie-servers: 10.0.0.202,10.0.0.203
+///   - Single reservation within the subnet:
+///     - HW address: aa:bb:cc:dd:ee:ff
+///     - ip-address: 10.0.0.7
+///     - Two options overriding subnet specific default options:
+///       - routers: 10.1.1.200, 10.1.1.201
+///       - domain-name-servers: 10.1.1.202, 10.1.1.203
+///
+/// - Configuration 2:
+///   - Used to test that client receives options solely specified in a
+///     host scope.
+///   - Single reservation within the subnet:
+///     - HW address: aa:bb:cc:dd:ee:ff
+///     - ip-address: 10.0.0.7
+///     - Two options:
+///       - routers: 10.1.1.200, 10.1.1.201
+///       - cookie-servers: 10.1.1.202, 10.1.1.203
+///
+/// - Configuration 3:
+///   - Used to test that host specific vendor options override globally
+///     specified vendor options.
+///   - Globally specified option 125 with Cable Labs vendor id.
+///     - TFTP servers sub option: 10.0.0.202,10.0.0.203
+///   - Single subnet 10.0.0.0/24 with a pool of 10.0.0.10-10.0.0.100
+///   - Single reservation within the subnet:
+///     - HW address: aa:bb:cc:dd:ee:ff
+///     - ip-adress 10.0.0.7
+///     - Vendor option for Cable Labs vendor id specified for the reservation:
+///       - TFTP servers suboption overriding globally spececified suboption:
+///         10.1.1.202,10.1.1.203
+///
+const char* HOST_CONFIGS[] = {
+// Configuration 0
+    "{ \"interfaces-config\": {"
+        "      \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"valid-lifetime\": 600,"
+        "\"subnet4\": [ { "
+        "    \"subnet\": \"10.0.0.0/24\", "
+        "    \"id\": 1,"
+        "    \"relay\": { \"ip-address\": \"10.0.0.233\" },"
+        "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+        "    \"option-data\": [ {"
+        "        \"name\": \"routers\","
+        "        \"data\": \"10.0.0.200,10.0.0.201\""
+        "    },"
+        "    {"
+        "        \"name\": \"domain-name-servers\","
+        "        \"data\": \"10.0.0.202,10.0.0.203\""
+        "    },"
+        "    {"
+        "        \"name\": \"log-servers\","
+        "        \"data\": \"10.0.0.204,10.0.0.205\""
+        "    },"
+        "    {"
+        "        \"name\": \"cookie-servers\","
+        "        \"data\": \"10.0.0.206,10.0.0.207\""
+        "    } ],"
+        "    \"reservations\": [ "
+        "    {"
+        "        \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+        "        \"ip-address\": \"10.0.0.7\","
+        "        \"option-data\": [ {"
+        "            \"name\": \"cookie-servers\","
+        "            \"data\": \"10.1.1.202,10.1.1.203\""
+        "        },"
+        "        {"
+        "            \"name\": \"log-servers\","
+        "            \"data\": \"10.1.1.200,10.1.1.201\""
+        "        } ]"
+        "    } ]"
+        " } ]"
+    "}",
+
+// Configuration 1
+    "{ \"interfaces-config\": {"
+        "      \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"valid-lifetime\": 600,"
+        "\"subnet4\": [ { "
+        "    \"subnet\": \"10.0.0.0/24\", "
+        "    \"id\": 1,"
+        "    \"relay\": { \"ip-address\": \"10.0.0.233\" },"
+        "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+        "    \"option-data\": [ {"
+        "        \"name\": \"routers\","
+        "        \"data\": \"10.0.0.200,10.0.0.201\""
+        "    },"
+        "    {"
+        "        \"name\": \"domain-name-servers\","
+        "        \"data\": \"10.0.0.202,10.0.0.203\""
+        "    },"
+        "    {"
+        "        \"name\": \"log-servers\","
+        "        \"data\": \"10.0.0.204,10.0.0.205\""
+        "    },"
+        "    {"
+        "        \"name\": \"cookie-servers\","
+        "        \"data\": \"10.0.0.206,10.0.0.207\""
+        "    } ],"
+        "    \"reservations\": [ "
+        "    {"
+        "        \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+        "        \"ip-address\": \"10.0.0.7\","
+        "        \"option-data\": [ {"
+        "            \"name\": \"routers\","
+        "            \"data\": \"10.1.1.200,10.1.1.201\""
+        "        },"
+        "        {"
+        "            \"name\": \"domain-name-servers\","
+        "            \"data\": \"10.1.1.202,10.1.1.203\""
+        "        } ]"
+        "    } ]"
+        " } ]"
+    "}",
+
+// Configuration 2
+    "{ \"interfaces-config\": {"
+        "      \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"valid-lifetime\": 600,"
+        "\"subnet4\": [ { "
+        "    \"subnet\": \"10.0.0.0/24\", "
+        "    \"id\": 1,"
+        "    \"relay\": { \"ip-address\": \"10.0.0.233\" },"
+        "    \"reservations\": [ "
+        "    {"
+        "        \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+        "        \"ip-address\": \"10.0.0.7\","
+        "        \"option-data\": [ {"
+        "            \"name\": \"routers\","
+        "            \"data\": \"10.1.1.200,10.1.1.201\""
+        "        },"
+        "        {"
+        "            \"name\": \"cookie-servers\","
+        "            \"data\": \"10.1.1.206,10.1.1.207\""
+        "        } ]"
+        "    } ]"
+        " } ]"
+    "}",
+
+// Configuration 3
+    "{ \"interfaces-config\": {"
+        "      \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"valid-lifetime\": 600,"
+        "\"option-data\": [ {"
+        "    \"name\": \"vivso-suboptions\","
+        "    \"data\": 4491"
+        "},"
+        "{"
+        "    \"name\": \"tftp-servers\","
+        "    \"space\": \"vendor-4491\","
+        "    \"data\": \"10.0.0.202,10.0.0.203\""
+        "} ],"
+        "\"subnet4\": [ { "
+        "    \"subnet\": \"10.0.0.0/24\", "
+        "    \"id\": 1,"
+        "    \"relay\": { \"ip-address\": \"10.0.0.233\" },"
+        "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+        "    \"reservations\": [ "
+        "    {"
+        "        \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+        "        \"ip-address\": \"10.0.0.7\","
+        "        \"option-data\": [ {"
+        "            \"name\": \"vivso-suboptions\","
+        "            \"data\": 4491"
+        "        },"
+        "        {"
+        "            \"name\": \"tftp-servers\","
+        "            \"space\": \"vendor-4491\","
+        "            \"data\": \"10.1.1.202,10.1.1.203\""
+        "        } ]"
+        "    } ]"
+        " } ]"
+    "}"
+};
+
+/// @brief Test fixture class for testing static reservations of options.
+class HostOptionsTest : public Dhcpv4SrvTest {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Sets up fake interfaces.
+    HostOptionsTest()
+        : Dhcpv4SrvTest(),
+          iface_mgr_test_config_(true) {
+        IfaceMgr::instance().openSockets4();
+    }
+
+    /// @brief Verifies that host specific options override subnet specific
+    /// options.
+    ///
+    /// Overridden options are requested with Parameter Request List
+    /// option.
+    ///
+    /// @param stateless Boolean value indicating if statless or stateful
+    /// configuration should be performed.
+    void testOverrideRequestedOptions(const bool stateless);
+
+    /// @brief Verifies that host specific options override subnet specific
+    /// options.
+    ///
+    /// Overridden options are the options which server sends regardless
+    /// if they are requested with Parameter Request List option or not.
+    ///
+    /// @param stateless Boolean value indicating if statless or stateful
+    /// configuration should be performed.
+    void testOverrideDefaultOptions(const bool stateless);
+
+    /// @brief Verifies that client receives options when they are solely
+    /// defined in the host scope (and not in the global or subnet scope).
+    ///
+    /// @param stateless Boolean value indicating if statless or stateful
+    /// configuration should be performed.
+    void testHostOnlyOptions(const bool stateless);
+
+    /// @brief Verifies that host specific vendor options override vendor
+    /// options defined in the global scope.
+    ///
+    /// @param stateless Boolean value indicating if statless or stateful
+    /// configuration should be performed.
+    void testOverrideVendorOptions(const bool stateless);
+
+    /// @brief Interface Manager's fake configuration control.
+    IfaceMgrTestConfig iface_mgr_test_config_;
+
+};
+
+void
+HostOptionsTest::testOverrideRequestedOptions(const bool stateless) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    client.setHWAddress("aa:bb:cc:dd:ee:ff");
+    client.requestOptions(DHO_DOMAIN_NAME_SERVERS, DHO_LOG_SERVERS,
+                          DHO_COOKIE_SERVERS);
+
+    // Configure DHCP server.
+    configure(HOST_CONFIGS[0], *client.getServer());
+
+    if (stateless) {
+        // Need to relay the message from a specific address which can
+        // be matched with a configured subnet.
+        client.useRelay(true, IOAddress("10.0.0.233"));
+        ASSERT_NO_THROW(client.doInform());
+
+    } else {
+        // Perform 4-way exchange with the server but to not request any
+        // specific address in the DHCPDISCOVER message.
+        ASSERT_NO_THROW(client.doDORA());
+    }
+
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    Pkt4Ptr resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+    if (!stateless) {
+        // Make sure that the client has got the lease for the reserved
+        // address.
+        ASSERT_EQ("10.0.0.7", client.config_.lease_.addr_.toText());
+    }
+
+    ASSERT_EQ(2, client.config_.routers_.size());
+    EXPECT_EQ("10.0.0.200", client.config_.routers_[0].toText());
+    EXPECT_EQ("10.0.0.201", client.config_.routers_[1].toText());
+    // Make sure that the DNS Servers option has been received.
+    ASSERT_EQ(2, client.config_.dns_servers_.size());
+    EXPECT_EQ("10.0.0.202", client.config_.dns_servers_[0].toText());
+    EXPECT_EQ("10.0.0.203", client.config_.dns_servers_[1].toText());
+    // Make sure that the Quotes Servers option has been received.
+    ASSERT_EQ(2, client.config_.quotes_servers_.size());
+    EXPECT_EQ("10.1.1.202", client.config_.quotes_servers_[0].toText());
+    EXPECT_EQ("10.1.1.203", client.config_.quotes_servers_[1].toText());
+    // Make sure that the Log Servers option has been received.
+    ASSERT_EQ(2, client.config_.log_servers_.size());
+    EXPECT_EQ("10.1.1.200", client.config_.log_servers_[0].toText());
+    EXPECT_EQ("10.1.1.201", client.config_.log_servers_[1].toText());
+}
+
+void
+HostOptionsTest::testOverrideDefaultOptions(const bool stateless) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    client.setHWAddress("aa:bb:cc:dd:ee:ff");
+
+    client.requestOptions(DHO_LOG_SERVERS, DHO_COOKIE_SERVERS);
+
+    // Configure DHCP server.
+    configure(HOST_CONFIGS[1], *client.getServer());
+
+    if (stateless) {
+        // Need to relay the message from a specific address which can
+        // be matched with a configured subnet.
+        client.useRelay(true, IOAddress("10.0.0.233"));
+        ASSERT_NO_THROW(client.doInform());
+
+    } else {
+        // Perform 4-way exchange with the server but to not request any
+        // specific address in the DHCPDISCOVER message.
+        ASSERT_NO_THROW(client.doDORA());
+    }
+
+    // Perform 4-way exchange with the server but to not request any
+    // specific address in the DHCPDISCOVER message.
+    ASSERT_NO_THROW(client.doDORA());
+
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    Pkt4Ptr resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+    if (!stateless) {
+        // Make sure that the client has got the lease for the reserved
+        // address.
+        ASSERT_EQ("10.0.0.7", client.config_.lease_.addr_.toText());
+    }
+
+    ASSERT_EQ(2, client.config_.routers_.size());
+    EXPECT_EQ("10.1.1.200", client.config_.routers_[0].toText());
+    EXPECT_EQ("10.1.1.201", client.config_.routers_[1].toText());
+    // Make sure that the DNS Servers option has been received.
+    ASSERT_EQ(2, client.config_.dns_servers_.size());
+    EXPECT_EQ("10.1.1.202", client.config_.dns_servers_[0].toText());
+    EXPECT_EQ("10.1.1.203", client.config_.dns_servers_[1].toText());
+    // Make sure that the Quotes Servers option has been received.
+    ASSERT_EQ(2, client.config_.quotes_servers_.size());
+    EXPECT_EQ("10.0.0.206", client.config_.quotes_servers_[0].toText());
+    EXPECT_EQ("10.0.0.207", client.config_.quotes_servers_[1].toText());
+    // Make sure that the Log Servers option has been received.
+    ASSERT_EQ(2, client.config_.log_servers_.size());
+    EXPECT_EQ("10.0.0.204", client.config_.log_servers_[0].toText());
+    EXPECT_EQ("10.0.0.205", client.config_.log_servers_[1].toText());
+}
+
+void
+HostOptionsTest::testHostOnlyOptions(const bool stateless) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    client.setHWAddress("aa:bb:cc:dd:ee:ff");
+    client.requestOptions(DHO_COOKIE_SERVERS);
+
+    // Configure DHCP server.
+    configure(HOST_CONFIGS[2], *client.getServer());
+
+    if (stateless) {
+        // Need to relay the message from a specific address which can
+        // be matched with a configured subnet.
+        client.useRelay(true, IOAddress("10.0.0.233"));
+        ASSERT_NO_THROW(client.doInform());
+
+    } else {
+        // Perform 4-way exchange with the server but to not request any
+        // specific address in the DHCPDISCOVER message.
+        ASSERT_NO_THROW(client.doDORA());
+    }
+
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    Pkt4Ptr resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+    if (!stateless) {
+        // Make sure that the client has got the lease for the reserved
+        // address.
+        ASSERT_EQ("10.0.0.7", client.config_.lease_.addr_.toText());
+    }
+
+    // Make sure that the Routers options has been received.
+    ASSERT_EQ(2, client.config_.routers_.size());
+    EXPECT_EQ("10.1.1.200", client.config_.routers_[0].toText());
+    EXPECT_EQ("10.1.1.201", client.config_.routers_[1].toText());
+    // Make sure that the Quotes Servers option has been received.
+    ASSERT_EQ(2, client.config_.quotes_servers_.size());
+    EXPECT_EQ("10.1.1.206", client.config_.quotes_servers_[0].toText());
+    EXPECT_EQ("10.1.1.207", client.config_.quotes_servers_[1].toText());
+
+    // Other options are not configured and should not be delivered.
+    EXPECT_EQ(0, client.config_.dns_servers_.size());
+    EXPECT_EQ(0, client.config_.log_servers_.size());
+}
+
+void
+HostOptionsTest::testOverrideVendorOptions(const bool stateless) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    client.setHWAddress("aa:bb:cc:dd:ee:ff");
+
+    // Client needs to include V-I Vendor Specific Information option
+    // to include ORO suboption, which the server will use to determine
+    // which suboptions should be returned to the client.
+    OptionVendorPtr opt_vendor(new OptionVendor(Option::V4,
+                                                VENDOR_ID_CABLE_LABS));
+    // Include ORO with TFTP servers suboption code being requested.
+    opt_vendor->addOption(OptionPtr(new OptionUint8(Option::V4, DOCSIS3_V4_ORO,
+                                                    DOCSIS3_V4_TFTP_SERVERS)));
+    client.addExtraOption(opt_vendor);
+
+    // Configure DHCP server.
+    configure(HOST_CONFIGS[3], *client.getServer());
+
+    if (stateless) {
+        // Need to relay the message from a specific address which can
+        // be matched with a configured subnet.
+        client.useRelay(true, IOAddress("10.0.0.233"));
+        ASSERT_NO_THROW(client.doInform());
+
+    } else {
+        // Perform 4-way exchange with the server.
+        ASSERT_NO_THROW(client.doDORA());
+    }
+
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    Pkt4Ptr resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+    if (!stateless) {
+        // Make sure that the client has got the lease for the reserved
+        // address.
+        ASSERT_EQ("10.0.0.7", client.config_.lease_.addr_.toText());
+    }
+
+    // Make sure the server has responded with a V-I Vendor Specific
+    // Information option with exactly one suboption.
+    ASSERT_EQ(1, client.config_.vendor_suboptions_.size());
+    // Assume this suboption is a TFTP servers suboption.
+    std::multimap<unsigned int, OptionPtr>::const_iterator opt =
+        client.config_.vendor_suboptions_.find(DOCSIS3_V4_TFTP_SERVERS);
+    ASSERT_TRUE(opt->second);
+    Option4AddrLstPtr opt_tftp = boost::dynamic_pointer_cast<
+        Option4AddrLst>(opt->second);
+    ASSERT_TRUE(opt_tftp);
+    // TFTP servers suboption should contain addresses specified on host level.
+    const Option4AddrLst::AddressContainer& tftps = opt_tftp->getAddresses();
+    ASSERT_EQ(2, tftps.size());
+    EXPECT_EQ("10.1.1.202", tftps[0].toText());
+    EXPECT_EQ("10.1.1.203", tftps[1].toText());
+}
+
+// This test checks that host specific options override subnet specific
+// options. Overridden options are requested with Parameter Request List
+// option (stateless case).
+TEST_F(HostOptionsTest, overrideRequestedOptionsStateless) {
+    testOverrideRequestedOptions(STATELESS);
+}
+
+// This test checks that host specific options override subnet specific
+// options. Overridden options are requested with Parameter Request List
+// option (stateful case).
+TEST_F(HostOptionsTest, overrideRequestedOptionsStateful) {
+    testOverrideRequestedOptions(STATEFUL);
+}
+
+// This test checks that host specific options override subnet specific
+// options. Overridden options are the options which server sends
+// regardless if they are requested with Parameter Request List option
+// or not (stateless case).
+TEST_F(HostOptionsTest, overrideDefaultOptionsStateless) {
+    testOverrideDefaultOptions(STATELESS);
+}
+
+// This test checks that host specific options override subnet specific
+// options. Overridden options are the options which server sends
+// regardless if they are requested with Parameter Request List option
+// or not (stateful case).
+TEST_F(HostOptionsTest, overrideDefaultOptionsStateful) {
+    testOverrideDefaultOptions(STATEFUL);
+}
+
+// This test checks that client receives options when they are
+// solely defined in the host scope and not in the global or subnet
+// scope (stateless case).
+TEST_F(HostOptionsTest, hostOnlyOptionsStateless) {
+    testHostOnlyOptions(STATELESS);
+}
+
+// This test checks that client receives options when they are
+// solely defined in the host scope and not in the global or subnet
+// scope (stateful case).
+TEST_F(HostOptionsTest, hostOnlyOptionsStateful) {
+    testHostOnlyOptions(STATEFUL);
+}
+
+// This test checks that host specific vendor options override vendor
+// options defined in the global scope (stateless case).
+TEST_F(HostOptionsTest, overrideVendorOptionsStateless) {
+    testOverrideVendorOptions(STATELESS);
+}
+
+// This test checks that host specific vendor options override vendor
+// options defined in the global scope (stateful case).
+TEST_F(HostOptionsTest, overrideVendorOptionsStateful) {
+    testOverrideVendorOptions(STATEFUL);
+}
+
+} // end of anonymous namespace

+ 13 - 13
src/bin/dhcp4/tests/inform_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 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
@@ -58,11 +58,11 @@ const char* INFORM_CONFIGS[] = {
         "    },"
         "    {"
         "        \"name\": \"log-servers\","
-        "        \"data\": \"10.0.0.200,10.0.0.201\""
+        "        \"data\": \"10.0.0.202,10.0.0.203\""
         "    },"
         "    {"
         "        \"name\": \"cookie-servers\","
-        "        \"data\": \"10.0.0.202,10.0.0.203\""
+        "        \"data\": \"10.0.0.200,10.0.0.201\""
         "    } ]"
         " } ]"
     "}",
@@ -159,12 +159,12 @@ TEST_F(InformTest, directClientBroadcast) {
     EXPECT_EQ("10.0.0.203", client.config_.dns_servers_[1].toText());
     // Make sure that the Log Servers option has been received.
     ASSERT_EQ(2, client.config_.quotes_servers_.size());
-    EXPECT_EQ("10.0.0.200", client.config_.routers_[0].toText());
-    EXPECT_EQ("10.0.0.201", client.config_.routers_[1].toText());
+    EXPECT_EQ("10.0.0.200", client.config_.quotes_servers_[0].toText());
+    EXPECT_EQ("10.0.0.201", client.config_.quotes_servers_[1].toText());
     // Make sure that the Quotes Servers option has been received.
     ASSERT_EQ(2, client.config_.log_servers_.size());
-    EXPECT_EQ("10.0.0.202", client.config_.dns_servers_[0].toText());
-    EXPECT_EQ("10.0.0.203", client.config_.dns_servers_[1].toText());
+    EXPECT_EQ("10.0.0.202", client.config_.log_servers_[0].toText());
+    EXPECT_EQ("10.0.0.203", client.config_.log_servers_[1].toText());
 
     // Check that we can send another DHCPINFORM message using
     // different ciaddr and we will get the configuration.
@@ -313,14 +313,14 @@ TEST_F(InformTest, relayedClient) {
     ASSERT_EQ(2, client.config_.dns_servers_.size());
     EXPECT_EQ("192.0.2.202", client.config_.dns_servers_[0].toText());
     EXPECT_EQ("192.0.2.203", client.config_.dns_servers_[1].toText());
-    // Make sure that the Log Servers option has been received.
-    ASSERT_EQ(2, client.config_.quotes_servers_.size());
-    EXPECT_EQ("192.0.2.200", client.config_.routers_[0].toText());
-    EXPECT_EQ("192.0.2.201", client.config_.routers_[1].toText());
     // Make sure that the Quotes Servers option has been received.
+    ASSERT_EQ(2, client.config_.quotes_servers_.size());
+    EXPECT_EQ("10.0.0.202", client.config_.quotes_servers_[0].toText());
+    EXPECT_EQ("10.0.0.203", client.config_.quotes_servers_[1].toText());
+    // Make sure that the Log Servers option has been received.
     ASSERT_EQ(2, client.config_.log_servers_.size());
-    EXPECT_EQ("192.0.2.202", client.config_.dns_servers_[0].toText());
-    EXPECT_EQ("192.0.2.203", client.config_.dns_servers_[1].toText());
+    EXPECT_EQ("10.0.0.200", client.config_.log_servers_[0].toText());
+    EXPECT_EQ("10.0.0.201", client.config_.log_servers_[1].toText());
 }
 
 // This test checks that the server can respond to the DHCPINFORM message

+ 13 - 1
src/lib/dhcp/std_option_defs.h

@@ -211,7 +211,19 @@ const OptionDefParams OPTION_DEF_PARAMS4[] = {
     { "domain-search", DHO_DOMAIN_SEARCH, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
     { "vivco-suboptions", DHO_VIVCO_SUBOPTIONS, OPT_RECORD_TYPE,
       false, RECORD_DEF(VIVCO_RECORDS), "" },
-    { "vivso-suboptions", DHO_VIVSO_SUBOPTIONS, OPT_BINARY_TYPE,
+    // Vendor-Identifying Vendor Specific Information option payload begins with a
+    // 32-bit log enterprise number, followed by a tuple of data-len/option-data.
+    // The format defined here includes 32-bit field holding enterprise number.
+    // This allows for specifying option-data information where the enterprise-id
+    // is represented by a uint32_t value. Previously we represented this option
+    // as a binary, but that would imply that enterprise number would have to be
+    // represented in binary format in the server configuration. That would be
+    // inconvenient and non-intuitive.
+    /// @todo We need to extend support for vendor options with ability to specify
+    /// multiple enterprise numbers for a single option. Perhaps it would be
+    /// ok to specify multiple instances of the "vivso-suboptions" which will be
+    /// combined in a single option by the server before responding to a client.
+    { "vivso-suboptions", DHO_VIVSO_SUBOPTIONS, OPT_UINT32_TYPE,
       false, NO_RECORD_DEF, "" }
 
         // @todo add definitions for all remaning options.