diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index ad25969aad..78117d74ff 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -2331,6 +2331,7 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) { buildCfgOptionList(renew, ctx, co_list); appendDefaultOptions(renew, reply, co_list); appendRequestedOptions(renew, reply, co_list); + appendRequestedVendorOptions(renew, reply, ctx, co_list); processClientFqdn(renew, reply, ctx); extendLeases(renew, reply, ctx); @@ -2356,6 +2357,7 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) { buildCfgOptionList(rebind, ctx, co_list); appendDefaultOptions(rebind, reply, co_list); appendRequestedOptions(rebind, reply, co_list); + appendRequestedVendorOptions(rebind, reply, ctx, co_list); processClientFqdn(rebind, reply, ctx); extendLeases(rebind, reply, ctx); @@ -2389,6 +2391,7 @@ Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) { buildCfgOptionList(confirm, ctx, co_list); appendDefaultOptions(confirm, reply, co_list); appendRequestedOptions(confirm, reply, co_list); + appendRequestedVendorOptions(confirm, reply, ctx, co_list); // Indicates if at least one address has been verified. If no addresses // are verified it means that the client has sent no IA_NA options // or no IAAddr options and that client's message has to be discarded. @@ -2788,6 +2791,9 @@ Dhcpv6Srv::processInfRequest(const Pkt6Ptr& inf_request) { // Try to assign options that were requested by the client. appendRequestedOptions(inf_request, reply, co_list); + // Try to assigne vendor options that were requested by the client. + appendRequestedVendorOptions(inf_request, reply, ctx, co_list); + return (reply); } diff --git a/src/bin/dhcp6/tests/dhcp6_client.cc b/src/bin/dhcp6/tests/dhcp6_client.cc index 686f517ffb..9299f11506 100644 --- a/src/bin/dhcp6/tests/dhcp6_client.cc +++ b/src/bin/dhcp6/tests/dhcp6_client.cc @@ -6,11 +6,13 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -100,6 +102,7 @@ Dhcp6Client::Dhcp6Client() : srv_(boost::shared_ptr(new NakedDhcpv6Srv(0))), use_relay_(false), use_oro_(false), + use_docsis_oro_(false), use_client_id_(true), use_rapid_commit_(false), client_ias_(), @@ -116,6 +119,7 @@ Dhcp6Client::Dhcp6Client(boost::shared_ptr& srv) : srv_(srv), use_relay_(false), use_oro_(false), + use_docsis_oro_(false), use_client_id_(true), use_rapid_commit_(false), client_ias_(), @@ -396,6 +400,15 @@ Dhcp6Client::createMsg(const uint8_t msg_type) { 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 (!extra_options_.empty()) { for (OptionCollection::iterator opt = extra_options_.begin(); diff --git a/src/bin/dhcp6/tests/dhcp6_client.h b/src/bin/dhcp6/tests/dhcp6_client.h index 3f0fc6ff3c..f20e8e32e5 100644 --- a/src/bin/dhcp6/tests/dhcp6_client.h +++ b/src/bin/dhcp6/tests/dhcp6_client.h @@ -27,7 +27,7 @@ namespace test { /// @brief DHCPv6 client used for unit testing. /// /// 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 /// the response in the @c NakedDhcpv6Srv::fake_sent_ container. The client /// pops messages from this container which simulates reception of the @@ -684,6 +684,25 @@ public: 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 /// @return client-id DuidPtr getDuid() const { @@ -840,6 +859,7 @@ private: bool use_relay_; ///< Enable relaying messages to the server. bool use_oro_; ///< Conth + bool use_docsis_oro_; bool use_client_id_; bool use_rapid_commit_; @@ -852,6 +872,12 @@ private: /// to true. See @ref sendORO for details. std::vector 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 docsis_oro_; + /// @brief forced (Overridden) value of the server-id option (may be NULL) OptionPtr forced_server_id_; diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc index 9d93428140..82fcaf6163 100644 --- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc +++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc @@ -240,7 +240,7 @@ TEST_F(Dhcpv6SrvTest, basic) { }); srv.reset(); ASSERT_NO_THROW({ - // open an unpriviledged port + // open an unprivileged port srv.reset(new NakedDhcpv6Srv(DHCP6_SERVER_PORT + 10000)); }); } @@ -1631,7 +1631,7 @@ TEST_F(Dhcpv6SrvTest, vendorOptionsORO) { // check if we get response at all 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. ASSERT_FALSE(adv->getOption(D6O_VENDOR_OPTS)); @@ -2449,8 +2449,8 @@ TEST_F(Dhcpv6SrvTest, rsoo2relays) { opt = createRSOO(rsoo2, 2); // use 0x2 as payload 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(relay1); @@ -2651,7 +2651,7 @@ TEST_F(Dhcpv6SrvTest, receiveParseFailedStat) { // fakeReceive() srv.run(); - // All expected statstics must be present. + // All expected statistics must be present. pkt6_rcvd = mgr.getObservation("pkt6-received"); parse_fail = mgr.getObservation("pkt6-parse-failed"); recv_drop = mgr.getObservation("pkt6-receive-drop"); diff --git a/src/bin/dhcp6/tests/rebind_unittest.cc b/src/bin/dhcp6/tests/rebind_unittest.cc index e186380c52..801db22867 100644 --- a/src/bin/dhcp6/tests/rebind_unittest.cc +++ b/src/bin/dhcp6/tests/rebind_unittest.cc @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -60,6 +63,12 @@ namespace { /// - address pool: 2001:db8:1::/64 /// - 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[] = { // Configuration 0 "{ \"interfaces-config\": {" @@ -221,6 +230,32 @@ const char* REBIND_CONFIGS[] = { " \"interface-id\": \"\"," " \"interface\": \"eth0\"" " } ]," + "\"valid-lifetime\": 4000 }", + +// Configuration 7 + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"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 }" }; @@ -895,5 +930,78 @@ TEST_F(RebindTest, requestAddressInRebind) { 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.requestAddress(); + // 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 OptionVendor object. + boost::shared_ptr vendor = + boost::dynamic_pointer_cast(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(opt); + ASSERT_TRUE(opt); + EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue()); +} } // end of anonymous namespace diff --git a/src/bin/dhcp6/tests/renew_unittest.cc b/src/bin/dhcp6/tests/renew_unittest.cc index 136f244d98..3413d659ca 100644 --- a/src/bin/dhcp6/tests/renew_unittest.cc +++ b/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 // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -7,9 +7,13 @@ #include #include #include +#include +#include +#include #include #include #include +#include using namespace isc; using namespace isc::asiolink; @@ -35,6 +39,11 @@ namespace { /// - address pool: 2001:db8:1::/64 /// - 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[] = { // Configuration 0 "{ \"interfaces-config\": {" @@ -88,7 +97,28 @@ const char* RENEW_CONFIGS[] = { " \"interface-id\": \"\"," " \"interface\": \"eth0\"" " } ]," + "\"valid-lifetime\": 4000 }", + +// Configuration 3 + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"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 }" + }; /// @brief Test fixture class for testing Renew. @@ -367,5 +397,79 @@ TEST_F(RenewTest, requestAddressInRenewHint) { EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_)); } +// This test verifies that the client can request the DOCSIS sub-options. +TEST_F(RenewTest, docsisORO) { + Dhcp6Client client; + + // Configure client to request IA_NA. + client.requestAddress(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 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 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 OptionVendor object. + boost::shared_ptr vendor = + boost::dynamic_pointer_cast(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(opt); + ASSERT_TRUE(opt); + EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue()); +} } // end of anonymous namespace