Commit 77f8577b authored by Marcin Siodelski's avatar Marcin Siodelski

[master] Merge branch 'trac3390'

parents aec605bd cc0fc02c
......@@ -1058,6 +1058,67 @@ temporarily override a list of interface names and listen on all interfaces.
</para>
</section>
<section id="dhcp4-stateless-configuration">
<title>Stateless Configuration of DHCPv4 clients</title>
<para>The DHCPv4 server supports the stateless client configuration whereby the
client has an IP address configured (e.g. using manual configuration) and only
contacts the server to obtain other configuration parameters, e.g. DNS servers' addresses.
In order to obtain the stateless configuration parameters the client sends the
DHCPINFORM message to the server with the "ciaddr" set to the address that the
client is currently using. The server unicasts the DHCPACK message to the
client that includes the stateless configuration ("yiaddr" not set).
</para>
<para>The server will respond to the DHCPINFORM when the client is associated
with the particular subnet defined in the server's configuration. The example
subnet configuration will look like this:
<screen>
"Dhcp4": {
"subnet4": [
{
"subnet": "192.0.2.0/24"
"option-data": [ {"
"name": "domain-name-servers",
"code": 6,
"data": "192.0.2.200,192.0.2.201",
"csv-format": true,
"space": "dhcp4"
} ]
}
]
}</screen>
</para>
<para>This subnet specifies the single option which will be included in
the DHCPACK message to the client in response to DHCPINFORM. Note that
the subnet definition does not require the address pool configuration
if it will be used solely for the stateless configuration.
</para>
<para>This server will associate the subnet with the client if one of
the following conditions is met:
<itemizedlist>
<listitem>
<simpara>The DHCPINFORM is relayed and the giaddr matches the
configured subnet.</simpara>
</listitem>
<listitem>
<simpara>The DHCPINFORM is unicast from the client and the ciaddr
matches the configured subnet.</simpara>
</listitem>
<listitem>
<simpara>The DHCPINFORM is unicast from the client, the ciaddr is
not set but the source address of the IP packet matches the
configured subnet.</simpara>
</listitem>
<listitem>
<simpara>The DHCPINFORM is not relayed and the IP address on the
interface on which the message is received matches the configured
subnet.</simpara>
</listitem>
</itemizedlist>
</para>
</section>
<section id="dhcp4-client-classifier">
<title>Client Classification in DHCPv4</title>
<note>
......@@ -1699,8 +1760,8 @@ temporarily override a list of interface names and listen on all interfaces.
<para>The following standards and draft standards are currently supported:</para>
<itemizedlist>
<listitem>
<simpara><ulink url="http://tools.ietf.org/html/rfc2131">RFC 2131</ulink>: Supported messages are DISCOVER, OFFER,
REQUEST, RELEASE, ACK, and NAK.</simpara>
<simpara><ulink url="http://tools.ietf.org/html/rfc2131">RFC 2131</ulink>: Supported messages are DISCOVER (1), OFFER (2),
REQUEST (3), RELEASE (7), INFORM (8), ACK (5), and NAK(6).</simpara>
</listitem>
<listitem>
<simpara><ulink url="http://tools.ietf.org/html/rfc2132">RFC 2132</ulink>:
......
......@@ -299,7 +299,7 @@ Dhcpv4Srv::run() {
break;
case DHCPINFORM:
processInform(query);
rsp = processInform(query);
break;
default:
......@@ -1133,8 +1133,48 @@ Dhcpv4Srv::adjustRemoteAddr(const Pkt4Ptr& question, const Pkt4Ptr& response) {
static const IOAddress zero_addr("0.0.0.0");
static const IOAddress bcast_addr("255.255.255.255");
// The DHCPINFORM is slightly different than other messages in a sense
// that the server should always unicast the response to the ciaddr.
// It appears however that some clients don't set the ciaddr. We still
// want to provision these clients and we do what we can't to send the
// packet to the address where client can receive it.
if (question->getType() == DHCPINFORM) {
// If client adheres to RFC2131 it will set the ciaddr and in this
// case we always unicast our response to this address.
if (question->getCiaddr() != zero_addr) {
response->setRemoteAddr(question->getCiaddr());
// If we received DHCPINFOM via relay and the ciaddr is not set we
// will try to send the response via relay. The caveat is that the
// relay will not have any idea where to forward the packet because
// the yiaddr is likely not set. So, the broadcast flag is set so
// as the response may be broadcast.
} else if (question->isRelayed()) {
response->setRemoteAddr(question->getGiaddr());
response->setFlags(response->getFlags() | BOOTP_BROADCAST);
// If there is no ciaddr and no giaddr the only thing we can do is
// to use the source address of the packet.
} else {
response->setRemoteAddr(question->getRemoteAddr());
}
// Remote addres is now set so return.
return;
}
// If received relayed message, server responds to the relay address.
if (question->isRelayed()) {
// The client should set the ciaddr when sending the DHCPINFORM
// but in case he didn't, the relay may not be able to determine the
// address of the client, because yiaddr is not set when responding
// to Confirm and the only address available was the source address
// of the client. The source address is however not used here because
// the message is relayed. Therefore, we set the BROADCAST flag so
// as the relay can broadcast the packet.
if ((question->getType() == DHCPINFORM) &&
(question->getCiaddr() == zero_addr)) {
response->setFlags(BOOTP_BROADCAST);
}
response->setRemoteAddr(question->getGiaddr());
// If giaddr is 0 but client set ciaddr, server should unicast the
......@@ -1385,9 +1425,30 @@ Dhcpv4Srv::processDecline(Pkt4Ptr& /* decline */) {
Pkt4Ptr
Dhcpv4Srv::processInform(Pkt4Ptr& inform) {
/// @todo Implement this for real. (also see ticket #3116)
return (inform);
// DHCPINFORM MUST not include server identifier.
sanityCheck(inform, FORBIDDEN);
Pkt4Ptr ack = Pkt4Ptr(new Pkt4(DHCPACK, inform->getTransid()));
copyDefaultFields(inform, ack);
appendRequestedOptions(inform, ack);
appendRequestedVendorOptions(inform, ack);
appendBasicOptions(inform, ack);
adjustIfaceData(inform, ack);
// There are cases for the DHCPINFORM that the server receives it via
// relay but will send the response to the client's unicast address
// carried in the ciaddr. In this case, the giaddr and hops field should
// be cleared (these fields were copied by the copyDefaultFields function).
// Also Relay Agent Options should be removed if present.
if (ack->getRemoteAddr() != inform->getGiaddr()) {
ack->setHops(0);
ack->setGiaddr(IOAddress("0.0.0.0"));
ack->delOption(DHO_DHCP_AGENT_OPTIONS);
}
// The DHCPACK must contain server id.
appendServerID(ack);
return (ack);
}
const char*
......@@ -1449,6 +1510,15 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) const {
subnet = CfgMgr::instance().getSubnet4(question->getCiaddr(),
question->classes_);
// Either renewing client or the client that sends DHCPINFORM
// must set the ciaddr. But apparently some clients don't do it,
// so if the client didn't include ciaddr we will use the source
// address.
} else if ((question->getLocalAddr() != bcast) &&
(question->getRemoteAddr() != notset)) {
subnet = CfgMgr::instance().getSubnet4(question->getRemoteAddr(),
question->classes_);
// The message has been received from a directly connected client
// and this client appears to have no address. The IPv4 address
// assigned to the interface on which this message has been received,
......@@ -1493,6 +1563,11 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) const {
bool
Dhcpv4Srv::accept(const Pkt4Ptr& query) const {
// Check that the message type is accepted by the server. We rely on the
// function called to log a message if needed.
if (!acceptMessageType(query)) {
return (false);
}
// Check if the message from directly connected client (if directly
// connected) should be dropped or processed.
if (!acceptDirectRequest(query)) {
......@@ -1511,12 +1586,6 @@ Dhcpv4Srv::accept(const Pkt4Ptr& query) const {
return (false);
}
// Check that the message type is accepted by the server. We rely on the
// function called to log a message if needed.
if (!acceptMessageType(query)) {
return (false);
}
return (true);
}
......@@ -1529,6 +1598,23 @@ Dhcpv4Srv::acceptDirectRequest(const Pkt4Ptr& pkt) const {
} catch (const Exception& ex) {
return (false);
}
// The source address must not be zero for the DHCPINFORM message from
// the directly connected client because the server will not know where
// to respond if the ciaddr was not present.
static const IOAddress zero_addr("0.0.0.0");
try {
if (pkt->getType() == DHCPINFORM) {
if ((pkt->getRemoteAddr() == zero_addr) &&
(pkt->getCiaddr() == zero_addr)) {
return (false);
}
}
} catch (...) {
// If we got here, it is probably because the message type hasn't
// been set. But, this should not really happen assuming that
// we validate the message type prior to calling this function.
return (false);
}
static const IOAddress bcast("255.255.255.255");
return ((pkt->getLocalAddr() != bcast || selectSubnet(pkt)));
}
......
......@@ -239,8 +239,9 @@ protected:
/// This function accepts the following messages:
/// - all valid relayed messages,
/// - all unicast messages,
/// - all broadcast messages received on the interface for which the
/// suitable subnet exists (is configured).
/// - all broadcast messages except DHCPINFORM received on the interface
/// for which the suitable subnet exists (is configured).
/// - all DHCPINFORM messages with source address or ciaddr set.
///
/// @param query Message sent by a client.
///
......
......@@ -88,6 +88,8 @@ dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
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
......
// 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>
#include <dhcp/option.h>
#include <dhcp/option_int_array.h>
#include <dhcpsrv/lease.h>
#include <dhcp4/tests/dhcp4_client.h>
#include <util/range_utilities.h>
#include <boost/pointer_cast.hpp>
#include <cstdlib>
namespace isc {
namespace dhcp {
namespace test {
Dhcp4Client::Configuration::Configuration()
: routers_(), dns_servers_(), log_servers_(), quotes_servers_(),
serverid_("0.0.0.0") {
reset();
}
void
Dhcp4Client::Configuration::reset() {
routers_.clear();
dns_servers_.clear();
log_servers_.clear();
quotes_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_("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_("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();
// 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_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
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, e.g. lease.
}
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_++));
msg->setHWAddr(hwaddr_);
return (msg);
}
void
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
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");
}
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() {
// 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();
// 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(msg->getLocalAddr());
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
// 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.
#ifndef DHCP4_CLIENT_H
#define DHCP4_CLIENT_H
#include <asiolink/io_address.h>
#include <dhcp/hwaddr.h>
#include <dhcp/pkt4.h>
#include <dhcp4/tests/dhcp4_test_utils.h>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <set>
namespace isc {
namespace dhcp {
namespace test {
/// @brief DHCPv4 client used for unit testing.
///
/// This class implements a DHCPv4 "client" which interoperates with the
/// @c NakedDhcpv4Srv class. It calls @c NakedDhcpv4Srv::fakeReceive to
/// deliver client messages to the server for processing. The server places
/// the response in the @c NakedDhcpv4Srv::fake_sent_ container. The client
/// pops messages from this container which simulates reception of the
/// response from the server.
///
/// The client maintains the leases it acquired from the server.
///
/// The client exposes a set of functions which simulate different exchange
/// types between the client and the server. It also provides the access to
/// the objects encapsulating responses from the server so as it is possible
/// to verify from the unit test that the server's response is correct.
class Dhcp4Client : public boost::noncopyable {
public:
/// @brief Holds the DHCPv4 messages taking part in transaction between
/// the client and the server.
struct Context {
/// @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_;
};
/// @brief Holds the configuration of the client received from the
/// DHCP server.
struct Configuration {
/// @brief Holds IP addresses received in the Routers option.
Option4AddrLst::AddressContainer routers_;
/// @brief Holds IP addresses received in the DNS Servers option.
Option4AddrLst::AddressContainer dns_servers_;
/// @brief Holds IP addresses received in the Log Servers option.
Option4AddrLst::AddressContainer log_servers_;
/// @brief Holds IP addresses received in the Quotes Servers option.
Option4AddrLst::AddressContainer quotes_servers_;
/// @brief Holds a lease obtained by the client.
Lease4 lease_;
/// @brief Holds server id of the server which responded to the client's
/// request.
asiolink::IOAddress serverid_;
/// @brief Constructor.
Configuration();
/// @brief Sets configuration values to defaults.
void reset();
};
/// @brief Creates a new client.
Dhcp4Client();
/// @brief Creates a new client that communicates with a specified server.
///
/// @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). The server's response and the
/// message sent to the server is stored in the context structure and can
/// be accessed using @c getContext function.
///
/// The configuration returned by the server is stored in the
/// @c config_ public member and can be accessed directly.
///
/// @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(const bool set_ciaddr = true);
/// @brief Generates a hardware address used by the client.
///
/// It assigns random values to the bytes of the hardware address.
///
/// @param htype hardware address type. Currently the only type
/// supported is Ethernet hardware address.
///
/// @return Pointer to the generated hardware address.
HWAddrPtr generateHWAddr(const uint8_t htype = HTYPE_ETHER) const;
/// @brief Returns HW address used by the client.
HWAddrPtr getHWAddress() const;
/// @brief Returns current context.
const Context& getContext() const {
return (context_);
}
/// @brief Returns the server that the client is communicating with.
boost::shared_ptr<NakedDhcpv4Srv> getServer() const {
return (srv_);
}
/// @brief Modifies the client's HW address (adds one to it).
///
/// The HW address should be modified to test negative scenarios when the
/// client acquires a lease and tries to renew it with a different HW
/// address. The server should detect the HW address mismatch and react
/// accordingly.
///
/// The HW address modification affects the value returned by the
/// @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);