Commit 852fb7c1 authored by Marcin Siodelski's avatar Marcin Siodelski

[3390] Implemented tests for DHCPINFORM processing.

parent fad91c5b
...@@ -89,6 +89,7 @@ dhcp4_unittests_SOURCES += config_parser_unittest.cc ...@@ -89,6 +89,7 @@ dhcp4_unittests_SOURCES += config_parser_unittest.cc
dhcp4_unittests_SOURCES += fqdn_unittest.cc dhcp4_unittests_SOURCES += fqdn_unittest.cc
dhcp4_unittests_SOURCES += marker_file.cc dhcp4_unittests_SOURCES += marker_file.cc
dhcp4_unittests_SOURCES += dhcp4_client.cc dhcp4_client.h dhcp4_unittests_SOURCES += dhcp4_client.cc dhcp4_client.h
dhcp4_unittests_SOURCES += inform_unittest.cc
if CONFIG_BACKEND_BUNDY if CONFIG_BACKEND_BUNDY
# For Bundy backend, we only need to run the usual tests. There are no # For Bundy backend, we only need to run the usual tests. There are no
......
...@@ -13,30 +13,92 @@ ...@@ -13,30 +13,92 @@
// PERFORMANCE OF THIS SOFTWARE. // PERFORMANCE OF THIS SOFTWARE.
#include <dhcp/dhcp4.h> #include <dhcp/dhcp4.h>
#include <dhcp/option.h>
#include <dhcp/option_int_array.h>
#include <dhcpsrv/lease.h>
#include <dhcp4/tests/dhcp4_client.h> #include <dhcp4/tests/dhcp4_client.h>
#include <boost/pointer_cast.hpp>
#include <cstdlib> #include <cstdlib>
namespace isc { namespace isc {
namespace dhcp { namespace dhcp {
namespace test { namespace test {
Dhcp4Client::Configuration::Configuration()
: routers_(), dns_servers_(), serverid_("0.0.0.0") {
reset();
}
void
Dhcp4Client::Configuration::reset() {
routers_.clear();
dns_servers_.clear();
serverid_ = asiolink::IOAddress("0.0.0.0");
}
Dhcp4Client::Dhcp4Client() : Dhcp4Client::Dhcp4Client() :
config_(),
curr_transid_(0), curr_transid_(0),
dest_addr_("255.255.255.255"), dest_addr_("255.255.255.255"),
hwaddr_(generateHWAddr()), hwaddr_(generateHWAddr()),
relay_addr_("10.0.0.2"), relay_addr_("192.0.2.2"),
requested_options_(),
server_facing_relay_addr_("10.0.0.2"),
srv_(boost::shared_ptr<NakedDhcpv4Srv>(new NakedDhcpv4Srv(0))), srv_(boost::shared_ptr<NakedDhcpv4Srv>(new NakedDhcpv4Srv(0))),
use_relay_(false) { use_relay_(false) {
} }
Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv>& srv) : Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv>& srv) :
config_(),
curr_transid_(0), curr_transid_(0),
dest_addr_("255.255.255.255"), dest_addr_("255.255.255.255"),
hwaddr_(generateHWAddr()), hwaddr_(generateHWAddr()),
relay_addr_("10.0.0.2"), relay_addr_("192.0.2.2"),
requested_options_(),
server_facing_relay_addr_("10.0.0.2"),
srv_(srv), srv_(srv),
use_relay_(false) { use_relay_(false) {
} }
void
Dhcp4Client::applyConfiguration() {
Pkt4Ptr resp = context_.response_;
if (!resp) {
return;
}
config_.reset();
Option4AddrLstPtr opt_routers = boost::dynamic_pointer_cast<
Option4AddrLst>(resp->getOption(DHO_ROUTERS));
if (opt_routers) {
config_.routers_ = opt_routers->getAddresses();
}
Option4AddrLstPtr opt_dns_servers = boost::dynamic_pointer_cast<
Option4AddrLst>(resp->getOption(DHO_DOMAIN_NAME_SERVERS));
if (opt_dns_servers) {
config_.dns_servers_ = opt_dns_servers->getAddresses();
}
OptionCustomPtr opt_serverid = boost::dynamic_pointer_cast<
OptionCustom>(resp->getOption(DHO_DHCP_SERVER_IDENTIFIER));
if (opt_serverid) {
config_.serverid_ = opt_serverid->readAddress();
}
/// @todo Other possible configuration.
}
void
Dhcp4Client::createLease(const asiolink::IOAddress& addr,
const uint32_t valid_lft) {
Lease4 lease(addr, &hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
0, 0, valid_lft, valid_lft / 2, valid_lft,
time(NULL), false, false, "");
config_.lease_ = lease;
}
Pkt4Ptr Pkt4Ptr
Dhcp4Client::createMsg(const uint8_t msg_type) { Dhcp4Client::createMsg(const uint8_t msg_type) {
Pkt4Ptr msg(new Pkt4(msg_type, curr_transid_++)); Pkt4Ptr msg(new Pkt4(msg_type, curr_transid_++));
...@@ -45,10 +107,39 @@ Dhcp4Client::createMsg(const uint8_t msg_type) { ...@@ -45,10 +107,39 @@ Dhcp4Client::createMsg(const uint8_t msg_type) {
} }
void void
Dhcp4Client::doInform() { Dhcp4Client::doInform(const bool set_ciaddr) {
context_.query_ = createMsg(DHCPINFORM); 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);
}
// 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.
if (set_ciaddr) {
context_.query_->setCiaddr(config_.lease_.addr_);
}
context_.query_->setLocalAddr(config_.lease_.addr_);
// Send the message to the server.
sendMsg(context_.query_); sendMsg(context_.query_);
// Expect response. If there is no response, return.
context_.response_ = receiveOneMsg(); context_.response_ = receiveOneMsg();
if (!context_.response_) {
return;
}
// If DHCPACK has been returned by the server, use the returned
// configuration.
if (context_.response_->getType() == DHCPACK) {
applyConfiguration();
}
} }
HWAddrPtr HWAddrPtr
...@@ -76,6 +167,23 @@ Dhcp4Client::modifyHWAddr() { ...@@ -76,6 +167,23 @@ Dhcp4Client::modifyHWAddr() {
++hwaddr_->hwaddr_[hwaddr_->hwaddr_.size() - 1]; ++hwaddr_->hwaddr_[hwaddr_->hwaddr_.size() - 1];
} }
void
Dhcp4Client::requestOption(const uint8_t option) {
if (option != 0) {
requested_options_.insert(option);
}
}
void
Dhcp4Client::requestOptions(const uint8_t option1, const uint8_t option2,
const uint8_t option3) {
requested_options_.clear();
requestOption(option1);
requestOption(option2);
requestOption(option3);
}
Pkt4Ptr Pkt4Ptr
Dhcp4Client::receiveOneMsg() { Dhcp4Client::receiveOneMsg() {
// Return empty pointer if server hasn't responded. // Return empty pointer if server hasn't responded.
...@@ -84,21 +192,35 @@ Dhcp4Client::receiveOneMsg() { ...@@ -84,21 +192,35 @@ Dhcp4Client::receiveOneMsg() {
} }
Pkt4Ptr msg = srv_->fake_sent_.front(); Pkt4Ptr msg = srv_->fake_sent_.front();
srv_->fake_sent_.pop_front(); srv_->fake_sent_.pop_front();
return (msg);
// Copy the original message to simulate reception over the wire.
msg->pack();
Pkt4Ptr msg_copy(new Pkt4(static_cast<const uint8_t*>
(msg->getBuffer().getData()),
msg->getBuffer().getLength()));
msg_copy->setRemoteAddr(msg->getLocalAddr());
msg_copy->setLocalAddr(msg->getRemoteAddr());
msg_copy->setIface(msg->getIface());
msg_copy->unpack();
return (msg_copy);
} }
void void
Dhcp4Client::sendMsg(const Pkt4Ptr& msg) { Dhcp4Client::sendMsg(const Pkt4Ptr& msg) {
srv_->shutdown_ = false; srv_->shutdown_ = false;
if (use_relay_) { if (use_relay_) {
msg->setHops(1);
msg->setGiaddr(relay_addr_); msg->setGiaddr(relay_addr_);
msg->setLocalAddr(server_facing_relay_addr_);
} }
// Repack the message to simulate wire-data parsing. // Repack the message to simulate wire-data parsing.
msg->pack(); msg->pack();
Pkt4Ptr msg_copy(new Pkt4(static_cast<const uint8_t*> Pkt4Ptr msg_copy(new Pkt4(static_cast<const uint8_t*>
(msg->getBuffer().getData()), (msg->getBuffer().getData()),
msg->getBuffer().getLength())); msg->getBuffer().getLength()));
msg_copy->setRemoteAddr(asiolink::IOAddress("0.0.0.0")); msg_copy->setRemoteAddr(msg->getLocalAddr());
msg_copy->setLocalAddr(dest_addr_); msg_copy->setLocalAddr(dest_addr_);
msg_copy->setIface("eth0"); msg_copy->setIface("eth0");
srv_->fakeReceive(msg_copy); srv_->fakeReceive(msg_copy);
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include <dhcp4/tests/dhcp4_test_utils.h> #include <dhcp4/tests/dhcp4_test_utils.h>
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp> #include <boost/shared_ptr.hpp>
#include <set>
namespace isc { namespace isc {
namespace dhcp { namespace dhcp {
...@@ -50,7 +51,21 @@ public: ...@@ -50,7 +51,21 @@ public:
/// @brief Holds the last sent message from the client to the server. /// @brief Holds the last sent message from the client to the server.
Pkt4Ptr query_; Pkt4Ptr query_;
/// @brief Holds the last sent message by the server to the client. /// @brief Holds the last sent message by the server to the client.
Pkt4Ptr response_; }; Pkt4Ptr response_;
};
/// @brief Holds the configuration of the client received from the
/// DHCP server.
struct Configuration {
Option4AddrLst::AddressContainer routers_;
Option4AddrLst::AddressContainer dns_servers_;
Lease4 lease_;
asiolink::IOAddress serverid_;
Configuration();
void reset();
};
/// @brief Creates a new client. /// @brief Creates a new client.
Dhcp4Client(); Dhcp4Client();
...@@ -60,15 +75,35 @@ public: ...@@ -60,15 +75,35 @@ public:
/// @param srv An instance of the DHCPv4 server to be used. /// @param srv An instance of the DHCPv4 server to be used.
Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv>& srv); Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv>& srv);
/// @brief Creates a lease for the client using the specified address
/// and valid lifetime.
///
/// This method creates the lease using the specified address and
/// valid lease lifetime. The client will use this lease in any
/// future communication with the DHCP server. One of the use cases
/// for this method is to pre-configure the client with the explicitly
/// given address before it sends the DHCPINFORM to the DHCP server.
/// The client will inject the leased address into the ciaddr field
/// of the DHCPINFORM message.
///
/// @param addr Lease address.
/// @param valid_lft Valid lifetime.
void createLease(const asiolink::IOAddress& addr, const uint32_t valid_lft);
/// @brief Sends DHCPINFORM message to the server and receives response. /// @brief Sends DHCPINFORM message to the server and receives response.
/// ///
/// This function simulates sending the DHCPINFORM message to the server /// This function simulates sending the DHCPINFORM message to the server
/// and receiving server's response (if any). /// and receiving server's response (if any).
/// ///
/// @param set_ciaddr Indicates if the ciaddr should be set for an
/// outgoing message and defaults to true. Note, that the RFC2131 mandates
/// setting the ciaddr for DHCPINFORM but the server may still want to
/// respond if the ciaddr is not set.
///
/// @throw This function doesn't thrown exceptions on its own, but it calls /// @throw This function doesn't thrown exceptions on its own, but it calls
/// functions that are not exception safe, so it may emit an exception if /// functions that are not exception safe, so it may emit an exception if
/// an error occurs. /// an error occurs.
void doInform(); void doInform(const bool set_ciaddr = true);
/// @brief Generates a hardware address used by the client. /// @brief Generates a hardware address used by the client.
/// ///
...@@ -104,6 +139,33 @@ public: ...@@ -104,6 +139,33 @@ public:
/// @c Dhcp4Client::getHWAddress. /// @c Dhcp4Client::getHWAddress.
void modifyHWAddr(); void modifyHWAddr();
/// @brief Specify an option to be requested by a client.
///
/// This function adds option code to the collection of option
/// codes to be requested by a client.
///
/// @param option Option code to be requested. The value of 0 is
/// ignored and the function is no-op.
void requestOption(const uint8_t option);
/// @brief Specifies options to be requested by the client.
///
/// This function configures the client to request options having
/// specified codes using Parameter Request List option. The default
/// value of 0 specify that the option is not requested.
///
/// If there are options specified to be requested before the function
/// is called, the new option codes override previously specified ones.
/// In order to clear the list of requested options call
/// @c requestOptions(0).
///
/// @param option1 First option to be requested.
/// @param option2 Second option to be requested (optional).
/// @param option3 Third option to be requested (optional).
void requestOptions(const uint8_t option1,
const uint8_t option2 = 0,
const uint8_t option3 = 0);
/// @brief Sets destination address for the messages being sent by the /// @brief Sets destination address for the messages being sent by the
/// client. /// client.
/// ///
...@@ -124,13 +186,22 @@ public: ...@@ -124,13 +186,22 @@ public:
/// @param relay_addr Relay address /// @param relay_addr Relay address
void useRelay(const bool use = true, void useRelay(const bool use = true,
const asiolink::IOAddress& relay_addr = const asiolink::IOAddress& relay_addr =
asiolink::IOAddress("192.0.2.2"),
const asiolink::IOAddress& sf_relay_addr =
asiolink::IOAddress("10.0.0.2")) { asiolink::IOAddress("10.0.0.2")) {
use_relay_ = use; use_relay_ = use;
relay_addr_ = relay_addr; relay_addr_ = relay_addr;
server_facing_relay_addr_ = sf_relay_addr;
} }
/// @brief Current client's configuration obtained from the server.
Configuration config_;
private: private:
/// @brief Stores configuration received from the server.
void applyConfiguration();
/// @brief Creates client's side DHCP message. /// @brief Creates client's side DHCP message.
/// ///
/// @param msg_type Type of the message to be created. /// @param msg_type Type of the message to be created.
...@@ -166,6 +237,12 @@ private: ...@@ -166,6 +237,12 @@ private:
/// @brief Relay address to use. /// @brief Relay address to use.
asiolink::IOAddress relay_addr_; asiolink::IOAddress relay_addr_;
/// @brief Collection of options codes to be requested by the client.
std::set<uint8_t> requested_options_;
/// @brief Address of the relay interface connected to the server.
asiolink::IOAddress server_facing_relay_addr_;
/// @brief Pointer to the server that the client is communicating with. /// @brief Pointer to the server that the client is communicating with.
boost::shared_ptr<NakedDhcpv4Srv> srv_; boost::shared_ptr<NakedDhcpv4Srv> srv_;
......
...@@ -480,20 +480,6 @@ TEST_F(Dhcpv4SrvTest, processDecline) { ...@@ -480,20 +480,6 @@ TEST_F(Dhcpv4SrvTest, processDecline) {
EXPECT_NO_THROW(srv.processDecline(pkt)); EXPECT_NO_THROW(srv.processDecline(pkt));
} }
TEST_F(Dhcpv4SrvTest, processInform) {
NakedDhcpv4Srv srv;
Pkt4Ptr pkt(new Pkt4(DHCPINFORM, 1234));
// Should not throw
EXPECT_NO_THROW(srv.processInform(pkt));
// Should return something
EXPECT_TRUE(srv.processInform(pkt));
// @todo Implement more reasonable tests before starting
// work on processSomething() method.
}
TEST_F(Dhcpv4SrvTest, serverReceivedPacketName) { TEST_F(Dhcpv4SrvTest, serverReceivedPacketName) {
// Check all possible packet types // Check all possible packet types
for (int itype = 0; itype < 256; ++itype) { for (int itype = 0; itype < 256; ++itype) {
...@@ -3587,6 +3573,7 @@ TEST_F(Dhcpv4SrvTest, acceptDirectRequest) { ...@@ -3587,6 +3573,7 @@ TEST_F(Dhcpv4SrvTest, acceptDirectRequest) {
// message is considered malformed and the accept() function should // message is considered malformed and the accept() function should
// return false. // return false.
pkt->setGiaddr(IOAddress("192.0.10.1")); pkt->setGiaddr(IOAddress("192.0.10.1"));
pkt->setRemoteAddr(IOAddress("0.0.0.0"));
pkt->setLocalAddr(IOAddress("192.0.2.3")); pkt->setLocalAddr(IOAddress("192.0.2.3"));
pkt->setIface("eth1"); pkt->setIface("eth1");
EXPECT_FALSE(srv.accept(pkt)); EXPECT_FALSE(srv.accept(pkt));
...@@ -3621,6 +3608,20 @@ TEST_F(Dhcpv4SrvTest, acceptDirectRequest) { ...@@ -3621,6 +3608,20 @@ TEST_F(Dhcpv4SrvTest, acceptDirectRequest) {
pkt->setLocalAddr(IOAddress("10.0.0.1")); pkt->setLocalAddr(IOAddress("10.0.0.1"));
EXPECT_TRUE(srv.accept(pkt)); EXPECT_TRUE(srv.accept(pkt));
// For the DHCPINFORM the ciaddr should be set or at least the source
// address.
pkt->setType(DHCPINFORM);
pkt->setRemoteAddr(IOAddress("10.0.0.101"));
EXPECT_TRUE(srv.accept(pkt));
// When neither ciaddr nor source addres is present, the packet should
// be dropped.
pkt->setRemoteAddr(IOAddress("0.0.0.0"));
EXPECT_FALSE(srv.accept(pkt));
// When ciaddr is set, the packet should be accepted.
pkt->setCiaddr(IOAddress("10.0.0.1"));
EXPECT_TRUE(srv.accept(pkt));
} }
// This test checks that the server rejects a message with invalid type. // This test checks that the server rejects a message with invalid type.
......
...@@ -563,16 +563,22 @@ Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) { ...@@ -563,16 +563,22 @@ Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) {
void void
Dhcpv4SrvTest::configure(const std::string& config) { Dhcpv4SrvTest::configure(const std::string& config) {
configure(config, srv_);
}
void
Dhcpv4SrvTest::configure(const std::string& config, NakedDhcpv4Srv& srv) {
ElementPtr json = Element::fromJSON(config); ElementPtr json = Element::fromJSON(config);
ConstElementPtr status; ConstElementPtr status;
// Configure the server and make sure the config is accepted // Configure the server and make sure the config is accepted
EXPECT_NO_THROW(status = configureDhcp4Server(srv_, json)); EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
ASSERT_TRUE(status); ASSERT_TRUE(status);
int rcode; int rcode;
ConstElementPtr comment = config::parseAnswer(rcode, status); ConstElementPtr comment = config::parseAnswer(rcode, status);
ASSERT_EQ(0, rcode); ASSERT_EQ(0, rcode);
} }
}; // end of isc::dhcp::test namespace }; // end of isc::dhcp::test namespace
......
...@@ -407,6 +407,12 @@ public: ...@@ -407,6 +407,12 @@ public:
/// @param config String holding server configuration in JSON format. /// @param config String holding server configuration in JSON format.
void configure(const std::string& config); void configure(const std::string& config);
/// @brief Configure specified DHCP server using JSON string.
///
/// @param config String holding server configuration in JSON format.
/// @param srv Instance of the server to be configured.
void configure(const std::string& config, NakedDhcpv4Srv& srv);
/// @brief This function cleans up after the test. /// @brief This function cleans up after the test.
virtual void TearDown(); virtual void TearDown();
......
// 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>
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 Rebind 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.
///
/// - 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.
const char* INFORM_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\""
" } ]"
" } ]"
"}",
// 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\""
" } ]"