direct_client_unittest.cc 16.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// 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 <dhcp/iface_mgr.h>
#include <dhcp/pkt4.h>
#include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcpsrv/cfgmgr.h>
19
#include <dhcpsrv/lease_mgr_factory.h>
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
#include <dhcpsrv/subnet.h>
#include <dhcp4/config_parser.h>
#include <dhcp4/tests/dhcp4_test_utils.h>
#include <gtest/gtest.h>
#include <string>

using namespace isc;
using namespace isc::asiolink;
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::dhcp::test;

namespace {

/// @brief Test fixture class for testing message processing from directly
/// connected clients.
///
/// This class provides mechanisms for testing processing of DHCPv4 messages
/// from directly connected clients.
class DirectClientTest : public Dhcpv4SrvTest {
public:
    /// @brief Constructor.
    ///
    /// Initializes DHCPv4 server object used by various tests.
    DirectClientTest();

    /// @brief Configures the server with one subnet.
    ///
    /// This creates new configuration for the DHCPv4 with one subnet having
    /// a specified prefix.
    ///
    /// The subnet parameters (such as options, timers etc.) are aribitrarily
52 53 54
    /// selected. The subnet and pool mask is always /24. The real configuration
    /// would exclude .0 (network address) and .255 (broadcast address), but we
    /// ignore that fact for the sake of test simplicity.
55 56 57 58 59 60 61 62
    ///
    /// @param prefix Prefix for a subnet.
    void configureSubnet(const std::string& prefix);

    /// @brief Configures the server with two subnets.
    ///
    /// This function configures DHCPv4 server with two different subnets.
    /// The subnet parameters (such as options, timers etc.) are aribitrarily
63 64 65
    /// selected. The subnet and pool mask is /24. The real configuration
    /// would exclude .0 (network address) and .255 (broadcast address), but we
    /// ignore that fact for the sake of test simplicity.
66 67 68 69 70 71 72 73 74
    ///
    /// @param prefix1 Prefix of the first subnet to be configured.
    /// @param prefix2 Prefix of the second subnet to be configured.
    void configureTwoSubnets(const std::string& prefix1,
                             const std::string& prefix2);

    /// @brief Creates simple message from a client.
    ///
    /// This function creates a DHCPv4 message having a specified type
75 76 77 78 79
    /// (e.g. Discover, Request) and sets some properties of this
    /// message: client identifier, address and interface. The copy of
    /// this message is then created by parsing wire data of the original
    /// message. This simulates the case when the message is received and
    /// parsed by the server.
80 81 82 83 84 85 86 87 88
    ///
    /// @param msg_type Type of the message to be created.
    /// @param iface Name of the interface on which the message has been
    /// "received" by the server.
    ///
    /// @return Generated message.
    Pkt4Ptr createClientMessage(const uint16_t msg_type,
                                const std::string& iface);

89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
    /// @brief Creates simple message from a client.
    ///
    /// This function configures a client's message by adding client identifier,
    /// setting interface and addresses. The copy of this message is then
    /// created by parsing wire data of the original message. This simulates the
    /// case when the message is received and parsed by the server.
    ///
    /// @param msg Caller supplied message to be configured. This object must
    /// not be NULL.
    /// @param iface Name of the interface on which the message has been
    /// "received" by the server.
    ///
    /// @return Configured and parsed message.
    Pkt4Ptr createClientMessage(const Pkt4Ptr &msg, const std::string& iface);

104 105
};

106
DirectClientTest::DirectClientTest() : Dhcpv4SrvTest() {
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
}

void
DirectClientTest::configureSubnet(const std::string& prefix) {
    std::ostringstream config;
    config << "{ \"interfaces\": [ \"*\" ],"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"option-data\": [ ],"
        "\"subnet4\": [ { "
        "    \"pool\": [ \"" << prefix << "/24\" ],"
        "    \"subnet\": \"" << prefix << "/24\", "
        "    \"rebind-timer\": 2000, "
        "    \"renew-timer\": 1000, "
        "    \"valid-lifetime\": 4000"
        "} ],"
        "\"valid-lifetime\": 4000 }";

    configure(config.str());

}

void
DirectClientTest::configureTwoSubnets(const std::string& prefix1,
                                      const std::string& prefix2) {
    std::ostringstream config;
    config << "{ \"interfaces\": [ \"*\" ],"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"option-data\": [ ],"
        "\"subnet4\": [ { "
        "    \"pool\": [ \"" << prefix1 << "/24\" ],"
        "    \"subnet\": \"" << prefix1 << "/24\", "
        "    \"rebind-timer\": 2000, "
        "    \"renew-timer\": 1000, "
        "    \"valid-lifetime\": 4000"
        " },"
        "{ "
        "    \"pool\": [ \"" << prefix2 << "/24\" ],"
        "    \"subnet\": \"" << prefix2 << "/24\", "
        "    \"rebind-timer\": 2000, "
        "    \"renew-timer\": 1000, "
        "    \"valid-lifetime\": 4000"
        "} ],"
        "\"valid-lifetime\": 4000 }";

    configure(config.str());
}

Pkt4Ptr
DirectClientTest:: createClientMessage(const uint16_t msg_type,
                                       const std::string& iface) {
    // Create a source packet.
    Pkt4Ptr msg = Pkt4Ptr(new Pkt4(msg_type, 1234));
161 162 163 164 165 166 167
    return (createClientMessage(msg, iface));

}

Pkt4Ptr
DirectClientTest::createClientMessage(const Pkt4Ptr& msg,
                                      const std::string& iface) {
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
    msg->setRemoteAddr(IOAddress("255.255.255.255"));
    msg->addOption(generateClientId());
    msg->setIface(iface);

    // Create copy of this packet by parsing its wire data. Make sure that the
    // local and remote address are set like it was a message sent from the
    // directly connected client.
    Pkt4Ptr received;
    createPacketFromBuffer(msg, received);
    received->setIface(iface);
    received->setLocalAddr(IOAddress("255.255.255.255"));
    received->setRemoteAddr(IOAddress("0.0.0.0"));

    return (received);
}

// This test checks that the message from directly connected client
// is processed and that client is offered IPv4 address from the subnet which
// is suitable for the local interface on which the client's message is
// received. This test uses two subnets, with two active interfaces which IP
// addresses belong to these subnets. The address offered to the client
// which message has been sent over eth0 should belong to a different
// subnet than the address offered for the client sending its message
// via eth1.
TEST_F(DirectClientTest,  twoSubnets) {
    // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
    IfaceMgrTestConfig iface_config(true);
    // After creating interfaces we have to open sockets as it is required
    // by the message processing code.
    ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
    // Add two subnets: address on eth0 belongs to the second subnet,
    // address on eth1 belongs to the first subnet.
    ASSERT_NO_FATAL_FAILURE(configureTwoSubnets("192.0.2.0", "10.0.0.0"));
    // Create Discover and simulate reception of this message through eth0.
    Pkt4Ptr dis = createClientMessage(DHCPDISCOVER, "eth0");
    srv_.fakeReceive(dis);
    // Create Request and simulate reception of this message through eth1.
    Pkt4Ptr req = createClientMessage(DHCPREQUEST, "eth1");
    srv_.fakeReceive(req);

    // Process clients' messages.
    srv_.run();

    // Check that the server did send reposonses.
    ASSERT_EQ(2, srv_.fake_sent_.size());

214
    // Make sure that we received a response.
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
    Pkt4Ptr response = srv_.fake_sent_.front();
    ASSERT_TRUE(response);
    srv_.fake_sent_.pop_front();

    // Client should get an Offer (not a NAK).
    ASSERT_EQ(DHCPOFFER, response->getType());
    // Check that the offered address belongs to the suitable subnet.
    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(response->getYiaddr());
    ASSERT_TRUE(subnet);
    EXPECT_EQ("10.0.0.0", subnet->get().first.toText());

    // A client that sent Request over the other interface should get Ack.
    response = srv_.fake_sent_.front();
    ASSERT_TRUE(response);

    // Client should get an Ack (not a NAK).
    ASSERT_EQ(DHCPACK, response->getType());
    // Check that the offered address belongs to the suitable subnet.
    subnet = CfgMgr::instance().getSubnet4(response->getYiaddr());
    ASSERT_TRUE(subnet);
    EXPECT_EQ("192.0.2.0", subnet->get().first.toText());

}

239 240 241 242
// This test checks that server selects a subnet when receives a message
// through an interface for which the subnet has been configured. This
// interface has IPv4 address assigned which belongs to this subnet.
// This test also verifies that when the message is received through
243 244 245
// the interface for which there is no suitable subnet, the message
// is discarded.
TEST_F(DirectClientTest, oneSubnet) {
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
    // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
    IfaceMgrTestConfig iface_config(true);
    // After creating interfaces we have to open sockets as it is required
    // by the message processing code.
    ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
    // Add a subnet which will be selected when a message from directly
    // connected client is received through interface eth0.
    ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
    // Create Discover and simulate reception of this message through eth0.
    Pkt4Ptr dis = createClientMessage(DHCPDISCOVER, "eth0");
    srv_.fakeReceive(dis);
    // Create Request and simulate reception of this message through eth1.
    Pkt4Ptr req = createClientMessage(DHCPDISCOVER, "eth1");
    srv_.fakeReceive(req);

    // Process clients' messages.
    srv_.run();

264 265 266
    // Check that the server sent one response for the message received
    // through eth0. The other client's message should be dicarded.
    ASSERT_EQ(1, srv_.fake_sent_.size());
267

268 269
    // Check the response. The first Discover was sent via eth0 for which
    // the subnet has been configured.
270 271 272 273 274 275 276 277 278 279 280 281 282
    Pkt4Ptr response = srv_.fake_sent_.front();
    ASSERT_TRUE(response);
    srv_.fake_sent_.pop_front();

    // Since Discover has been received through the interface for which
    // the subnet has been configured, the server should respond with
    // an Offer message.
    ASSERT_EQ(DHCPOFFER, response->getType());
    // Check that the offered address belongs to the suitable subnet.
    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(response->getYiaddr());
    ASSERT_TRUE(subnet);
    EXPECT_EQ("10.0.0.0", subnet->get().first.toText());

283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
}

// This test verifies that the server uses ciaddr to select a subnet for a
// client which renews its lease.
TEST_F(DirectClientTest, renew) {
    // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
    IfaceMgrTestConfig iface_config(true);
    // After creating interfaces we have to open sockets as it is required
    // by the message processing code.
    ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
    // Add a subnet.
    ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
    // Make sure that the subnet has been really added. Also, the subnet
    // will be needed to create a lease for a client.
    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("10.0.0.10"));
    // Create a lease for a client that we will later renewed. By explicitly
    // creating a lease we will get to know the lease parameters, such as
    // leased address etc.
    const uint8_t hwaddr[] = { 1, 2, 3, 4, 5, 6 };
    Lease4Ptr lease(new Lease4(IOAddress("10.0.0.10"), hwaddr, sizeof(hwaddr),
                               &generateClientId()->getData()[0],
                               generateClientId()->getData().size(),
                               100, 50, 75, time(NULL),
                               subnet->getID()));
    LeaseMgrFactory::instance().addLease(lease);

    // Create a Request to renew client's lease. The renew request is unicast
    // through eth1. Note, that in case of renewal the client unicasts its
    // Request and sets the ciaddr. The server is supposed to use ciaddr to
    // pick the subnet for the client. In order to make sure that the server
    // uses ciaddr, we simulate reception of the packet through eth1, for which
    // there is no subnet for directly connected clients.
    Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
    req->setCiaddr(IOAddress("10.0.0.10"));
    req = createClientMessage(req, "eth1");
    req->setLocalAddr(IOAddress("10.0.0.1"));
    req->setRemoteAddr(req->getCiaddr());

    srv_.fakeReceive(req);

    // Process clients' messages.
    srv_.run();

    // Check that the server did send reposonse.
    ASSERT_EQ(1, srv_.fake_sent_.size());
    Pkt4Ptr response = srv_.fake_sent_.front();
329 330
    ASSERT_TRUE(response);

331 332 333 334 335
    ASSERT_EQ(DHCPACK, response->getType());
    // Check that the offered address belongs to the suitable subnet.
    subnet = CfgMgr::instance().getSubnet4(response->getYiaddr());
    ASSERT_TRUE(subnet);
    EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
336 337 338

}

339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
// This test verifies that when a client in the Rebinding state broadcasts
// a Request message through an interface for which a subnet is configured,
// the server responds to this Request. It also verifies that when such a
// Request is sent through the interface for which there is no subnet configured
// the client's message is discarded.
TEST_F(DirectClientTest, rebind) {
    // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
    IfaceMgrTestConfig iface_config(true);
    // After creating interfaces we have to open sockets as it is required
    // by the message processing code.
    ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
    // Add a subnet.
    ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
    // Make sure that the subnet has been really added. Also, the subnet
    // will be needed to create a lease for a client.
    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("10.0.0.10"));
    // Create a lease, which will be later renewed. By explicitly creating a
    // lease we will know the lease parameters, such as leased address etc.
    const uint8_t hwaddr[] = { 1, 2, 3, 4, 5, 6 };
    Lease4Ptr lease(new Lease4(IOAddress("10.0.0.10"), hwaddr, sizeof(hwaddr),
                               &generateClientId()->getData()[0],
                               generateClientId()->getData().size(),
                               100, 50, 75, time(NULL),
                               subnet->getID()));
    LeaseMgrFactory::instance().addLease(lease);

    // Broadcast Request through an interface for which there is no subnet
    // configured. This messag should be discarded by the server.
    Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
    req->setCiaddr(IOAddress("10.0.0.10"));
    req = createClientMessage(req, "eth1");
    req->setRemoteAddr(req->getCiaddr());

    srv_.fakeReceive(req);

    // Broadcast another Request through an interface for which there is
    // a subnet configured. The server should generate a response.
    req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 5678));
    req->setCiaddr(IOAddress("10.0.0.10"));
    req = createClientMessage(req, "eth0");
    req->setRemoteAddr(req->getCiaddr());

    srv_.fakeReceive(req);

    // Process clients' messages.
    srv_.run();

    // Check that the server did send exactly one reposonse.
    ASSERT_EQ(1, srv_.fake_sent_.size());
    Pkt4Ptr response = srv_.fake_sent_.front();
    ASSERT_TRUE(response);

    // Make sure that the server responsed with ACK, not NAK.
    ASSERT_EQ(DHCPACK, response->getType());
    // Make sure that the response is generated for the second Request
    // (transmitted over eth0).
    EXPECT_EQ(5678, response->getTransid());
    // Check that the offered address belongs to the suitable subnet.
    subnet = CfgMgr::instance().getSubnet4(response->getYiaddr());
    ASSERT_TRUE(subnet);
    EXPECT_EQ("10.0.0.0", subnet->get().first.toText());

}
402

403
}