Browse Source

[master] Merge branch 'trac3367'

Marcin Siodelski 10 years ago
parent
commit
9f05a29caa

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

@@ -504,9 +504,11 @@ void
 Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
 Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     answer->setIface(question->getIface());
     answer->setIface(question->getIface());
     answer->setIndex(question->getIndex());
     answer->setIndex(question->getIndex());
-    answer->setCiaddr(question->getCiaddr());
 
 
     answer->setSiaddr(IOAddress("0.0.0.0")); // explicitly set this to 0
     answer->setSiaddr(IOAddress("0.0.0.0")); // explicitly set this to 0
+    // ciaddr is always 0, except for the Renew/Rebind state when it may
+    // be set to the ciaddr sent by the client.
+    answer->setCiaddr(IOAddress("0.0.0.0"));
     answer->setHops(question->getHops());
     answer->setHops(question->getHops());
 
 
     // copy MAC address
     // copy MAC address
@@ -1050,6 +1052,18 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
 
 
         answer->setYiaddr(lease->addr_);
         answer->setYiaddr(lease->addr_);
 
 
+        /// @todo The server should check what ciaddr the client has supplied
+        /// in ciaddr. Currently the ciaddr is ignored except for the subnet
+        /// selection. If the client supplied an invalid address, the server
+        /// will also return an invalid address here.
+        if (!fake_allocation) {
+            // If this is a renewing client it will set a ciaddr which the
+            // server may include in the response. If this is a new allocation
+            // the client will set ciaddr to 0 and this will also be propagated
+            // to the server's answer.
+            answer->setCiaddr(question->getCiaddr());
+        }
+
         // If there has been Client FQDN or Hostname option sent, but the
         // If there has been Client FQDN or Hostname option sent, but the
         // hostname is empty, it means that server is responsible for
         // hostname is empty, it means that server is responsible for
         // generating the entire hostname for the client. The example of the
         // generating the entire hostname for the client. The example of the

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

@@ -45,6 +45,7 @@ Dhcp4Client::Configuration::reset() {
 
 
 Dhcp4Client::Dhcp4Client(const Dhcp4Client::State& state) :
 Dhcp4Client::Dhcp4Client(const Dhcp4Client::State& state) :
     config_(),
     config_(),
+    ciaddr_(IOAddress("0.0.0.0")),
     curr_transid_(0),
     curr_transid_(0),
     dest_addr_("255.255.255.255"),
     dest_addr_("255.255.255.255"),
     hwaddr_(generateHWAddr()),
     hwaddr_(generateHWAddr()),
@@ -59,6 +60,7 @@ Dhcp4Client::Dhcp4Client(const Dhcp4Client::State& state) :
 Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv>& srv,
 Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv>& srv,
                          const Dhcp4Client::State& state) :
                          const Dhcp4Client::State& state) :
     config_(),
     config_(),
+    ciaddr_(IOAddress("0.0.0.0")),
     curr_transid_(0),
     curr_transid_(0),
     dest_addr_("255.255.255.255"),
     dest_addr_("255.255.255.255"),
     hwaddr_(generateHWAddr()),
     hwaddr_(generateHWAddr()),
@@ -151,6 +153,10 @@ Dhcp4Client::doDiscover(const boost::shared_ptr<IOAddress>& requested_addr) {
     if (requested_addr) {
     if (requested_addr) {
         addRequestedAddress(*requested_addr);
         addRequestedAddress(*requested_addr);
     }
     }
+    // Override the default ciaddr if specified by a test.
+    if (ciaddr_.isSpecified()) {
+        context_.query_->setCiaddr(ciaddr_.get());
+    }
     // Send the message to the server.
     // Send the message to the server.
     sendMsg(context_.query_);
     sendMsg(context_.query_);
     // Expect response.
     // Expect response.
@@ -195,8 +201,10 @@ void
 Dhcp4Client::doRequest() {
 Dhcp4Client::doRequest() {
     context_.query_ = createMsg(DHCPREQUEST);
     context_.query_ = createMsg(DHCPREQUEST);
 
 
-    // Set ciaddr.
-    if ((state_ == SELECTING) || (state_ == INIT_REBOOT)) {
+    // Override the default ciaddr if specified by a test.
+    if (ciaddr_.isSpecified()) {
+        context_.query_->setCiaddr(ciaddr_.get());
+    } else if ((state_ == SELECTING) || (state_ == INIT_REBOOT)) {
         context_.query_->setCiaddr(IOAddress("0.0.0.0"));
         context_.query_->setCiaddr(IOAddress("0.0.0.0"));
     } else {
     } else {
         context_.query_->setCiaddr(IOAddress(config_.lease_.addr_));
         context_.query_->setCiaddr(IOAddress(config_.lease_.addr_));

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

@@ -19,6 +19,7 @@
 #include <dhcp/hwaddr.h>
 #include <dhcp/hwaddr.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/pkt4.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
+#include <util/optional_value.h>
 #include <boost/noncopyable.hpp>
 #include <boost/noncopyable.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 #include <set>
 #include <set>
@@ -294,6 +295,13 @@ public:
     /// @brief Current client's configuration obtained from the server.
     /// @brief Current client's configuration obtained from the server.
     Configuration config_;
     Configuration config_;
 
 
+    /// @brief Specific ciaddr to be used in client's messages.
+    ///
+    /// If this value is "unspecified" the default values will be used
+    /// by the client. If this value is specified, it will override ciaddr
+    /// in the client's messages.
+    isc::util::OptionalValue<asiolink::IOAddress> ciaddr_;
+
 private:
 private:
 
 
     /// @brief Creates and adds Requested IP Address option to the client's
     /// @brief Creates and adds Requested IP Address option to the client's
@@ -373,6 +381,7 @@ private:
 
 
     /// @brief Enable relaying messages to the server.
     /// @brief Enable relaying messages to the server.
     bool use_relay_;
     bool use_relay_;
+
 };
 };
 
 
 } // end of namespace isc::dhcp::test
 } // end of namespace isc::dhcp::test

+ 54 - 1
src/bin/dhcp4/tests/dora_unittest.cc

@@ -302,7 +302,7 @@ TEST_F(DORATest, selectingRequestNonMatchingAddress) {
 // Test that the client in the INIT-REBOOT state can request the IP
 // Test that the client in the INIT-REBOOT state can request the IP
 // address it has and the address is returned. Also, check that if
 // address it has and the address is returned. Also, check that if
 // if the client requests in valid address the server sends a DHCPNAK.
 // if the client requests in valid address the server sends a DHCPNAK.
-TEST_F(DORATest, InitRebootRequest) {
+TEST_F(DORATest, initRebootRequest) {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Configure DHCP server.
     // Configure DHCP server.
     configure(DORA_CONFIGS[0], *client.getServer());
     configure(DORA_CONFIGS[0], *client.getServer());
@@ -349,4 +349,57 @@ TEST_F(DORATest, InitRebootRequest) {
     EXPECT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
     EXPECT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
 }
 }
 
 
+// Check that the ciaddr returned by the server is correct for DHCPOFFER and
+// DHCPNAK according to RFC2131, section 4.3.1.
+TEST_F(DORATest, ciaddr) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    // Configure DHCP server.
+    configure(DORA_CONFIGS[0], *client.getServer());
+    // Force ciaddr of Discover message to be non-zero.
+    client.ciaddr_.specify(IOAddress("10.0.0.50"));
+    // Obtain a lease from the server using the 4-way exchange.
+    ASSERT_NO_THROW(client.doDiscover(boost::shared_ptr<
+                                      IOAddress>(new IOAddress("10.0.0.50"))));
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    Pkt4Ptr resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPOFFER.
+    ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+    // Make sure ciaddr is not set for DHCPOFFER.
+    EXPECT_EQ("0.0.0.0", resp->getCiaddr().toText());
+
+    // Obtain a lease from the server using the 4-way exchange.
+    ASSERT_NO_THROW(client.doRequest());
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+    // Let's transition the client to Renewing state.
+    client.setState(Dhcp4Client::RENEWING);
+
+    // Set the unicast destination address to indicate that it is a renewal.
+    client.setDestAddress(IOAddress("10.0.0.1"));
+    ASSERT_NO_THROW(client.doRequest());
+    // The client is sending invalid ciaddr so the server should send a NAK.
+    resp = client.getContext().response_;
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // For DHCPACK the ciaddr may be 0 or may be set to the ciaddr value
+    // from the client's message. Kea sets it to the latter.
+    EXPECT_EQ("10.0.0.50", resp->getCiaddr().toText());
+
+    // Replace the address held by the client. The client will request
+    // the assignment of this address but the server has a different
+    // address for this client.
+    client.ciaddr_.specify(IOAddress("192.168.0.30"));
+    ASSERT_NO_THROW(client.doRequest());
+    // The client is sending invalid ciaddr so the server should send a NAK.
+    resp = client.getContext().response_;
+    ASSERT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
+    // For DHCPNAK the ciaddr is always 0 (should not be copied) from the
+    // client's message.
+    EXPECT_EQ("0.0.0.0", resp->getCiaddr().toText());
+}
+
 } // end of anonymous namespace
 } // end of anonymous namespace

+ 1 - 0
src/lib/util/Makefile.am

@@ -30,6 +30,7 @@ libkea_util_la_SOURCES += memory_segment_local.h memory_segment_local.cc
 if USE_SHARED_MEMORY
 if USE_SHARED_MEMORY
 libkea_util_la_SOURCES += memory_segment_mapped.h memory_segment_mapped.cc
 libkea_util_la_SOURCES += memory_segment_mapped.h memory_segment_mapped.cc
 endif
 endif
+libkea_util_la_SOURCES += optional_value.h
 libkea_util_la_SOURCES += range_utilities.h
 libkea_util_la_SOURCES += range_utilities.h
 libkea_util_la_SOURCES += signal_set.cc signal_set.h
 libkea_util_la_SOURCES += signal_set.cc signal_set.h
 libkea_util_la_SOURCES += encode/base16_from_binary.h
 libkea_util_la_SOURCES += encode/base16_from_binary.h

+ 102 - 0
src/lib/util/optional_value.h

@@ -0,0 +1,102 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef OPTIONAL_VALUE_H
+#define OPTIONAL_VALUE_H
+
+namespace isc {
+namespace util {
+
+/// @brief Simple class representing an optional value.
+///
+/// This template class encapsulates a value of any type. An additional flag
+/// held by this class indicates if the value is "specified" or "unspecified".
+/// For example, a configuration parser for DHCP server may use this class
+/// to represent the value of the configuration parameter which may appear
+/// in the configuration file, but is not mandatory. The value of the
+/// @c OptionalValue may be initialized to "unspecified" initially. When the
+/// configuration parser finds that the appropriate parameter exists in the
+/// configuration file, the default value can be overriden and the value may
+/// be marked as "specified". If the parameter is not found, the value remains
+/// "unspecified" and the appropriate actions may be taken, e.g. the default
+/// value may be used.
+///
+/// This is a generic class and may be used in all cases when there is a need
+/// for the additional information to be carried along with the value.
+/// Alternative approach is to use a pointer which is only initialized if the
+/// actual value needs to be specified, but this may not be feasible in all
+/// cases.
+///
+/// @tparam Type of the encapsulated value.
+template<typename T>
+class OptionalValue {
+public:
+
+    /// @brief Constructor
+    ///
+    /// Creates optional value. The value defaults to "unspecified".
+    ///
+    /// @param value Default explicit value.
+    /// @param specified Boolean value which determines if the value is
+    /// initially specified or not (default is false).
+    OptionalValue(const T& value, const bool specified = false)
+        : value_(value),
+          specified_(specified) {
+    }
+
+    /// @brief Retrieves the actual value.
+    T get() const {
+        return (value_);
+    }
+
+    /// @brief Sets the actual value.
+    ///
+    /// @param value New value.
+    void set(const T& value) {
+        value_ = value;
+    }
+
+    /// @brief Sets the new value and marks it specified.
+    ///
+    /// @param value New actual value.
+    void specify(const T& value) {
+        set(value);
+        specify(true);
+    }
+
+    /// @brief Sets the value to "specified" or "unspecified".
+    ///
+    /// It does not alter the actual value. It only marks it "specified" or
+    /// "unspecified".
+    /// @param specified boolean that determined if a value is specified or not
+    void specify(const bool specified) {
+        specified_ = specified;
+    }
+
+    /// @brief Checks if the value is specified or unspecified.
+    ///
+    /// @return true if the value is specified, false otherwise.
+    bool isSpecified() const {
+        return (specified_);
+    }
+
+private:
+    T value_;         ///< Encapsulated value.
+    bool specified_;  ///< Flag which indicates if the value is specified.
+};
+
+} // end of namespace isc::util
+} // end of namespace isc
+
+#endif // OPTIONAL_VALUE_H

+ 1 - 0
src/lib/util/tests/Makefile.am

@@ -40,6 +40,7 @@ run_unittests_SOURCES += memory_segment_mapped_unittest.cc
 endif
 endif
 run_unittests_SOURCES += memory_segment_common_unittest.h
 run_unittests_SOURCES += memory_segment_common_unittest.h
 run_unittests_SOURCES += memory_segment_common_unittest.cc
 run_unittests_SOURCES += memory_segment_common_unittest.cc
+run_unittests_SOURCES += optional_value_unittest.cc
 run_unittests_SOURCES += qid_gen_unittest.cc
 run_unittests_SOURCES += qid_gen_unittest.cc
 run_unittests_SOURCES += random_number_generator_unittest.cc
 run_unittests_SOURCES += random_number_generator_unittest.cc
 run_unittests_SOURCES += socketsession_unittest.cc
 run_unittests_SOURCES += socketsession_unittest.cc

+ 83 - 0
src/lib/util/tests/optional_value_unittest.cc

@@ -0,0 +1,83 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <util/optional_value.h>
+#include <gtest/gtest.h>
+
+namespace {
+
+using namespace isc::util;
+
+// This test checks that the constructor sets the values passed as arguments.
+TEST(OptionalValueTest, constructor) {
+    // Do not specify the second parameter. The default should be that
+    // the value is "unspecified".
+    OptionalValue<int> value1(10);
+    EXPECT_EQ(10, value1.get());
+    EXPECT_FALSE(value1.isSpecified());
+
+    // Use the non-default value for second parameter.
+    OptionalValue<int> value2(2, true);
+    EXPECT_EQ(2, value2.get());
+    EXPECT_TRUE(value2.isSpecified());
+}
+
+// This test checks that the OptionalValue::set and OptionalValue::specify
+// set the values as expected.
+TEST(OptionalValueTest, set) {
+    OptionalValue<int> value(10);
+    ASSERT_EQ(10, value.get());
+    ASSERT_FALSE(value.isSpecified());
+
+    // Set new value. This should not change the "specified" flag.
+    value.set(100);
+    ASSERT_EQ(100, value.get());
+    ASSERT_FALSE(value.isSpecified());
+
+    // Mark value "specified". The value itself should not change.
+    value.specify(true);
+    ASSERT_EQ(100, value.get());
+    ASSERT_TRUE(value.isSpecified());
+
+    // Once it is "specified", set the new value. It should remain specified.
+    value.set(5);
+    ASSERT_EQ(5, value.get());
+    ASSERT_TRUE(value.isSpecified());
+
+    // Mark it "unspecified". The value should remain the same.
+    value.specify(false);
+    ASSERT_EQ(5, value.get());
+    ASSERT_FALSE(value.isSpecified());
+}
+
+// This test checks that the OptionalValue::specify functions may be used
+// to set the new value and to mark value specified.
+TEST(OptionalValueTest, specifyValue) {
+    OptionalValue<int> value(10);
+    ASSERT_EQ(10, value.get());
+    ASSERT_FALSE(value.isSpecified());
+
+    // Set the new value and mark it "specified".
+    value.specify(123);
+    ASSERT_EQ(123, value.get());
+    ASSERT_TRUE(value.isSpecified());
+
+    // Specify another value. The value should be still "specified".
+    value.specify(1000);
+    ASSERT_EQ(1000, value.get());
+    ASSERT_TRUE(value.isSpecified());
+}
+
+} // end of anonymous namespace