Browse Source

[2417] Append configured DHCPv6 options to server's response.

Marcin Siodelski 12 years ago
parent
commit
b966ebcc19

+ 1 - 1
src/bin/dhcp6/config_parser.cc

@@ -602,7 +602,7 @@ private:
                       << " be equal to zero. Option code '0' is reserved in"
                       << " DHCPv6.");
         } else if (option_code > std::numeric_limits<uint16_t>::max()) {
-            isc_throw(Dhcp6ConfigError, "Parser error: value of 'code' must not"ciwtezcowy
+            isc_throw(Dhcp6ConfigError, "Parser error: value of 'code' must not"
                       << " exceed " << std::numeric_limits<uint16_t>::max());
         }
         // Check the option name has been specified, is non-empty and does not

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

@@ -110,6 +110,10 @@ This is a debug message issued during the IPv6 DHCP server startup.
 It lists some information about the parameters with which the server
 is running.
 
+% DHCP6_NO_SUBNET_FOR_ADDRESS fail to find subnet for address: %1
+This warning message indicates that server does not support subnet
+that received DHCPv6 packet comes from.
+
 % DHCP6_CONFIG_LOAD_FAIL failed to load configuration: %1
 This critical error message indicates that the initial DHCPv6
 configuration has failed. The server will start, but nothing will be

+ 57 - 12
src/bin/dhcp6/dhcp6_srv.cc

@@ -24,11 +24,16 @@
 #include <dhcp/option6_addrlst.h>
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_ia.h>
+#include <dhcp/option6_int_array.h>
 #include <dhcp/pkt6.h>
+#include <dhcp/subnet.h>
+#include <dhcp/cfgmgr.h>
 #include <exceptions/exceptions.h>
 #include <util/io_utilities.h>
 #include <util/range_utilities.h>
 
+#include <boost/foreach.hpp>
+
 using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
@@ -289,23 +294,63 @@ void Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
     // TODO: Should throw if there is no client-id (except anonymous INF-REQUEST)
 }
 
-void Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr& /*question*/, Pkt6Ptr& answer) {
-    // TODO: question is currently unused, but we need it at least to know
-    // message type we are answering
-
+void Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
     // add server-id
     answer->addOption(getServerID());
-}
 
+    // Get the subnet object. It holds options to be sent to the client
+    // that belongs to the particular subnet.
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+    // Warn if subnet is not supported and quit.
+    if (!subnet) {
+        LOG_WARN(dhcp6_logger, DHCP6_NO_SUBNET_FOR_ADDRESS)
+            .arg(question->getRemoteAddr().toText());
+        return;
+    }
+    // Add DNS_SERVERS option. It should have been configured.
+    const Subnet::OptionContainer& options = subnet->getOptions();
+    const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+    const Subnet::OptionContainerTypeRange range =
+        idx.equal_range(D6O_NAME_SERVERS);
+    // In theory we may have multiple options with the same
+    // option code. They are not differentiated right now
+    // until support for option spaces is implemented.
+    // Until that's the case, simply add the first found option.
+    if (std::distance(range.first, range.second) > 0) {
+        answer->addOption(range.first->option);
+    }
+}
 
-void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& /*question*/, Pkt6Ptr& answer) {
-    // TODO: question is currently unused, but we need to extract ORO from it
-    // and act on its content. Now we just send DNS-SERVERS option.
+void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
+    // Get the subnet for a particular address.
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+    if (!subnet) {
+        LOG_WARN(dhcp6_logger, DHCP6_NO_SUBNET_FOR_ADDRESS)
+            .arg(question->getRemoteAddr().toText());
+        return;
+    }
 
-    // add dns-servers option
-    boost::shared_ptr<Option> dnsservers(new Option6AddrLst(D6O_NAME_SERVERS,
-                                         IOAddress(HARDCODED_DNS_SERVER)));
-    answer->addOption(dnsservers);
+    // Client requests some options using ORO option. Try to
+    // get this option from client's message.
+    boost::shared_ptr<Option6IntArray<uint16_t> > option_oro =
+        boost::dynamic_pointer_cast<Option6IntArray<uint16_t> >(question->getOption(D6O_ORO));
+    // Option ORO not found. Don't do anything then.
+    if (!option_oro) {
+        return;
+    }
+    // Get the list of options that client requested.
+    const std::vector<uint16_t>& requested_opts = option_oro->getValues();
+    // Get the list of options configured for a subnet.
+    const Subnet::OptionContainer& options = subnet->getOptions();
+    const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+    // Try to match requested options with those configured for a subnet.
+    // If match is found, append configured option to the answer message.
+    BOOST_FOREACH(uint16_t opt, requested_opts) {
+        const Subnet::OptionContainerTypeRange& range = idx.equal_range(opt);
+        BOOST_FOREACH(Subnet::OptionDescriptor desc, range) {
+            answer->addOption(desc.option);
+        }
+    }
 }
 
 void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {

+ 0 - 2
src/bin/dhcp6/dhcp6_srv.h

@@ -171,8 +171,6 @@ protected:
     /// @brief Appends requested options to server's answer.
     ///
     /// Appends options requested by client to the server's answer.
-    /// TODO: This method is currently a stub. It just appends DNS-SERVERS
-    /// option.
     ///
     /// @param question client's message
     /// @param answer server's message (options will be added here)

+ 99 - 10
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -20,9 +20,13 @@
 #include <arpa/inet.h>
 #include <gtest/gtest.h>
 
+#include <dhcp6/config_parser.h>
+#include <dhcp6/dhcp6_srv.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/option6_ia.h>
-#include <dhcp6/dhcp6_srv.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_int_array.h>
+#include <config/ccsession.h>
 #include <util/buffer.h>
 #include <util/range_utilities.h>
 #include <boost/scoped_ptr.hpp>
@@ -31,6 +35,9 @@ using namespace std;
 using namespace isc;
 using namespace isc::dhcp;
 using namespace isc::util;
+using namespace isc::data;
+using namespace isc::config;
+using namespace isc::asiolink;
 
 // namespace has to be named, because friends are defined in Dhcpv6Srv class
 // Maybe it should be isc::test?
@@ -53,11 +60,14 @@ public:
 
 class Dhcpv6SrvTest : public ::testing::Test {
 public:
-    // these are empty for now, but let's keep them around
-    Dhcpv6SrvTest() {
+    Dhcpv6SrvTest()
+        : rcode_(-1) {
     }
     ~Dhcpv6SrvTest() {
     };
+
+    int rcode_;
+    ConstElementPtr comment_;
 };
 
 TEST_F(Dhcpv6SrvTest, basic) {
@@ -150,10 +160,40 @@ TEST_F(Dhcpv6SrvTest, DUID) {
     }
 }
 
-TEST_F(Dhcpv6SrvTest, Solicit_basic) {
+TEST_F(Dhcpv6SrvTest, solicitBasic) {
+    ConstElementPtr x;
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1234::/80\" ],"
+        "    \"subnet\": \"2001:db8:1234::/64\", "
+        "    \"option-data\": [ {"
+        "          \"name\": \"OPTION_DNS_SERVERS\","
+        "          \"code\": 23,"
+        "          \"data\": \"2001 0DB8 1234 FFFF 0000 0000 0000 0001"
+        "2001 0DB8 1234 FFFF 0000 0000 0000 0002\""
+        "        },"
+        "        {"
+        "          \"name\": \"OPTION_FOO\","
+        "          \"code\": 1000,"
+        "          \"data\": \"1234\""
+        "        } ]"
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
     boost::scoped_ptr<NakedDhcpv6Srv> srv;
     ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv()) );
 
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+
+    ASSERT_EQ(0, rcode_);
+
     // a dummy content for client-id
     OptionBuffer clntDuid(32);
     for (int i = 0; i < 32; i++) {
@@ -169,10 +209,10 @@ TEST_F(Dhcpv6SrvTest, Solicit_basic) {
     sol->addOption(ia);
 
     // Let's not send address in solicit yet
-    // boost::shared_ptr<Option6IAAddr> addr(new Option6IAAddr(D6O_IAADDR,
-    //    IOAddress("2001:db8:1234:ffff::ffff"), 5001, 7001));
-    // ia->addOption(addr);
-    // sol->addOption(ia);
+    /*    boost::shared_ptr<Option6IAAddr>
+        addr(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8:1234:ffff::ffff"), 5001, 7001));
+    ia->addOption(addr);
+    sol->addOption(ia); */
 
     // constructed very simple SOLICIT message with:
     // - client-id option (mandatory)
@@ -191,15 +231,38 @@ TEST_F(Dhcpv6SrvTest, Solicit_basic) {
     boost::shared_ptr<Pkt6> reply = srv->processSolicit(sol);
 
     // check if we get response at all
-    ASSERT_TRUE( reply != boost::shared_ptr<Pkt6>() );
+    ASSERT_TRUE(reply);
 
     EXPECT_EQ( DHCPV6_ADVERTISE, reply->getType() );
     EXPECT_EQ( 1234, reply->getTransid() );
 
+    // We have not requested option with code 1000 so it should not
+    // be included in the response.
+    ASSERT_FALSE(reply->getOption(1000));
+
+    // Let's now request option with code 1000.
+    // We expect that server will include this option in its reply.
+    boost::shared_ptr<Option6IntArray<uint16_t> >
+        option_oro(new Option6IntArray<uint16_t>(D6O_ORO));
+    // Create vector with one code equal to 1000.
+    std::vector<uint16_t> codes(1, 1000);
+    // Pass this code to option.
+    option_oro->setValues(codes);
+    // Append ORO to SOLICIT message.
+    sol->addOption(option_oro);
+    
+    // Need to process SOLICIT again after requesting new option.
+    reply = srv->processSolicit(sol);
+    ASSERT_TRUE(reply);
+
+    EXPECT_EQ(DHCPV6_ADVERTISE, reply->getType());
+
     OptionPtr tmp = reply->getOption(D6O_IA_NA);
     ASSERT_TRUE( tmp );
 
-    Option6IA* reply_ia = dynamic_cast<Option6IA*>(tmp.get());
+    boost::shared_ptr<Option6IA> reply_ia =
+        boost::dynamic_pointer_cast<Option6IA>(tmp);
+    ASSERT_TRUE(reply_ia);
     EXPECT_EQ( 234, reply_ia->getIAID() );
 
     // check that there's an address included
@@ -219,6 +282,32 @@ TEST_F(Dhcpv6SrvTest, Solicit_basic) {
     ASSERT_EQ(tmp->len(),  srv->getServerID()->len() );
 
     EXPECT_TRUE(tmp->getData() == srv->getServerID()->getData());
+ 
+    tmp = reply->getOption(D6O_NAME_SERVERS);
+    ASSERT_TRUE(tmp);
+    
+    boost::shared_ptr<Option6AddrLst> reply_nameservers =
+        boost::dynamic_pointer_cast<Option6AddrLst>(tmp);
+    ASSERT_TRUE(reply_nameservers);
+
+    Option6AddrLst::AddressContainer addrs = reply_nameservers->getAddresses();
+    ASSERT_EQ(2, addrs.size());
+    EXPECT_TRUE(addrs[0] == IOAddress("2001:db8:1234:FFFF::1"));
+    EXPECT_TRUE(addrs[1] == IOAddress("2001:db8:1234:FFFF::2"));
+
+    // There is a dummy option with code 1000 we requested from a server.
+    // Expect that this option is in server's response.
+    tmp = reply->getOption(1000);
+    ASSERT_TRUE(tmp);
+
+    // Check that the option contains valid data (from configuration).
+    std::vector<uint8_t> data = tmp->getData();
+    ASSERT_EQ(2, data.size());
+
+    const uint8_t foo_expected[] = {
+        0x12, 0x34
+    };
+    EXPECT_EQ(0, memcmp(&data[0], foo_expected, 2));
 
     // more checks to be implemented
 }

+ 5 - 0
src/lib/dhcp/libdhcp++.cc

@@ -106,6 +106,11 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
                                               buf.begin() + offset,
                                               buf.begin() + offset + opt_len));
             break;
+        case D6O_ORO:
+            opt = OptionPtr(new Option6IntArray<uint16_t>(opt_type,
+                                                          buf.begin() + offset,
+                                                          buf.begin() + offset + opt_len));
+            break;
         default:
             opt = OptionPtr(new Option(Option::V6,
                                        opt_type,