diff options
Diffstat (limited to 'src/bin/dhcp4/tests/dhcp4_client.cc')
-rw-r--r-- | src/bin/dhcp4/tests/dhcp4_client.cc | 600 |
1 files changed, 600 insertions, 0 deletions
diff --git a/src/bin/dhcp4/tests/dhcp4_client.cc b/src/bin/dhcp4/tests/dhcp4_client.cc new file mode 100644 index 0000000..b054a0d --- /dev/null +++ b/src/bin/dhcp4/tests/dhcp4_client.cc @@ -0,0 +1,600 @@ +// Copyright (C) 2014-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcp/dhcp4.h> +#include <dhcp/option.h> +#include <dhcp/option_int_array.h> +#include <dhcp/option_vendor.h> +#include <dhcp/tests/iface_mgr_test_config.h> +#include <dhcpsrv/lease.h> +#include <dhcp4/tests/dhcp4_client.h> +#include <util/multi_threading_mgr.h> +#include <util/range_utilities.h> +#include <boost/pointer_cast.hpp> +#include <cstdlib> + +using namespace isc::asiolink; +using namespace isc::util; + +namespace isc { +namespace dhcp { +namespace test { + +Dhcp4Client::Configuration::Configuration() + : routers_(), dns_servers_(), log_servers_(), quotes_servers_(), + serverid_("0.0.0.0"), siaddr_(IOAddress::IPV4_ZERO_ADDRESS()) { + reset(); +} + +void +Dhcp4Client::Configuration::reset() { + routers_.clear(); + dns_servers_.clear(); + log_servers_.clear(); + quotes_servers_.clear(); + serverid_ = IOAddress("0.0.0.0"); + siaddr_ = IOAddress::IPV4_ZERO_ADDRESS(); + sname_.clear(); + boot_file_name_.clear(); + lease_ = Lease4(); +} + +Dhcp4Client::Dhcp4Client(const Dhcp4Client::State& state) : + config_(), + ciaddr_(), + curr_transid_(0), + dest_addr_("255.255.255.255"), + hwaddr_(generateHWAddr()), + clientid_(), + iface_name_("eth0"), + iface_index_(ETH0_INDEX), + relay_addr_("192.0.2.2"), + requested_options_(), + server_facing_relay_addr_("10.0.0.2"), + srv_(boost::shared_ptr<NakedDhcpv4Srv>(new NakedDhcpv4Srv(0))), + state_(state), + use_relay_(false), + circuit_id_() { +} + +Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv> srv, + const Dhcp4Client::State& state) : + config_(), + ciaddr_(), + curr_transid_(0), + dest_addr_("255.255.255.255"), + fqdn_(), + hwaddr_(generateHWAddr()), + clientid_(), + iface_name_("eth0"), + iface_index_(ETH0_INDEX), + relay_addr_("192.0.2.2"), + requested_options_(), + server_facing_relay_addr_("10.0.0.2"), + srv_(srv), + state_(state), + use_relay_(false), + circuit_id_() { +} + +void +Dhcp4Client::addRequestedAddress(const IOAddress& addr) { + if (context_.query_) { + Option4AddrLstPtr opt(new Option4AddrLst(DHO_DHCP_REQUESTED_ADDRESS, + addr)); + context_.query_->addOption(opt); + } +} + +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::appendServerId() { + OptionPtr opt(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER, + config_.serverid_)); + 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); + } +} + +void +Dhcp4Client::applyConfiguration() { + Pkt4Ptr resp = context_.response_; + if (!resp) { + return; + } + + // Let's keep the old lease in case this is a response to Inform. + Lease4 old_lease = config_.lease_; + config_.reset(); + + // Routers + Option4AddrLstPtr opt_routers = boost::dynamic_pointer_cast< + Option4AddrLst>(resp->getOption(DHO_ROUTERS)); + if (opt_routers) { + config_.routers_ = opt_routers->getAddresses(); + } + // DNS Servers + 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(); + } + // Log Servers + Option4AddrLstPtr opt_log_servers = boost::dynamic_pointer_cast< + Option4AddrLst>(resp->getOption(DHO_LOG_SERVERS)); + if (opt_log_servers) { + config_.log_servers_ = opt_log_servers->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_quotes_servers->getAddresses(); + } + // Vendor Specific options + OptionVendorPtr opt_vendor = boost::dynamic_pointer_cast< + OptionVendor>(resp->getOption(DHO_VIVSO_SUBOPTIONS)); + if (opt_vendor) { + config_.vendor_suboptions_ = opt_vendor->getOptions(); + } + // siaddr + config_.siaddr_ = resp->getSiaddr(); + // sname + OptionBuffer buf = resp->getSname(); + // sname is a fixed length field holding null terminated string. Use + // of c_str() guarantees that only a useful portion (ending with null + // character) is assigned. + config_.sname_.assign(std::string(buf.begin(), buf.end()).c_str()); + // (boot)file + buf = resp->getFile(); + config_.boot_file_name_.assign(std::string(buf.begin(), buf.end()).c_str()); + // Server Identifier + OptionCustomPtr opt_serverid = boost::dynamic_pointer_cast< + OptionCustom>(resp->getOption(DHO_DHCP_SERVER_IDENTIFIER)); + if (opt_serverid) { + config_.serverid_ = opt_serverid->readAddress(); + } + + // If the message sent was Inform, we don't want to throw + // away the old lease info, just the bits about options. + if (context_.query_->getType() == DHCPINFORM) { + config_.lease_ = old_lease; + } else { + /// @todo Set the valid lifetime, t1, t2 etc. + config_.lease_ = Lease4(IOAddress(context_.response_->getYiaddr()), + context_.response_->getHWAddr(), + 0, 0, 0, time(NULL), 0, false, false, + ""); + } +} + +void +Dhcp4Client::createLease(const IOAddress& addr, const uint32_t valid_lft) { + Lease4 lease(addr, hwaddr_, 0, 0, valid_lft, + time(NULL), 0, false, false, ""); + config_.lease_ = lease; +} + +Pkt4Ptr +Dhcp4Client::createMsg(const uint8_t msg_type) { + Pkt4Ptr msg(new Pkt4(msg_type, curr_transid_++)); + msg->setHWAddr(hwaddr_); + return (msg); +} + +void +Dhcp4Client::appendExtraOptions() { + // If there are any custom options specified, add them all to the message. + if (!extra_options_.empty()) { + for (OptionCollection::iterator opt = extra_options_.begin(); + opt != extra_options_.end(); ++opt) { + // Call base class function so that unittests can add multiple + // options with the same code. + context_.query_->Pkt::addOption(opt->second); + } + } +} + +void +Dhcp4Client::appendClasses() { + for (ClientClasses::const_iterator cclass = classes_.cbegin(); + cclass != classes_.cend(); ++cclass) { + context_.query_->addClass(*cclass); + } +} + +void +Dhcp4Client::doDiscover(const boost::shared_ptr<IOAddress>& requested_addr) { + context_.query_ = createMsg(DHCPDISCOVER); + // Request options if any. + appendPRL(); + // Include FQDN or Hostname. + appendName(); + // Include Client Identifier + appendClientId(); + if (requested_addr) { + addRequestedAddress(*requested_addr); + } + // Override the default ciaddr if specified by a test. + if (!ciaddr_.unspecified()) { + context_.query_->setCiaddr(ciaddr_); + } + appendExtraOptions(); + appendClasses(); + + // 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(); + } +} + +void +Dhcp4Client::doInform(const bool set_ciaddr) { + context_.query_ = createMsg(DHCPINFORM); + // Request options if any. + appendPRL(); + // Any other options to be sent by a client. + appendExtraOptions(); + // Add classes. + appendClasses(); + // 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(); + } +} + +void +Dhcp4Client::doRelease() { + // There is no response for Release message. + context_.response_.reset(); + + if (config_.lease_.addr_.isV4Zero()) { + isc_throw(Dhcp4ClientError, "failed to send the release" + " message because client doesn't have a lease"); + } + context_.query_ = createMsg(DHCPRELEASE); + // Set ciaddr to the address which we want to release. + context_.query_->setCiaddr(config_.lease_.addr_); + // Include client identifier. + appendClientId(); + + // Remove configuration. + config_.reset(); + + // Send the message to the server. + sendMsg(context_.query_); +} + +void +Dhcp4Client::doDecline() { + if (config_.lease_.addr_.isV4Zero()) { + isc_throw(Dhcp4ClientError, "failed to send the decline" + " message because client doesn't have a lease"); + } + + context_.query_ = createMsg(DHCPDECLINE); + + // Set ciaddr to 0. + context_.query_->setCiaddr(IOAddress("0.0.0.0")); + + // Include Requested IP Address Option + addRequestedAddress(config_.lease_.addr_); + + // Include client identifier. + appendClientId(); + + // Include server identifier. + appendServerId(); + + // Remove configuration. + config_.reset(); + + // Send the message to the server. + sendMsg(context_.query_); +} + +void +Dhcp4Client::doRequest() { + context_.query_ = createMsg(DHCPREQUEST); + + // Override the default ciaddr if specified by a test. + if (!ciaddr_.unspecified()) { + context_.query_->setCiaddr(ciaddr_); + } else 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) { + if (context_.response_ && + (context_.response_->getType() == DHCPOFFER) && + (context_.response_->getYiaddr() != IOAddress("0.0.0.0"))) { + addRequestedAddress(context_.response_->getYiaddr()); + } else { + isc_throw(Dhcp4ClientError, "error sending the DHCPREQUEST because" + " the received DHCPOFFER message was invalid"); + } + } else if (state_ == INIT_REBOOT) { + addRequestedAddress(config_.lease_.addr_); + } + + // 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. + appendPRL(); + // Include FQDN or Hostname. + appendName(); + // Include Client Identifier + appendClientId(); + // Any other options to be sent by a client. + appendExtraOptions(); + // Add classes. + appendClasses(); + // 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::receiveResponse() { + context_.response_ = receiveOneMsg(); + // If the server has responded, store the configuration received. + if (context_.response_) { + applyConfiguration(); + } +} + +void +Dhcp4Client::includeClientId(const std::string& clientid) { + if (clientid.empty()) { + clientid_.reset(); + + } else { + clientid_ = ClientId::fromText(clientid); + } +} + +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) { + if (name.empty()) { + hostname_.reset(); + } else { + hostname_.reset(new OptionString(Option::V4, DHO_HOST_NAME, name)); + } +} + +HWAddrPtr +Dhcp4Client::generateHWAddr(const uint8_t htype) const { + if (htype != HTYPE_ETHER) { + isc_throw(isc::NotImplemented, + "The hardware address type " << static_cast<int>(htype) + << " is currently not supported"); + } + std::vector<uint8_t> hwaddr(HWAddr::ETHERNET_HWADDR_LEN); + // Generate ethernet hardware address by assigning random byte values. + isc::util::fillRandom(hwaddr.begin(), hwaddr.end()); + 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]; +} + +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() { + Pkt4Ptr msg = srv_->receiveOneMsg(); + if (!msg) { + return (Pkt4Ptr()); + } + + // 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->setRemotePort(msg->getLocalPort()); + msg_copy->setLocalPort(msg->getRemotePort()); + msg_copy->setIface(msg->getIface()); + msg_copy->setIndex(msg->getIndex()); + + msg_copy->unpack(); + + return (msg_copy); +} + +void +Dhcp4Client::sendMsg(const Pkt4Ptr& msg) { + srv_->shutdown_ = false; + if (use_relay_) { + try { + msg->setHops(1); + msg->setGiaddr(relay_addr_); + msg->setLocalAddr(server_facing_relay_addr_); + // Insert RAI + OptionPtr rai(new Option(Option::V4, DHO_DHCP_AGENT_OPTIONS)); + // Insert circuit id, if specified. + if (!circuit_id_.empty()) { + rai->addOption(OptionPtr(new Option(Option::V4, RAI_OPTION_AGENT_CIRCUIT_ID, + OptionBuffer(circuit_id_.begin(), + circuit_id_.end())))); + } + msg->addOption(rai); + } catch (...) { + // If relay options have already been added in the unittest, ignore + // exception on add. + } + } + // 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(msg->getLocalAddr()); + msg_copy->setLocalAddr(dest_addr_); + msg_copy->setIface(iface_name_); + msg_copy->setIndex(iface_index_); + // Copy classes + const ClientClasses& classes = msg->getClasses(); + for (ClientClasses::const_iterator cclass = classes.cbegin(); + cclass != classes.cend(); ++cclass) { + msg_copy->addClass(*cclass); + } + srv_->fakeReceive(msg_copy); + + try { + // Invoke run_one instead of run, because we want to avoid triggering + // IO service. + srv_->run_one(); + } catch (...) { + // Suppress errors, as the DHCPv4 server does. + } + + // Make sure the server processed all packets in MT. + isc::util::MultiThreadingMgr::instance().getThreadPool().wait(3); +} + +void +Dhcp4Client::setHWAddress(const std::string& hwaddr_str) { + if (hwaddr_str.empty()) { + hwaddr_.reset(); + } else { + hwaddr_.reset(new HWAddr(HWAddr::fromText(hwaddr_str))); + } +} + +void +Dhcp4Client::addExtraOption(const OptionPtr& opt) { + extra_options_.insert(std::make_pair(opt->getType(), opt)); +} + +void +Dhcp4Client::addClass(const ClientClass& client_class) { + if (!classes_.contains(client_class)) { + classes_.insert(client_class); + } +} + +} // end of namespace isc::dhcp::test +} // end of namespace isc::dhcp +} // end of namespace isc |