dhcp4_client.cc 10.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// 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/dhcp4.h>
16 17 18
#include <dhcp/option.h>
#include <dhcp/option_int_array.h>
#include <dhcpsrv/lease.h>
19
#include <dhcp4/tests/dhcp4_client.h>
20
#include <util/range_utilities.h>
21
#include <boost/pointer_cast.hpp>
22 23
#include <cstdlib>

24 25
using namespace isc::asiolink;

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

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

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

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

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

void
Dhcp4Client::applyConfiguration() {
    Pkt4Ptr resp = context_.response_;
    if (!resp) {
        return;
    }

    config_.reset();

82
    // Routers
83 84 85 86 87
    Option4AddrLstPtr opt_routers = boost::dynamic_pointer_cast<
        Option4AddrLst>(resp->getOption(DHO_ROUTERS));
    if (opt_routers) {
        config_.routers_ = opt_routers->getAddresses();
    }
88
    // DNS Servers
89 90 91 92 93
    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();
    }
94 95 96 97 98 99 100 101 102 103 104 105 106
    // 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
107 108 109 110 111 112
    OptionCustomPtr opt_serverid = boost::dynamic_pointer_cast<
        OptionCustom>(resp->getOption(DHO_DHCP_SERVER_IDENTIFIER));
    if (opt_serverid) {
        config_.serverid_ = opt_serverid->readAddress();
    }

113 114 115 116 117 118
    /// @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,
                            "");
119 120 121 122 123 124 125 126 127 128 129
}

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

130 131 132 133 134 135 136
Pkt4Ptr
Dhcp4Client::createMsg(const uint8_t msg_type) {
    Pkt4Ptr msg(new Pkt4(msg_type, curr_transid_++));
    msg->setHWAddr(hwaddr_);
    return (msg);
}

137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
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);
    }
}


163
void
164
Dhcp4Client::doInform(const bool set_ciaddr) {
165
    context_.query_ = createMsg(DHCPINFORM);
166
    // Request options if any.
167
    includePRL();
168 169 170 171 172 173 174 175
    // 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.
176
    sendMsg(context_.query_);
177
    // Expect response. If there is no response, return.
178
    context_.response_ = receiveOneMsg();
179 180 181 182 183 184 185 186
    if (!context_.response_) {
        return;
    }
    // If DHCPACK has been returned by the server, use the returned
    // configuration.
    if (context_.response_->getType() == DHCPACK) {
        applyConfiguration();
    }
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 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
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);
    }
}

255 256 257 258 259 260 261
HWAddrPtr
Dhcp4Client::generateHWAddr(const uint8_t htype) const {
    if (htype != HTYPE_ETHER) {
        isc_throw(isc::NotImplemented,
                  "The harware address type " << static_cast<int>(htype)
                  << " is currently not supported");
    }
262
    std::vector<uint8_t> hwaddr(HWAddr::ETHERNET_HWADDR_LEN);
263
    // Generate ethernet hardware address by assigning random byte values.
264
    isc::util::fillRandom(hwaddr.begin(), hwaddr.end());
265 266 267 268 269 270 271 272 273 274 275 276 277
    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];
}

278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
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);
}


295 296 297 298 299 300 301 302
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();
303 304 305 306 307 308 309 310 311 312 313 314 315

    // 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);
316 317 318 319 320 321
}

void
Dhcp4Client::sendMsg(const Pkt4Ptr& msg) {
    srv_->shutdown_ = false;
    if (use_relay_) {
322
        msg->setHops(1);
323
        msg->setGiaddr(relay_addr_);
324
        msg->setLocalAddr(server_facing_relay_addr_);
325 326 327 328 329 330
    }
    // 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()));
331
    msg_copy->setRemoteAddr(msg->getLocalAddr());
332 333 334 335 336 337 338 339 340
    msg_copy->setLocalAddr(dest_addr_);
    msg_copy->setIface("eth0");
    srv_->fakeReceive(msg_copy);
    srv_->run();
}

} // end of namespace isc::dhcp::test
} // end of namespace isc::dhcp
} // end of namespace isc