Commit 1a9a8b52 authored by Marcin Siodelski's avatar Marcin Siodelski

[3320] Basic implementation of the Requested IP Address support.

parent 4852d3ec
......@@ -942,7 +942,15 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
}
// client-id is not mandatory in DHCPv4
IOAddress hint = question->getYiaddr();
// Try to get the Requested IP Address option and use the address as a hint
// for the allocation engine. If the server doesn't already have a lease
// for this client it will try to allocate the one requested.
OptionCustomPtr opt_requested_address = boost::dynamic_pointer_cast<
OptionCustom>(question->getOption(DHO_DHCP_REQUESTED_ADDRESS));
IOAddress hint("0.0.0.0");
if (opt_requested_address) {
hint = opt_requested_address->readAddress();
}
HWAddrPtr hwaddr = question->getHWAddr();
......@@ -1078,7 +1086,7 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
} else {
// Allocation engine did not allocate a lease. The engine logged
// cause of that failure. The only thing left is to insert
// cause of that failure. The onlxy thing left is to insert
// status code to pass the sad news to the client.
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, fake_allocation?
......
......@@ -90,6 +90,7 @@ dhcp4_unittests_SOURCES += fqdn_unittest.cc
dhcp4_unittests_SOURCES += marker_file.cc
dhcp4_unittests_SOURCES += dhcp4_client.cc dhcp4_client.h
dhcp4_unittests_SOURCES += inform_unittest.cc
dhcp4_unittests_SOURCES += dora_unittest.cc
if CONFIG_BACKEND_BUNDY
# For Bundy backend, we only need to run the usual tests. There are no
......
......@@ -21,6 +21,8 @@
#include <boost/pointer_cast.hpp>
#include <cstdlib>
using namespace isc::asiolink;
namespace isc {
namespace dhcp {
namespace test {
......@@ -38,9 +40,10 @@ Dhcp4Client::Configuration::reset() {
log_servers_.clear();
quotes_servers_.clear();
serverid_ = asiolink::IOAddress("0.0.0.0");
lease_ = Lease4();
}
Dhcp4Client::Dhcp4Client() :
Dhcp4Client::Dhcp4Client(const Dhcp4Client::State& state) :
config_(),
curr_transid_(0),
dest_addr_("255.255.255.255"),
......@@ -49,10 +52,12 @@ Dhcp4Client::Dhcp4Client() :
requested_options_(),
server_facing_relay_addr_("10.0.0.2"),
srv_(boost::shared_ptr<NakedDhcpv4Srv>(new NakedDhcpv4Srv(0))),
state_(state),
use_relay_(false) {
}
Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv>& srv) :
Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv>& srv,
const Dhcp4Client::State& state) :
config_(),
curr_transid_(0),
dest_addr_("255.255.255.255"),
......@@ -61,6 +66,7 @@ Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv>& srv) :
requested_options_(),
server_facing_relay_addr_("10.0.0.2"),
srv_(srv),
state_(state),
use_relay_(false) {
}
......@@ -104,7 +110,12 @@ Dhcp4Client::applyConfiguration() {
config_.serverid_ = opt_serverid->readAddress();
}
/// @todo Other possible configuration, e.g. lease.
/// @todo Set the valid lifetime, t1, t2 etc.
config_.lease_ = Lease4(IOAddress(context_.response_->getYiaddr()),
&context_.response_->getHWAddr()->hwaddr_[0],
context_.response_->getHWAddr()->hwaddr_.size(),
0, 0, 0, 0, 0, time(NULL), 0, false, false,
"");
}
void
......@@ -123,21 +134,37 @@ Dhcp4Client::createMsg(const uint8_t msg_type) {
return (msg);
}
void
Dhcp4Client::doDiscover(const boost::shared_ptr<IOAddress>& requested_addr) {
context_.query_ = createMsg(DHCPDISCOVER);
// Request options if any.
includePRL();
if (requested_addr) {
Option4AddrLstPtr
opt_requested_addr(new Option4AddrLst(DHO_DHCP_REQUESTED_ADDRESS,
IOAddress(*requested_addr)));
context_.query_->addOption(opt_requested_addr);
}
// Send the message to the server.
sendMsg(context_.query_);
// Expect response.
context_.response_ = receiveOneMsg();
}
void
Dhcp4Client::doDORA(const boost::shared_ptr<IOAddress>& requested_addr) {
doDiscover(requested_addr);
if (context_.response_ && (context_.response_->getType() == DHCPOFFER)) {
doRequest(requested_addr);
}
}
void
Dhcp4Client::doInform(const bool set_ciaddr) {
context_.query_ = createMsg(DHCPINFORM);
// Request options if any.
if (!requested_options_.empty()) {
// Include Parameter Request List if at least one option code
// has been specified to be requested.
OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
DHO_DHCP_PARAMETER_REQUEST_LIST));
for (std::set<uint8_t>::const_iterator opt = requested_options_.begin();
opt != requested_options_.end(); ++opt) {
prl->addValue(*opt);
}
context_.query_->addOption(prl);
}
includePRL();
// The client sending a DHCPINFORM message has an IP address obtained
// by some other means, e.g. static configuration. The lease which we
// are using here is most likely set by the createLease method.
......@@ -159,6 +186,72 @@ Dhcp4Client::doInform(const bool set_ciaddr) {
}
}
void
Dhcp4Client::doRequest(const boost::shared_ptr<IOAddress>& requested_addr) {
context_.query_ = createMsg(DHCPREQUEST);
// Set ciaddr.
if ((state_ == SELECTING) || (state_ == INIT_REBOOT)) {
context_.query_->setCiaddr(IOAddress("0.0.0.0"));
} else {
context_.query_->setCiaddr(IOAddress(config_.lease_.addr_));
}
// Requested IP address.
if ((state_ == SELECTING) || (state_ == INIT_REBOOT)) {
if (context_.response_ &&
(context_.response_->getType() == DHCPOFFER) &&
(context_.response_->getYiaddr() != IOAddress("0.0.0.0"))) {
Option4AddrLstPtr
opt_requested_addr(new Option4AddrLst(DHO_DHCP_REQUESTED_ADDRESS,
IOAddress(context_.response_->getYiaddr())));
context_.query_->addOption(opt_requested_addr);
} else {
isc_throw(Dhcp4ClientError, "error sending the DHCPREQUEST because"
" the received DHCPOFFER message was invalid");
}
}
// Server identifier.
if (state_ == SELECTING) {
if (context_.response_) {
OptionPtr opt_serverid =
context_.response_->getOption(DHO_DHCP_SERVER_IDENTIFIER);
if (!opt_serverid) {
isc_throw(Dhcp4ClientError, "missing server identifier in the"
" server's response");
}
context_.query_->addOption(opt_serverid);
}
}
// Request options if any.
includePRL();
// Send the message to the server.
sendMsg(context_.query_);
// Expect response.
context_.response_ = receiveOneMsg();
// If the server has responded, store the configuration received.
if (context_.response_) {
applyConfiguration();
}
}
void
Dhcp4Client::includePRL() {
if (!requested_options_.empty() && context_.query_) {
// Include Parameter Request List if at least one option code
// has been specified to be requested.
OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
DHO_DHCP_PARAMETER_REQUEST_LIST));
for (std::set<uint8_t>::const_iterator opt = requested_options_.begin();
opt != requested_options_.end(); ++opt) {
prl->addValue(*opt);
}
context_.query_->addOption(prl);
}
}
HWAddrPtr
Dhcp4Client::generateHWAddr(const uint8_t htype) const {
if (htype != HTYPE_ETHER) {
......
......@@ -27,6 +27,13 @@ namespace isc {
namespace dhcp {
namespace test {
/// @brief General error emitted by the DHCP4 test client.
class Dhcp4ClientError : public isc::Exception {
public:
Dhcp4ClientError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief DHCPv4 client used for unit testing.
///
/// This class implements a DHCPv4 "client" which interoperates with the
......@@ -45,6 +52,14 @@ namespace test {
class Dhcp4Client : public boost::noncopyable {
public:
/// @brief States of the DHCP client.
enum State {
SELECTING,
INIT_REBOOT,
RENEWING,
REBINDING
};
/// @brief Holds the DHCPv4 messages taking part in transaction between
/// the client and the server.
struct Context {
......@@ -79,12 +94,16 @@ public:
};
/// @brief Creates a new client.
Dhcp4Client();
///
/// @param Initial client's state.
Dhcp4Client(const State& state = SELECTING);
/// @brief Creates a new client that communicates with a specified server.
///
/// @param srv An instance of the DHCPv4 server to be used.
Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv>& srv);
/// @param state Initial client's state.
Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv>& srv,
const State& state = SELECTING);
/// @brief Creates a lease for the client using the specified address
/// and valid lifetime.
......@@ -101,6 +120,21 @@ public:
/// @param valid_lft Valid lifetime.
void createLease(const asiolink::IOAddress& addr, const uint32_t valid_lft);
/// @brief Sends DHCPDISCOVER message to the server and receives response.
///
/// @param requested_addr A pointer to the IP Address to be sent in the
/// Requested IP Address option or NULL if the option should not be
/// included.
void doDiscover(const boost::shared_ptr<asiolink::IOAddress>&
requested_addr = boost::shared_ptr<asiolink::IOAddress>());
/// @brief Perform 4-way exchange with a server.
///
/// @param requested_addr A pointer to the address to be requested using the
/// Requested IP Address option.
void doDORA(const boost::shared_ptr<asiolink::IOAddress>&
requested_addr = boost::shared_ptr<asiolink::IOAddress>());
/// @brief Sends DHCPINFORM message to the server and receives response.
///
/// This function simulates sending the DHCPINFORM message to the server
......@@ -121,6 +155,14 @@ public:
/// an error occurs.
void doInform(const bool set_ciaddr = true);
/// @brief Sends DHCPREQUEST Message to the server and receives a response.
///
/// @param requested_addr A pointer to the IP Address to be sent in the
/// Requested IP Address option or NULL if the option should not be
/// included.
void doRequest(const boost::shared_ptr<asiolink::IOAddress>&
requested_addr = boost::shared_ptr<asiolink::IOAddress>());
/// @brief Generates a hardware address used by the client.
///
/// It assigns random values to the bytes of the hardware address.
......@@ -195,6 +237,16 @@ public:
dest_addr_ = dest_addr;
}
/// @brief Sets client state.
///
/// Depending on the current state the client's behavior is different
/// when sending Request messages as per RFC2131, section 4.3.2.
///
/// @param state New client's state.
void setState(const State& state) {
state_ = state;
}
/// @brief Simulate sending messages through a relay.
///
/// @param use Parameter which 'true' value indicates that client should
......@@ -236,6 +288,13 @@ private:
/// @return An instance of the message created.
Pkt4Ptr createMsg(const uint8_t msg_type);
/// @brief Include PRL Option in the query message.
///
/// This function creates the instance of the PRL option and adds
/// option codes from the @c requested_options_ to it. It later adds
/// the PRL option to the @c context_.query_ message if it is non-NULL.
void includePRL();
/// @brief Simulates reception of the message from the server.
///
/// @return Received message.
......@@ -274,6 +333,9 @@ private:
/// @brief Pointer to the server that the client is communicating with.
boost::shared_ptr<NakedDhcpv4Srv> srv_;
/// @brief Current state of the client.
State state_;
/// @brief Enable relaying messages to the server.
bool use_relay_;
};
......
// 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 <asiolink/io_address.h>
#include <cc/data.h>
#include <dhcp/dhcp4.h>
#include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcp4/tests/dhcp4_test_utils.h>
#include <dhcp4/tests/dhcp4_client.h>
#include <boost/shared_ptr.hpp>
using namespace isc;
using namespace isc::asiolink;
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::dhcp::test;
namespace {
/// @brief Set of JSON configurations used throughout the DORA tests.
///
/// - Configuration 0:
/// - Used for testing direct traffic
/// - 1 subnet: 10.0.0.0/24
/// - 1 pool: 10.0.0.10-10.0.0.100
/// - Router option present: 10.0.0.200 and 10.0.0.201
/// - Domain Name Server option present: 10.0.0.202, 10.0.0.203.
/// - Log Servers option present: 192.0.2.200 and 192.0.2.201
/// - Quotes Servers option present: 192.0.2.202, 192.0.2.203.
///
/// - Configuration 1:
/// - Use for testing relayed messages
/// - 1 subnet: 192.0.2.0/24
/// - Router option present: 192.0.2.200 and 192.0.2.201
/// - Domain Name Server option present: 192.0.2.202, 192.0.2.203.
/// - Log Servers option present: 192.0.2.200 and 192.0.2.201
/// - Quotes Servers option present: 192.0.2.202, 192.0.2.203.
const char* DORA_CONFIGS[] = {
// Configuration 0
"{ \"interfaces\": [ \"all\" ],"
"\"valid-lifetime\": 600,"
"\"subnet4\": [ { "
" \"subnet\": \"10.0.0.0/24\", "
" \"pool\": [ \"10.0.0.10-10.0.0.100\" ],"
" \"option-data\": [ {"
" \"name\": \"routers\","
" \"code\": 3,"
" \"data\": \"10.0.0.200,10.0.0.201\","
" \"csv-format\": true,"
" \"space\": \"dhcp4\""
" },"
" {"
" \"name\": \"domain-name-servers\","
" \"code\": 6,"
" \"data\": \"10.0.0.202,10.0.0.203\","
" \"csv-format\": true,"
" \"space\": \"dhcp4\""
" },"
" {"
" \"name\": \"log-servers\","
" \"code\": 7,"
" \"data\": \"10.0.0.200,10.0.0.201\","
" \"csv-format\": true,"
" \"space\": \"dhcp4\""
" },"
" {"
" \"name\": \"cookie-servers\","
" \"code\": 8,"
" \"data\": \"10.0.0.202,10.0.0.203\","
" \"csv-format\": true,"
" \"space\": \"dhcp4\""
" } ]"
" } ]"
"}",
// Configuration 1
"{ \"interfaces\": [ \"all\" ],"
"\"valid-lifetime\": 600,"
"\"subnet4\": [ { "
" \"subnet\": \"192.0.2.0/24\", "
" \"option-data\": [ {"
" \"name\": \"routers\","
" \"code\": 3,"
" \"data\": \"192.0.2.200,192.0.2.201\","
" \"csv-format\": true,"
" \"space\": \"dhcp4\""
" },"
" {"
" \"name\": \"domain-name-servers\","
" \"code\": 6,"
" \"data\": \"192.0.2.202,192.0.2.203\","
" \"csv-format\": true,"
" \"space\": \"dhcp4\""
" },"
" {"
" \"name\": \"log-servers\","
" \"code\": 7,"
" \"data\": \"10.0.0.200,10.0.0.201\","
" \"csv-format\": true,"
" \"space\": \"dhcp4\""
" },"
" {"
" \"name\": \"cookie-servers\","
" \"code\": 8,"
" \"data\": \"10.0.0.202,10.0.0.203\","
" \"csv-format\": true,"
" \"space\": \"dhcp4\""
" } ]"
" } ]"
"}"
};
/// @brief Test fixture class for testing 4-way (DORA) exchanges.
class DORATest : public Dhcpv4SrvTest {
public:
/// @brief Constructor.
///
/// Sets up fake interfaces.
DORATest()
: Dhcpv4SrvTest(),
iface_mgr_test_config_(true) {
IfaceMgr::instance().openSockets4();
}
/// @brief Interface Manager's fake configuration control.
IfaceMgrTestConfig iface_mgr_test_config_;
};
// This test verifies that the client in a SELECTING state can request
// a specific address and that this address will be assigned when
// available.
TEST_F(DORATest, selectingRequestAvailableAddress) {
Dhcp4Client client(Dhcp4Client::SELECTING);
// Configure DHCP server.
configure(DORA_CONFIGS[0], *client.getServer());
// Perform 4-way exchange with the server.
ASSERT_NO_THROW(client.doDORA(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 DHCPACK.
ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
// Response must not be relayed.
EXPECT_FALSE(resp->isRelayed());
// Make sure that the server id is present.
EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
// Make sure that the client has got the lease with the requested address.
EXPECT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
}
} // end of anonymous namespace
......@@ -28,7 +28,7 @@ using namespace isc::dhcp::test;
namespace {
/// @brief Set of JSON configurations used throughout the Rebind tests.
/// @brief Set of JSON configurations used throughout the Inform tests.
///
/// - Configuration 0:
/// - Used for testing direct traffic
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment