Parcourir la source

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

Marcin Siodelski il y a 12 ans
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"
                       << " be equal to zero. Option code '0' is reserved in"
                       << " DHCPv6.");
                       << " DHCPv6.");
         } else if (option_code > std::numeric_limits<uint16_t>::max()) {
         } 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());
                       << " exceed " << std::numeric_limits<uint16_t>::max());
         }
         }
         // Check the option name has been specified, is non-empty and does not
         // 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
 It lists some information about the parameters with which the server
 is running.
 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
 % DHCP6_CONFIG_LOAD_FAIL failed to load configuration: %1
 This critical error message indicates that the initial DHCPv6
 This critical error message indicates that the initial DHCPv6
 configuration has failed. The server will start, but nothing will be
 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_addrlst.h>
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_ia.h>
+#include <dhcp/option6_int_array.h>
 #include <dhcp/pkt6.h>
 #include <dhcp/pkt6.h>
+#include <dhcp/subnet.h>
+#include <dhcp/cfgmgr.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 #include <util/io_utilities.h>
 #include <util/io_utilities.h>
 #include <util/range_utilities.h>
 #include <util/range_utilities.h>
 
 
+#include <boost/foreach.hpp>
+
 using namespace isc;
 using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
 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)
     // 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
     // add server-id
     answer->addOption(getServerID());
     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) {
 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.
     /// @brief Appends requested options to server's answer.
     ///
     ///
     /// Appends options requested by client to the 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 question client's message
     /// @param answer server's message (options will be added here)
     /// @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 <arpa/inet.h>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
+#include <dhcp6/config_parser.h>
+#include <dhcp6/dhcp6_srv.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/option6_ia.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/buffer.h>
 #include <util/range_utilities.h>
 #include <util/range_utilities.h>
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
@@ -31,6 +35,9 @@ using namespace std;
 using namespace isc;
 using namespace isc;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
 using namespace isc::util;
 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
 // namespace has to be named, because friends are defined in Dhcpv6Srv class
 // Maybe it should be isc::test?
 // Maybe it should be isc::test?
@@ -53,11 +60,14 @@ public:
 
 
 class Dhcpv6SrvTest : public ::testing::Test {
 class Dhcpv6SrvTest : public ::testing::Test {
 public:
 public:
-    // these are empty for now, but let's keep them around
-    Dhcpv6SrvTest() {
+    Dhcpv6SrvTest()
+        : rcode_(-1) {
     }
     }
     ~Dhcpv6SrvTest() {
     ~Dhcpv6SrvTest() {
     };
     };
+
+    int rcode_;
+    ConstElementPtr comment_;
 };
 };
 
 
 TEST_F(Dhcpv6SrvTest, basic) {
 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;
     boost::scoped_ptr<NakedDhcpv6Srv> srv;
     ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv()) );
     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
     // a dummy content for client-id
     OptionBuffer clntDuid(32);
     OptionBuffer clntDuid(32);
     for (int i = 0; i < 32; i++) {
     for (int i = 0; i < 32; i++) {
@@ -169,10 +209,10 @@ TEST_F(Dhcpv6SrvTest, Solicit_basic) {
     sol->addOption(ia);
     sol->addOption(ia);
 
 
     // Let's not send address in solicit yet
     // 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:
     // constructed very simple SOLICIT message with:
     // - client-id option (mandatory)
     // - client-id option (mandatory)
@@ -191,15 +231,38 @@ TEST_F(Dhcpv6SrvTest, Solicit_basic) {
     boost::shared_ptr<Pkt6> reply = srv->processSolicit(sol);
     boost::shared_ptr<Pkt6> reply = srv->processSolicit(sol);
 
 
     // check if we get response at all
     // 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( DHCPV6_ADVERTISE, reply->getType() );
     EXPECT_EQ( 1234, reply->getTransid() );
     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);
     OptionPtr tmp = reply->getOption(D6O_IA_NA);
     ASSERT_TRUE( tmp );
     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() );
     EXPECT_EQ( 234, reply_ia->getIAID() );
 
 
     // check that there's an address included
     // check that there's an address included
@@ -219,6 +282,32 @@ TEST_F(Dhcpv6SrvTest, Solicit_basic) {
     ASSERT_EQ(tmp->len(),  srv->getServerID()->len() );
     ASSERT_EQ(tmp->len(),  srv->getServerID()->len() );
 
 
     EXPECT_TRUE(tmp->getData() == srv->getServerID()->getData());
     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
     // 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,
                                               buf.begin() + offset + opt_len));
                                               buf.begin() + offset + opt_len));
             break;
             break;
+        case D6O_ORO:
+            opt = OptionPtr(new Option6IntArray<uint16_t>(opt_type,
+                                                          buf.begin() + offset,
+                                                          buf.begin() + offset + opt_len));
+            break;
         default:
         default:
             opt = OptionPtr(new Option(Option::V6,
             opt = OptionPtr(new Option(Option::V6,
                                        opt_type,
                                        opt_type,