dhcp4_client.cc 13.1 KB
Newer Older
Shawn Routhier's avatar
Shawn Routhier committed
1
// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
2 3 4 5 6 7 8 9 10 11 12 13 14
//
// 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.

15
#include <config.h>
16
#include <dhcp/dhcp4.h>
17 18 19
#include <dhcp/option.h>
#include <dhcp/option_int_array.h>
#include <dhcpsrv/lease.h>
20
#include <dhcp4/tests/dhcp4_client.h>
21
#include <util/range_utilities.h>
22
#include <boost/pointer_cast.hpp>
23 24
#include <cstdlib>

25 26
using namespace isc::asiolink;

27 28 29 30
namespace isc {
namespace dhcp {
namespace test {

31
Dhcp4Client::Configuration::Configuration()
32 33
    : routers_(), dns_servers_(), log_servers_(), quotes_servers_(),
      serverid_("0.0.0.0") {
34 35 36 37 38 39 40
    reset();
}

void
Dhcp4Client::Configuration::reset() {
    routers_.clear();
    dns_servers_.clear();
41 42
    log_servers_.clear();
    quotes_servers_.clear();
43
    serverid_ = asiolink::IOAddress("0.0.0.0");
44
    lease_ = Lease4();
45 46
}

47
Dhcp4Client::Dhcp4Client(const Dhcp4Client::State& state) :
48
    config_(),
49
    ciaddr_(IOAddress("0.0.0.0")),
50 51 52
    curr_transid_(0),
    dest_addr_("255.255.255.255"),
    hwaddr_(generateHWAddr()),
53
    clientid_(),
54
    iface_name_("eth0"),
55 56 57
    relay_addr_("192.0.2.2"),
    requested_options_(),
    server_facing_relay_addr_("10.0.0.2"),
58
    srv_(boost::shared_ptr<NakedDhcpv4Srv>(new NakedDhcpv4Srv(0))),
59
    state_(state),
60 61 62
    use_relay_(false) {
}

63
Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv> srv,
64
                         const Dhcp4Client::State& state) :
65
    config_(),
66
    ciaddr_(IOAddress("0.0.0.0")),
67 68
    curr_transid_(0),
    dest_addr_("255.255.255.255"),
69
    fqdn_(),
70
    hwaddr_(generateHWAddr()),
71
    clientid_(),
72
    iface_name_("eth0"),
73 74 75
    relay_addr_("192.0.2.2"),
    requested_options_(),
    server_facing_relay_addr_("10.0.0.2"),
76
    srv_(srv),
77
    state_(state),
78 79
    use_relay_(false) {
}
80

81 82 83 84 85 86 87 88 89
void
Dhcp4Client::addRequestedAddress(const asiolink::IOAddress& addr) {
    if (context_.query_) {
        Option4AddrLstPtr opt(new Option4AddrLst(DHO_DHCP_REQUESTED_ADDRESS,
                                                 addr));
        context_.query_->addOption(opt);
    }
}

90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 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
void
Dhcp4Client::appendClientId() {
    if (!context_.query_) {
        isc_throw(Dhcp4ClientError, "pointer to the query must not be NULL"
                  " when adding Client Identifier option");
    }

    if (clientid_) {
        OptionPtr opt(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
                                 clientid_->getClientId()));
        context_.query_->addOption(opt);
    }
}

void
Dhcp4Client::appendName() {
    if (!context_.query_) {
        isc_throw(Dhcp4ClientError, "pointer to the query must not be NULL"
                  " when adding FQDN or Hostname option");
    }

    if (fqdn_) {
        context_.query_->addOption(fqdn_);

    } else if (hostname_) {
        context_.query_->addOption(hostname_);
    }
}

void
Dhcp4Client::appendPRL() {
    if (!context_.query_) {
        isc_throw(Dhcp4ClientError, "pointer to the query must not be NULL"
                  " when adding option codes to the PRL option");

    } else 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);
    }
}

138 139 140 141 142 143 144 145 146
void
Dhcp4Client::applyConfiguration() {
    Pkt4Ptr resp = context_.response_;
    if (!resp) {
        return;
    }

    config_.reset();

147
    // Routers
148 149 150 151 152
    Option4AddrLstPtr opt_routers = boost::dynamic_pointer_cast<
        Option4AddrLst>(resp->getOption(DHO_ROUTERS));
    if (opt_routers) {
        config_.routers_ = opt_routers->getAddresses();
    }
153
    // DNS Servers
154 155 156 157 158
    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();
    }
159 160 161 162 163 164 165 166 167 168 169 170 171
    // Log Servers
    Option4AddrLstPtr opt_log_servers = boost::dynamic_pointer_cast<
        Option4AddrLst>(resp->getOption(DHO_LOG_SERVERS));
    if (opt_log_servers) {
        config_.log_servers_ = opt_routers->getAddresses();
    }
    // Quotes Servers
    Option4AddrLstPtr opt_quotes_servers = boost::dynamic_pointer_cast<
        Option4AddrLst>(resp->getOption(DHO_COOKIE_SERVERS));
    if (opt_quotes_servers) {
        config_.quotes_servers_ = opt_dns_servers->getAddresses();
    }
    // Server Identifier
172 173 174 175 176 177
    OptionCustomPtr opt_serverid = boost::dynamic_pointer_cast<
        OptionCustom>(resp->getOption(DHO_DHCP_SERVER_IDENTIFIER));
    if (opt_serverid) {
        config_.serverid_ = opt_serverid->readAddress();
    }

178 179
    /// @todo Set the valid lifetime, t1, t2 etc.
    config_.lease_ = Lease4(IOAddress(context_.response_->getYiaddr()),
180
                            context_.response_->getHWAddr(),
181 182
                            0, 0, 0, 0, 0, time(NULL), 0, false, false,
                            "");
183 184 185 186 187
}

void
Dhcp4Client::createLease(const asiolink::IOAddress& addr,
                         const uint32_t valid_lft) {
188
    Lease4 lease(addr, hwaddr_, 0, 0, valid_lft, valid_lft / 2, valid_lft,
189 190 191 192
                 time(NULL), false, false, "");
    config_.lease_ = lease;
}

193 194 195 196 197 198 199
Pkt4Ptr
Dhcp4Client::createMsg(const uint8_t msg_type) {
    Pkt4Ptr msg(new Pkt4(msg_type, curr_transid_++));
    msg->setHWAddr(hwaddr_);
    return (msg);
}

200 201 202 203
void
Dhcp4Client::doDiscover(const boost::shared_ptr<IOAddress>& requested_addr) {
    context_.query_ = createMsg(DHCPDISCOVER);
    // Request options if any.
204
    appendPRL();
205
    // Include FQDN or Hostname.
206
    appendName();
207
    // Include Client Identifier
208
    appendClientId();
209
    if (requested_addr) {
210
        addRequestedAddress(*requested_addr);
211
    }
212 213 214 215
    // Override the default ciaddr if specified by a test.
    if (ciaddr_.isSpecified()) {
        context_.query_->setCiaddr(ciaddr_.get());
    }
216 217 218 219 220 221 222 223 224 225
    // 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)) {
226
        doRequest();
227 228 229
    }
}

230
void
231
Dhcp4Client::doInform(const bool set_ciaddr) {
232
    context_.query_ = createMsg(DHCPINFORM);
233
    // Request options if any.
234
    appendPRL();
235 236 237 238 239 240 241 242
    // 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.
243
    sendMsg(context_.query_);
244
    // Expect response. If there is no response, return.
245
    context_.response_ = receiveOneMsg();
246 247 248 249 250 251 252 253
    if (!context_.response_) {
        return;
    }
    // If DHCPACK has been returned by the server, use the returned
    // configuration.
    if (context_.response_->getType() == DHCPACK) {
        applyConfiguration();
    }
254 255
}

256
void
257
Dhcp4Client::doRequest() {
258 259
    context_.query_ = createMsg(DHCPREQUEST);

260 261 262 263
    // Override the default ciaddr if specified by a test.
    if (ciaddr_.isSpecified()) {
        context_.query_->setCiaddr(ciaddr_.get());
    } else if ((state_ == SELECTING) || (state_ == INIT_REBOOT)) {
264 265 266 267 268 269
        context_.query_->setCiaddr(IOAddress("0.0.0.0"));
    } else {
        context_.query_->setCiaddr(IOAddress(config_.lease_.addr_));
    }

    // Requested IP address.
270
    if (state_ == SELECTING) {
271 272 273
        if (context_.response_ &&
            (context_.response_->getType() == DHCPOFFER) &&
            (context_.response_->getYiaddr() != IOAddress("0.0.0.0"))) {
274
            addRequestedAddress(context_.response_->getYiaddr());
275 276 277 278
        } else {
            isc_throw(Dhcp4ClientError, "error sending the DHCPREQUEST because"
                      " the received DHCPOFFER message was invalid");
        }
279 280
    } else if (state_ == INIT_REBOOT) {
        addRequestedAddress(config_.lease_.addr_);
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
    }

    // 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.
297
    appendPRL();
298
    // Include FQDN or Hostname.
299
    appendName();
300
    // Include Client Identifier
301
    appendClientId();
302 303 304 305 306 307 308 309 310 311
    // 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();
    }
}

312 313
void
Dhcp4Client::includeClientId(const std::string& clientid) {
314 315 316 317 318 319
    if (clientid.empty()) {
        clientid_.reset();

    } else {
        clientid_ = ClientId::fromText(clientid);
    }
320 321
}

322 323 324 325 326 327 328 329 330 331 332 333
void
Dhcp4Client::includeFQDN(const uint8_t flags, const std::string& fqdn_name,
                         Option4ClientFqdn::DomainNameType fqdn_type) {
    fqdn_.reset(new Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
                                      fqdn_name, fqdn_type));
}

void
Dhcp4Client::includeHostname(const std::string& name) {
    hostname_.reset(new OptionString(Option::V4, DHO_HOST_NAME, name));
}

334

335 336 337 338
HWAddrPtr
Dhcp4Client::generateHWAddr(const uint8_t htype) const {
    if (htype != HTYPE_ETHER) {
        isc_throw(isc::NotImplemented,
Francis Dupont's avatar
Francis Dupont committed
339
                  "The hardware address type " << static_cast<int>(htype)
340 341
                  << " is currently not supported");
    }
342
    std::vector<uint8_t> hwaddr(HWAddr::ETHERNET_HWADDR_LEN);
343
    // Generate ethernet hardware address by assigning random byte values.
344
    isc::util::fillRandom(hwaddr.begin(), hwaddr.end());
345 346 347 348 349 350 351 352 353 354 355 356 357
    return (HWAddrPtr(new HWAddr(hwaddr, htype)));
}

void
Dhcp4Client::modifyHWAddr() {
    if (!hwaddr_) {
        hwaddr_ = generateHWAddr();
        return;
    }
    // Modify the HW address by adding 1 to its last byte.
    ++hwaddr_->hwaddr_[hwaddr_->hwaddr_.size() - 1];
}

358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
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);
}

374 375 376 377 378 379 380 381
Pkt4Ptr
Dhcp4Client::receiveOneMsg() {
    // Return empty pointer if server hasn't responded.
    if (srv_->fake_sent_.empty()) {
        return (Pkt4Ptr());
    }
    Pkt4Ptr msg = srv_->fake_sent_.front();
    srv_->fake_sent_.pop_front();
382 383 384 385 386 387 388 389 390 391 392 393 394

    // 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);
395 396 397 398 399 400
}

void
Dhcp4Client::sendMsg(const Pkt4Ptr& msg) {
    srv_->shutdown_ = false;
    if (use_relay_) {
401
        msg->setHops(1);
402
        msg->setGiaddr(relay_addr_);
403
        msg->setLocalAddr(server_facing_relay_addr_);
404 405 406 407 408 409
    }
    // 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()));
410
    msg_copy->setRemoteAddr(msg->getLocalAddr());
411
    msg_copy->setLocalAddr(dest_addr_);
412
    msg_copy->setIface(iface_name_);
413 414 415 416
    srv_->fakeReceive(msg_copy);
    srv_->run();
}

417 418
void
Dhcp4Client::setHWAddress(const std::string& hwaddr_str) {
419 420 421 422 423
    if (hwaddr_str.empty()) {
        hwaddr_.reset();
    } else {
        hwaddr_.reset(new HWAddr(HWAddr::fromText(hwaddr_str)));
    }
424 425
}

426 427 428
} // end of namespace isc::dhcp::test
} // end of namespace isc::dhcp
} // end of namespace isc