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
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
if CONFIG_BACKEND_BUNDY
# For Bundy backend, we only need to run the usual tests. There are no
......
......@@ -13,30 +13,92 @@
// PERFORMANCE OF THIS SOFTWARE.
#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 <boost/pointer_cast.hpp>
#include <cstdlib>
namespace isc {
namespace dhcp {
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() :
config_(),
curr_transid_(0),
dest_addr_("255.255.255.255"),
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))),
use_relay_(false) {
}
Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv>& srv) :
config_(),
curr_transid_(0),
dest_addr_("255.255.255.255"),
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),
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
Dhcp4Client::createMsg(const uint8_t msg_type) {
Pkt4Ptr msg(new Pkt4(msg_type, curr_transid_++));
......@@ -45,10 +107,39 @@ Dhcp4Client::createMsg(const uint8_t msg_type) {
}
void
Dhcp4Client::doInform() {
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);
}
// 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_);
// Expect response. If there is no response, return.
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
......@@ -76,6 +167,23 @@ Dhcp4Client::modifyHWAddr() {
++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
Dhcp4Client::receiveOneMsg() {
// Return empty pointer if server hasn't responded.
......@@ -84,21 +192,35 @@ Dhcp4Client::receiveOneMsg() {
}
Pkt4Ptr msg = srv_->fake_sent_.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
Dhcp4Client::sendMsg(const Pkt4Ptr& msg) {
srv_->shutdown_ = false;
if (use_relay_) {
msg->setHops(1);
msg->setGiaddr(relay_addr_);
msg->setLocalAddr(server_facing_relay_addr_);
}
// Repack the message to simulate wire-data parsing.
msg->pack();
Pkt4Ptr msg_copy(new Pkt4(static_cast<const uint8_t*>
(msg->getBuffer().getData()),
msg->getBuffer().getLength()));
msg_copy->setRemoteAddr(asiolink::IOAddress("0.0.0.0"));
msg_copy->setRemoteAddr(msg->getLocalAddr());
msg_copy->setLocalAddr(dest_addr_);
msg_copy->setIface("eth0");
srv_->fakeReceive(msg_copy);
......
......@@ -21,6 +21,7 @@
#include <dhcp4/tests/dhcp4_test_utils.h>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <set>
namespace isc {
namespace dhcp {
......@@ -50,7 +51,21 @@ public:
/// @brief Holds the last sent message from the client to the server.
Pkt4Ptr query_;
/// @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.
Dhcp4Client();
......@@ -60,15 +75,35 @@ public:
/// @param srv An instance of the DHCPv4 server to be used.
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.
///
/// This function simulates sending the DHCPINFORM message to the server
/// 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
/// functions that are not exception safe, so it may emit an exception if
/// an error occurs.
void doInform();
void doInform(const bool set_ciaddr = true);
/// @brief Generates a hardware address used by the client.
///
......@@ -104,6 +139,33 @@ public:
/// @c Dhcp4Client::getHWAddress.
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
/// client.
///
......@@ -124,13 +186,22 @@ public:
/// @param relay_addr Relay address
void useRelay(const bool use = true,
const asiolink::IOAddress& relay_addr =
asiolink::IOAddress("192.0.2.2"),
const asiolink::IOAddress& sf_relay_addr =
asiolink::IOAddress("10.0.0.2")) {
use_relay_ = use;
relay_addr_ = relay_addr;
server_facing_relay_addr_ = sf_relay_addr;
}
/// @brief Current client's configuration obtained from the server.
Configuration config_;
private:
/// @brief Stores configuration received from the server.
void applyConfiguration();
/// @brief Creates client's side DHCP message.
///
/// @param msg_type Type of the message to be created.
......@@ -166,6 +237,12 @@ private:
/// @brief Relay address to use.
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.
boost::shared_ptr<NakedDhcpv4Srv> srv_;
......
......@@ -480,20 +480,6 @@ TEST_F(Dhcpv4SrvTest, processDecline) {
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) {
// Check all possible packet types
for (int itype = 0; itype < 256; ++itype) {
......@@ -3587,6 +3573,7 @@ TEST_F(Dhcpv4SrvTest, acceptDirectRequest) {
// message is considered malformed and the accept() function should
// return false.
pkt->setGiaddr(IOAddress("192.0.10.1"));
pkt->setRemoteAddr(IOAddress("0.0.0.0"));
pkt->setLocalAddr(IOAddress("192.0.2.3"));
pkt->setIface("eth1");
EXPECT_FALSE(srv.accept(pkt));
......@@ -3621,6 +3608,20 @@ TEST_F(Dhcpv4SrvTest, acceptDirectRequest) {
pkt->setLocalAddr(IOAddress("10.0.0.1"));
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.
......
......@@ -563,16 +563,22 @@ Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) {
void
Dhcpv4SrvTest::configure(const std::string& config) {
configure(config, srv_);
}
void
Dhcpv4SrvTest::configure(const std::string& config, NakedDhcpv4Srv& srv) {
ElementPtr json = Element::fromJSON(config);
ConstElementPtr status;
// 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);
int rcode;
ConstElementPtr comment = config::parseAnswer(rcode, status);
ASSERT_EQ(0, rcode);
}
}
}; // end of isc::dhcp::test namespace
......
......@@ -407,6 +407,12 @@ public:
/// @param config String holding server configuration in JSON format.
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.
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\""
" } ]"
" } ]"
"}"
};
/// @brief Test fixture class for testing DHCPINFORM.
class InformTest : public Dhcpv4SrvTest {
public:
/// @brief Constructor.
///
/// Sets up fake interfaces.
InformTest()
: Dhcpv4SrvTest(),
iface_mgr_test_config_(true) {
IfaceMgr::instance().openSockets4();
}
/// @brief Interface Manager's fake configuration control.
IfaceMgrTestConfig iface_mgr_test_config_;
};
// Test that directly connected client's DHCPINFORM message is processed and
// DHCPACK message is sent back.
TEST_F(InformTest, directClientBroadcast) {
Dhcp4Client client;
// Configure DHCP server.
configure(INFORM_CONFIGS[0], *client.getServer());
// Request some configuration when DHCPINFORM is sent.
client.requestOptions(DHO_DOMAIN_NAME_SERVERS, DHO_ROUTERS);
// Preconfigure the client with the IP address.
client.createLease(IOAddress("10.0.0.56"), 600);
// Send DHCPINFORM message to the server.
client.doInform();
// ASSERT_NO_THROW(client.doInform());
// 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 should have been unicast to the ciaddr.
EXPECT_EQ(IOAddress("10.0.0.56"), resp->getLocalAddr());
// 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 Routers option has been received.
ASSERT_EQ(2, client.config_.routers_.size());
EXPECT_EQ("10.0.0.200", client.config_.routers_[0].toText());
EXPECT_EQ("10.0.0.201", client.config_.routers_[1].toText());
// Make sure that the DNS Servers option has been received.
ASSERT_EQ(2, client.config_.dns_servers_.size());
EXPECT_EQ("10.0.0.202", client.config_.dns_servers_[0].toText());
EXPECT_EQ("10.0.0.203", client.config_.dns_servers_[1].toText());
// Check that we can send another DHCPINFORM message using
// different ciaddr and we will get the configuration.
client.createLease(IOAddress("10.0.0.12"), 600);
// This time do not request DNS Servers option and it should not
// be returned.
client.requestOptions(DHO_ROUTERS);
// Send DHCPINFORM.
ASSERT_NO_THROW(client.doInform());
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()));
// Response should have been unicast to the ciaddr.
EXPECT_EQ(IOAddress("10.0.0.12"), resp->getLocalAddr());
// 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 Routers option has been received.
ASSERT_EQ(2, client.config_.routers_.size());
EXPECT_EQ("10.0.0.200", client.config_.routers_[0].toText());
EXPECT_EQ("10.0.0.201", client.config_.routers_[1].toText());
// Make sure that the DNS Servers option has been received.
ASSERT_TRUE(client.config_.dns_servers_.empty());
}
// This test checks that the server drops DHCPINFORM message when the
// source address and ciaddr is 0.
TEST_F(InformTest, directClientBroadcastNoAddress) {
Dhcp4Client client;
// Configure DHCP server.
configure(INFORM_CONFIGS[0], *client.getServer());
// Request some configuration when DHCPINFORM is sent.
client.requestOptions(DHO_DOMAIN_NAME_SERVERS, DHO_ROUTERS);
// Send DHCPINFORM message to the server.
ASSERT_NO_THROW(client.doInform());
// Make sure that the server dropped the message.
ASSERT_FALSE(client.getContext().response_);
}
// Test that client's DHCPINFORM message sent to a unicast address
// is received and processed by the server and that the DHCPACK is
// is sent.
TEST_F(InformTest, directClientUnicast) {
Dhcp4Client client;
// Configure DHCP server.
configure(INFORM_CONFIGS[0], *client.getServer());
// Request some configuration when DHCPINFORM is sent.
client.requestOptions(DHO_DOMAIN_NAME_SERVERS, DHO_ROUTERS);
// Preconfigure the client with the IP address.
client.createLease(IOAddress("10.0.0.56"), 600);
// Set remote address to unicast.
client.setDestAddress(IOAddress("10.0.0.1"));
// Send DHCPINFORM message to the server.
ASSERT_NO_THROW(client.doInform());
// 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 should have been unicast to the ciaddr.
EXPECT_EQ(IOAddress("10.0.0.56"), resp->getLocalAddr());
// 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 Routers option has been received.