Browse Source

[4523] Added DOCSIS vendor ORO unit tests for renew and rebind

Francis Dupont 8 years ago
parent
commit
cfe22c8dd7

+ 15 - 2
src/bin/dhcp6/tests/dhcp6_client.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
 // 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
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -6,11 +6,13 @@
 
 
 #include <config.h>
 #include <config.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/dhcp6.h>
+#include <dhcp/docsis3_option_defs.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_status_code.h>
 #include <dhcp/option6_status_code.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_int_array.h>
+#include <dhcp/option_vendor.h>
 #include <dhcp/pkt6.h>
 #include <dhcp/pkt6.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease.h>
 #include <dhcp6/tests/dhcp6_client.h>
 #include <dhcp6/tests/dhcp6_client.h>
@@ -53,7 +55,7 @@ struct getLeasesByPropertyFun {
         for (typename std::vector<Lease6>::const_iterator lease =
         for (typename std::vector<Lease6>::const_iterator lease =
                  config.leases_.begin(); lease != config.leases_.end();
                  config.leases_.begin(); lease != config.leases_.end();
              ++lease) {
              ++lease) {
-            // Check if fulfils the condition.
+            // Check if fulfills the condition.
             if ((equals && ((*lease).*MemberPointer) == property) ||
             if ((equals && ((*lease).*MemberPointer) == property) ||
                 (!equals && ((*lease).*MemberPointer) != property)) {
                 (!equals && ((*lease).*MemberPointer) != property)) {
                 // Found the matching lease.
                 // Found the matching lease.
@@ -81,6 +83,7 @@ Dhcp6Client::Dhcp6Client() :
     use_pd_(false),
     use_pd_(false),
     use_relay_(false),
     use_relay_(false),
     use_oro_(false),
     use_oro_(false),
+    use_docsis_oro_(false),
     use_client_id_(true),
     use_client_id_(true),
     use_rapid_commit_(false),
     use_rapid_commit_(false),
     address_hint_(),
     address_hint_(),
@@ -103,6 +106,7 @@ Dhcp6Client::Dhcp6Client(boost::shared_ptr<NakedDhcpv6Srv>& srv) :
     use_pd_(false),
     use_pd_(false),
     use_relay_(false),
     use_relay_(false),
     use_oro_(false),
     use_oro_(false),
+    use_docsis_oro_(false),
     use_client_id_(true),
     use_client_id_(true),
     use_rapid_commit_(false),
     use_rapid_commit_(false),
     address_hint_(),
     address_hint_(),
@@ -369,6 +373,15 @@ Dhcp6Client::createMsg(const uint8_t msg_type) {
         msg->addOption(oro);
         msg->addOption(oro);
     };
     };
 
 
+    if (use_docsis_oro_) {
+        OptionUint16ArrayPtr vendor_oro(new OptionUint16Array(Option::V6,
+                                                              DOCSIS3_V6_ORO));
+        vendor_oro->setValues(docsis_oro_);
+        OptionPtr vendor(new OptionVendor(Option::V6, 4491));
+        vendor->addOption(vendor_oro);
+        msg->addOption(vendor);
+    }
+
     // If there are any custom options specified, add them all to the message.
     // If there are any custom options specified, add them all to the message.
     if (!extra_options_.empty()) {
     if (!extra_options_.empty()) {
         for (OptionCollection::iterator opt = extra_options_.begin();
         for (OptionCollection::iterator opt = extra_options_.begin();

+ 28 - 2
src/bin/dhcp6/tests/dhcp6_client.h

@@ -24,7 +24,7 @@ namespace test {
 /// @brief DHCPv6 client used for unit testing.
 /// @brief DHCPv6 client used for unit testing.
 ///
 ///
 /// This class implements a DHCPv6 "client" which interoperates with the
 /// This class implements a DHCPv6 "client" which interoperates with the
-/// @c NakedDhcpv6Srv class. It calls @c NakedDhcpv6Srv::fakeRecive to
+/// @c NakedDhcpv6Srv class. It calls @c NakedDhcpv6Srv::fakeReceive to
 /// deliver client messages to the server for processing. The server places
 /// deliver client messages to the server for processing. The server places
 /// the response in the @c NakedDhcpv6Srv::fake_sent_ container. The client
 /// the response in the @c NakedDhcpv6Srv::fake_sent_ container. The client
 /// pops messages from this container which simulates reception of the
 /// pops messages from this container which simulates reception of the
@@ -499,7 +499,7 @@ public:
         use_client_id_ = send;
         use_client_id_ = send;
     }
     }
 
 
-    /// @brief Controls whether the client should send an addres in IA_NA
+    /// @brief Controls whether the client should send an address in IA_NA
     ///
     ///
     /// @todo: For now, this flag is only used in Decline
     /// @todo: For now, this flag is only used in Decline
     /// @param send should the address be included?
     /// @param send should the address be included?
@@ -566,6 +566,25 @@ public:
         oro_.push_back(option_code);
         oro_.push_back(option_code);
     }
     }
 
 
+    /// @brief Controls whether the client will send DOCSIS vendor ORO
+    ///
+    /// The actual content of the ORO is specified in docsis_oro_.
+    /// It is useful to split the actual content and the ORO sending
+    /// decision, so we could test cases of sending empty ORO.
+    /// @param send controls whether ORO will be sent or not.
+    void useDocsisORO(bool send) {
+        use_docsis_oro_ = send;
+    }
+
+    /// @brief Instructs client to request specified option in DOCSIS
+    /// vendor ORO
+    ///
+    /// @param option_code client will request this option code
+    void requestDocsisOption(uint16_t option_code) {
+        use_docsis_oro_ = true;
+        docsis_oro_.push_back(option_code);
+    }
+
     /// @brief returns client-id
     /// @brief returns client-id
     /// @return client-id
     /// @return client-id
     DuidPtr getDuid() const {
     DuidPtr getDuid() const {
@@ -738,6 +757,7 @@ private:
     bool use_relay_; ///< Enable relaying messages to the server.
     bool use_relay_; ///< Enable relaying messages to the server.
 
 
     bool use_oro_;  ///< Conth
     bool use_oro_;  ///< Conth
+    bool use_docsis_oro_;
     bool use_client_id_;
     bool use_client_id_;
     bool use_rapid_commit_;
     bool use_rapid_commit_;
 
 
@@ -753,6 +773,12 @@ private:
     /// to true. See @ref sendORO for details.
     /// to true. See @ref sendORO for details.
     std::vector<uint16_t> oro_;
     std::vector<uint16_t> oro_;
 
 
+    /// @brief List of DOCSIS vendor options to be requested
+    ///
+    /// Content of this vector will be sent as DOCSIS vendor ORO if
+    /// use_docsis_oro_ is set to true. See @ref sendDocsisORO for details.
+    std::vector<uint16_t> docsis_oro_;
+
     /// @brief forced (Overridden) value of the server-id option (may be NULL)
     /// @brief forced (Overridden) value of the server-id option (may be NULL)
     OptionPtr forced_server_id_;
     OptionPtr forced_server_id_;
 
 

+ 5 - 5
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -240,7 +240,7 @@ TEST_F(Dhcpv6SrvTest, basic) {
     });
     });
     srv.reset();
     srv.reset();
     ASSERT_NO_THROW({
     ASSERT_NO_THROW({
-        // open an unpriviledged port
+        // open an unprivileged port
         srv.reset(new NakedDhcpv6Srv(DHCP6_SERVER_PORT + 10000));
         srv.reset(new NakedDhcpv6Srv(DHCP6_SERVER_PORT + 10000));
     });
     });
 }
 }
@@ -1631,7 +1631,7 @@ TEST_F(Dhcpv6SrvTest, vendorOptionsORO) {
     // check if we get response at all
     // check if we get response at all
     ASSERT_TRUE(adv);
     ASSERT_TRUE(adv);
 
 
-    // We did not include any vendor opts in SOLCIT, so there should be none
+    // We did not include any vendor opts in SOLICIT, so there should be none
     // in ADVERTISE.
     // in ADVERTISE.
     ASSERT_FALSE(adv->getOption(D6O_VENDOR_OPTS));
     ASSERT_FALSE(adv->getOption(D6O_VENDOR_OPTS));
 
 
@@ -2449,8 +2449,8 @@ TEST_F(Dhcpv6SrvTest, rsoo2relays) {
     opt = createRSOO(rsoo2, 2); // use 0x2 as payload
     opt = createRSOO(rsoo2, 2); // use 0x2 as payload
     relay2.options_.insert(make_pair(opt->getType(), opt));
     relay2.options_.insert(make_pair(opt->getType(), opt));
 
 
-    // The relays ecapsulate packet in this order: relay1, relay2, but the server
-    // decapsulates the packet in reverse order.
+    // The relays encapsulate packet in this order: relay1, relay2,
+    // but the server decapsulates the packet in reverse order.
     client.relay_info_.push_back(relay2);
     client.relay_info_.push_back(relay2);
     client.relay_info_.push_back(relay1);
     client.relay_info_.push_back(relay1);
 
 
@@ -2639,7 +2639,7 @@ TEST_F(Dhcpv6SrvTest, receiveParseFailedStat) {
     // fakeReceive()
     // fakeReceive()
     srv.run();
     srv.run();
 
 
-    // All expected statstics must be present.
+    // All expected statistics must be present.
     pkt6_rcvd = mgr.getObservation("pkt6-received");
     pkt6_rcvd = mgr.getObservation("pkt6-received");
     parse_fail = mgr.getObservation("pkt6-parse-failed");
     parse_fail = mgr.getObservation("pkt6-parse-failed");
     recv_drop = mgr.getObservation("pkt6-receive-drop");
     recv_drop = mgr.getObservation("pkt6-receive-drop");

+ 114 - 0
src/bin/dhcp6/tests/rebind_unittest.cc

@@ -7,6 +7,9 @@
 #include <config.h>
 #include <config.h>
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <cc/data.h>
 #include <cc/data.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp6/json_config_parser.h>
 #include <dhcp6/json_config_parser.h>
 #include <dhcp6/tests/dhcp6_message_test.h>
 #include <dhcp6/tests/dhcp6_message_test.h>
@@ -60,6 +63,12 @@ namespace {
 ///   - address pool: 2001:db8:1::/64
 ///   - address pool: 2001:db8:1::/64
 ///   - prefix pool: 3000::/72
 ///   - prefix pool: 3000::/72
 ///
 ///
+/// - Configuration 7:
+///   - only addresses (no prefixes)
+///   - 2 subnets with 2001:db8:1::/64 and 2001:db8:2::/64
+///   - 1 subnet for eth0 and 1 subnet for eth1
+///   - DOCSIS vendor config file sub-option
+///
 const char* REBIND_CONFIGS[] = {
 const char* REBIND_CONFIGS[] = {
 // Configuration 0
 // Configuration 0
     "{ \"interfaces-config\": {"
     "{ \"interfaces-config\": {"
@@ -221,6 +230,38 @@ const char* REBIND_CONFIGS[] = {
         "    \"interface-id\": \"\","
         "    \"interface-id\": \"\","
         "    \"interface\": \"eth0\""
         "    \"interface\": \"eth0\""
         " } ],"
         " } ],"
+        "\"valid-lifetime\": 4000 }",
+
+// Configuration 0
+    "{ \"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "    \"option-def\": [ {"
+        "        \"name\": \"config-file\","
+        "        \"code\": 33,"
+        "        \"type\": \"string\","
+        "        \"space\": \"vendor-4491\""
+        "     } ],"
+        "    \"option-data\": [ {"
+        "          \"name\": \"config-file\","
+        "          \"space\": \"vendor-4491\","
+        "          \"data\": \"normal_erouter_v6.cm\""
+        "        }],"
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth0\""
+        " },"
+        " {"
+        "    \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ],"
+        "    \"subnet\": \"2001:db8:2::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth1\""
+        " } ],"
         "\"valid-lifetime\": 4000 }"
         "\"valid-lifetime\": 4000 }"
 };
 };
 
 
@@ -892,5 +933,78 @@ TEST_F(RebindTest, requestAddressInRebind) {
     EXPECT_EQ(STATUS_Success, client.getStatusCode(1234));
     EXPECT_EQ(STATUS_Success, client.getStatusCode(1234));
 }
 }
 
 
+// This test verifies that the client can request the DOCSIS sub-options.
+TEST_F(RebindTest, docsisORO) {
+    Dhcp6Client client;
+    // Configure client to request IA_NA.
+    client.useNA();
+    // Configure the DOCSIS vendor ORO for 32, 33, 34, 37 and 38.
+    client.requestDocsisOption(DOCSIS3_V6_TFTP_SERVERS);
+    client.requestDocsisOption(DOCSIS3_V6_CONFIG_FILE);
+    client.requestDocsisOption(DOCSIS3_V6_SYSLOG_SERVERS);
+    client.requestDocsisOption(DOCSIS3_V6_TIME_SERVERS);
+    client.requestDocsisOption(DOCSIS3_V6_TIME_OFFSET);
+    // Don't add it for now.
+    client.useDocsisORO(false);
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[7], 2, client));
+    // Keep the client's lease for future reference.
+    Lease6 lease_client = client.getLease(0);
+
+    // Send Rebind message to the server.
+    ASSERT_NO_THROW(client.doRebind());
+    // The client should still have one lease which belong to one of the
+    // subnets.
+    ASSERT_EQ(1, client.getLeaseNum());
+    Lease6 lease_client2 = client.getLease(0);
+    ASSERT_TRUE(CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->
+                selectSubnet(lease_client2.addr_, ClientClasses()));
+    // The client's lease should have been extended. The client will
+    // update the cltt to current time when the lease gets extended.
+    ASSERT_GE(lease_client2.cltt_ - lease_client.cltt_, 1000);
+    // Make sure, that the client's lease matches the lease held by the
+    // server.
+    Lease6Ptr lease_server2 = checkLease(lease_client2);
+    EXPECT_TRUE(lease_server2);
+    // No vendor option was included in the renew so there should be none
+    // in the received configuration.
+    OptionPtr opt = client.config_.findOption(D6O_VENDOR_OPTS);
+    ASSERT_FALSE(opt);
+
+    // Add a DOCSIS ORO.
+    client.useDocsisORO(true);
+    // Send Rebind message to the server.
+    ASSERT_NO_THROW(client.doRebind());
+    // The client should still have one lease which belong to one of the
+    // subnets.
+    ASSERT_EQ(1, client.getLeaseNum());
+    lease_client2 = client.getLease(0);
+    ASSERT_TRUE(CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->
+                selectSubnet(lease_client2.addr_, ClientClasses()));
+    // The client's lease should have been extended. The client will
+    // update the cltt to current time when the lease gets extended.
+    ASSERT_GE(lease_client2.cltt_ - lease_client.cltt_, 1000);
+    // Make sure, that the client's lease matches the lease held by the
+    // server.
+    lease_server2 = checkLease(lease_client2);
+    EXPECT_TRUE(lease_server2);
+
+    // Verify whether there is a vendor option.
+    opt = client.config_.findOption(D6O_VENDOR_OPTS);
+    ASSERT_TRUE(opt);
+    // The vendor option must be a OptionVentor object.
+    boost::shared_ptr<OptionVendor> vendor =
+        boost::dynamic_pointer_cast<OptionVendor>(opt);
+    ASSERT_TRUE(vendor);
+    // The vendor-id should be DOCSIS.
+    EXPECT_EQ(VENDOR_ID_CABLE_LABS, vendor->getVendorId());
+    // There must be a config file sub-option.
+    opt = vendor->getOption(DOCSIS3_V6_CONFIG_FILE);
+    // With the expected content.
+    OptionStringPtr config_file =
+        boost::dynamic_pointer_cast<OptionString>(opt);
+    ASSERT_TRUE(opt);
+    EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
+}
 
 
 } // end of anonymous namespace
 } // end of anonymous namespace

+ 111 - 1
src/bin/dhcp6/tests/renew_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // 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
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,9 +7,13 @@
 #include <config.h>
 #include <config.h>
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <cc/data.h>
 #include <cc/data.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp6/json_config_parser.h>
 #include <dhcp6/json_config_parser.h>
 #include <dhcp6/tests/dhcp6_message_test.h>
 #include <dhcp6/tests/dhcp6_message_test.h>
+#include <boost/pointer_cast.hpp>
 
 
 using namespace isc;
 using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::asiolink;
@@ -35,6 +39,11 @@ namespace {
 ///   - address pool: 2001:db8:1::/64
 ///   - address pool: 2001:db8:1::/64
 ///   - prefix pool: 3000::/72
 ///   - prefix pool: 3000::/72
 ///
 ///
+/// - Configuration 3:
+///   - only addresses (no prefixes)
+///   - 1 subnet with 2001:db8:1::/64 pool
+///   - DOCSIS vendor config file sub-option
+///
 const char* RENEW_CONFIGS[] = {
 const char* RENEW_CONFIGS[] = {
 // Configuration 0
 // Configuration 0
     "{ \"interfaces-config\": {"
     "{ \"interfaces-config\": {"
@@ -88,7 +97,34 @@ const char* RENEW_CONFIGS[] = {
         "    \"interface-id\": \"\","
         "    \"interface-id\": \"\","
         "    \"interface\": \"eth0\""
         "    \"interface\": \"eth0\""
         " } ],"
         " } ],"
+        "\"valid-lifetime\": 4000 }",
+
+// Configuration 3
+    "{ \"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "    \"option-def\": [ {"
+        "        \"name\": \"config-file\","
+        "        \"code\": 33,"
+        "        \"type\": \"string\","
+        "        \"space\": \"vendor-4491\""
+        "     } ],"
+        "    \"option-data\": [ {"
+        "          \"name\": \"config-file\","
+        "          \"space\": \"vendor-4491\","
+        "          \"data\": \"normal_erouter_v6.cm\""
+        "        }],"
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth0\""
+        " } ],"
         "\"valid-lifetime\": 4000 }"
         "\"valid-lifetime\": 4000 }"
+
 };
 };
 
 
 /// @brief Test fixture class for testing Renew.
 /// @brief Test fixture class for testing Renew.
@@ -370,5 +406,79 @@ TEST_F(RenewTest, requestAddressInRenewHint) {
     EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
     EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
 }
 }
 
 
+// This test verifies that the client can request the DOCSIS sub-options.
+TEST_F(RenewTest, requestDocsisORORenew) {
+    Dhcp6Client client;
+
+    // Configure client to request IA_NA.
+    client.useNA(na_iaid_);
+
+    // Configure the DOCSIS vendor ORO for 32, 33, 34, 37 and 38.
+    client.requestDocsisOption(DOCSIS3_V6_TFTP_SERVERS);
+    client.requestDocsisOption(DOCSIS3_V6_CONFIG_FILE);
+    client.requestDocsisOption(DOCSIS3_V6_SYSLOG_SERVERS);
+    client.requestDocsisOption(DOCSIS3_V6_TIME_SERVERS);
+    client.requestDocsisOption(DOCSIS3_V6_TIME_OFFSET);
+    // Don't add it for now.
+    client.useDocsisORO(false);
+
+    // Configure the server with NA pools and DOCSIS config file.
+    ASSERT_NO_THROW(configure(RENEW_CONFIGS[3], *client.getServer()));
+
+    // Perform 4-way exchange.
+    ASSERT_NO_THROW(client.doSARR());
+
+    // Simulate aging of leases.
+    client.fastFwdTime(1000);
+
+    // Make sure that the client has acquired NA lease.
+    std::vector<Lease6> leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+    ASSERT_EQ(1, leases_client_na.size());
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+    // Send Renew message to the server.
+    ASSERT_NO_THROW(client.doRenew());
+
+    std::vector<Lease6> leases_client_na_renewed =
+        client.getLeasesByType(Lease::TYPE_NA);
+    ASSERT_EQ(1, leases_client_na_renewed.size());
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+    // No vendor option was included in the renew so there should be none
+    // in the received configuration.
+    OptionPtr opt = client.config_.findOption(D6O_VENDOR_OPTS);
+    ASSERT_FALSE(opt);
+
+    // Add a DOCSIS ORO.
+    client.useDocsisORO(true);
+
+    // Send Renew message to the server.
+    ASSERT_NO_THROW(client.doRenew());
+
+    leases_client_na_renewed = client.getLeasesByType(Lease::TYPE_NA);
+    ASSERT_EQ(1, leases_client_na_renewed.size());
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+    // Verify whether there is a vendor option.
+    opt = client.config_.findOption(D6O_VENDOR_OPTS);
+    ASSERT_TRUE(opt);
+
+    // The vendor option must be a OptionVentor object.
+    boost::shared_ptr<OptionVendor> vendor =
+        boost::dynamic_pointer_cast<OptionVendor>(opt);
+    ASSERT_TRUE(vendor);
+
+    // The vendor-id should be DOCSIS.
+    EXPECT_EQ(VENDOR_ID_CABLE_LABS, vendor->getVendorId());
+
+    // There must be a config file sub-option.
+    opt = vendor->getOption(DOCSIS3_V6_CONFIG_FILE);
+
+    // With the expected content.
+    OptionStringPtr config_file =
+        boost::dynamic_pointer_cast<OptionString>(opt);
+    ASSERT_TRUE(opt);
+    EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
+}
 
 
 } // end of anonymous namespace
 } // end of anonymous namespace