Parcourir la source

[master] Merge branch 'trac3390'

Marcin Siodelski il y a 10 ans
Parent
commit
77f8577b1d

+ 63 - 2
doc/guide/dhcp4-srv.xml

@@ -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>:

+ 96 - 10
src/bin/dhcp4/dhcp4_srv.cc

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

+ 3 - 2
src/bin/dhcp4/dhcp4_srv.h

@@ -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.
     ///

+ 2 - 0
src/bin/dhcp4/tests/Makefile.am

@@ -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

+ 247 - 0
src/bin/dhcp4/tests/dhcp4_client.cc

@@ -0,0 +1,247 @@
+// 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

+ 285 - 0
src/bin/dhcp4/tests/dhcp4_client.h

@@ -0,0 +1,285 @@
+// 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);
+
+    /// @brief Sets destination address for the messages being sent by the
+    /// client.
+    ///
+    /// By default, the client uses broadcast address 255.255.255.255 to
+    /// communicate with the server. In certain cases it may be desired
+    /// that different address is used. This function sets the new address
+    /// for all future exchanges with the server.
+    ///
+    /// @param dest_addr New destination address.
+    void setDestAddress(const asiolink::IOAddress& dest_addr) {
+        dest_addr_ = dest_addr;
+    }
+
+    /// @brief Simulate sending messages through a relay.
+    ///
+    /// @param use Parameter which 'true' value indicates that client should
+    /// simulate sending messages via relay.
+    /// @param relay_addr Relay address
+    /// @param sf_relay_addr Server facing relay address.
+    void useRelay(const bool use = true,
+                  const asiolink::IOAddress& relay_addr =
+                  asiolink::IOAddress("192.0.2.2"),
+                  const asiolink::IOAddress& sf_relay_addr =
+                  asiolink::IOAddress("10.0.0.2")) {
+        use_relay_ = use;
+        relay_addr_ = relay_addr;
+        server_facing_relay_addr_ = sf_relay_addr;
+    }
+
+    /// @brief Current client's configuration obtained from the server.
+    Configuration config_;
+
+private:
+
+    /// @brief Stores configuration received from the server.
+    ///
+    /// This methods stores the configuration obtained from the DHCP server
+    /// in the @c Configuration structure. This configuration includes:
+    /// - obtained lease
+    /// - server id of the server that provided the configuration
+    /// - lease
+    /// - selected options (used by unit tests):
+    ///   - DNS Servers
+    ///   - Routers
+    ///   - Log Servers
+    ///   - Quotes Servers
+    void applyConfiguration();
+
+    /// @brief Creates client's side DHCP message.
+    ///
+    /// @param msg_type Type of the message to be created.
+    /// @return An instance of the message created.
+    Pkt4Ptr createMsg(const uint8_t msg_type);
+
+    /// @brief Simulates reception of the message from the server.
+    ///
+    /// @return Received message.
+    Pkt4Ptr receiveOneMsg();
+
+    /// @brief Simulates sending a message to the server.
+    ///
+    /// This function instantly triggers processing of the message by the
+    /// server. The server's response can be gathered by invoking the
+    /// @c receiveOneMsg function.
+    ///
+    /// @param msg Message to be sent.
+    void sendMsg(const Pkt4Ptr& msg);
+
+    /// @brief Current context (sent and received message).
+    Context context_;
+
+    /// @biref Current transaction id (altered on each send).
+    uint32_t curr_transid_;
+
+    /// @brief Currently used destination address.
+    asiolink::IOAddress dest_addr_;
+
+    /// @brief Current hardware address of the client.
+    HWAddrPtr hwaddr_;
+
+    /// @brief Relay address to use.
+    asiolink::IOAddress relay_addr_;
+
+    /// @brief Collection of options codes to be requested by the client.
+    std::set<uint8_t> requested_options_;
+
+    /// @brief Address of the relay interface connected to the server.
+    asiolink::IOAddress server_facing_relay_addr_;
+
+    /// @brief Pointer to the server that the client is communicating with.
+    boost::shared_ptr<NakedDhcpv4Srv> srv_;
+
+    /// @brief Enable relaying messages to the server.
+    bool use_relay_;
+};
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // DHCP4_CLIENT

+ 15 - 14
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -480,20 +480,6 @@ TEST_F(Dhcpv4SrvTest, processDecline) {
     EXPECT_NO_THROW(srv.processDecline(pkt));
 }
 
-TEST_F(Dhcpv4SrvTest, processInform) {
-    NakedDhcpv4Srv srv;
-    Pkt4Ptr pkt(new Pkt4(DHCPINFORM, 1234));
-
-    // Should not throw
-    EXPECT_NO_THROW(srv.processInform(pkt));
-
-    // Should return something
-    EXPECT_TRUE(srv.processInform(pkt));
-
-    // @todo Implement more reasonable tests before starting
-    // work on processSomething() method.
-}
-
 TEST_F(Dhcpv4SrvTest, serverReceivedPacketName) {
     // Check all possible packet types
     for (int itype = 0; itype < 256; ++itype) {
@@ -3587,6 +3573,7 @@ TEST_F(Dhcpv4SrvTest, acceptDirectRequest) {
     // message is considered malformed and the accept() function should
     // return false.
     pkt->setGiaddr(IOAddress("192.0.10.1"));
+    pkt->setRemoteAddr(IOAddress("0.0.0.0"));
     pkt->setLocalAddr(IOAddress("192.0.2.3"));
     pkt->setIface("eth1");
     EXPECT_FALSE(srv.accept(pkt));
@@ -3621,6 +3608,20 @@ TEST_F(Dhcpv4SrvTest, acceptDirectRequest) {
     pkt->setLocalAddr(IOAddress("10.0.0.1"));
     EXPECT_TRUE(srv.accept(pkt));
 
+    // For the DHCPINFORM the ciaddr should be set or at least the source
+    // address.
+    pkt->setType(DHCPINFORM);
+    pkt->setRemoteAddr(IOAddress("10.0.0.101"));
+    EXPECT_TRUE(srv.accept(pkt));
+
+    // When neither ciaddr nor source addres is present, the packet should
+    // be dropped.
+    pkt->setRemoteAddr(IOAddress("0.0.0.0"));
+    EXPECT_FALSE(srv.accept(pkt));
+
+    // When ciaddr is set, the packet should be accepted.
+    pkt->setCiaddr(IOAddress("10.0.0.1"));
+    EXPECT_TRUE(srv.accept(pkt));
 }
 
 // This test checks that the server rejects a message with invalid type.

+ 8 - 2
src/bin/dhcp4/tests/dhcp4_test_utils.cc

@@ -563,16 +563,22 @@ Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) {
 
 void
 Dhcpv4SrvTest::configure(const std::string& config) {
+    configure(config, srv_);
+}
+
+void
+Dhcpv4SrvTest::configure(const std::string& config, NakedDhcpv4Srv& srv) {
     ElementPtr json = Element::fromJSON(config);
     ConstElementPtr status;
 
     // Configure the server and make sure the config is accepted
-    EXPECT_NO_THROW(status = configureDhcp4Server(srv_, json));
+    EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
     ASSERT_TRUE(status);
     int rcode;
     ConstElementPtr comment = config::parseAnswer(rcode, status);
     ASSERT_EQ(0, rcode);
-}
+ }
+
 
 
 }; // end of isc::dhcp::test namespace

+ 7 - 0
src/bin/dhcp4/tests/dhcp4_test_utils.h

@@ -204,6 +204,7 @@ public:
     using Dhcpv4Srv::acceptMessageType;
     using Dhcpv4Srv::selectSubnet;
     using Dhcpv4Srv::VENDOR_CLASS_PREFIX;
+    using Dhcpv4Srv::shutdown_;
 };
 
 class Dhcpv4SrvTest : public ::testing::Test {
@@ -406,6 +407,12 @@ public:
     /// @param config String holding server configuration in JSON format.
     void configure(const std::string& config);
 
+    /// @brief Configure specified DHCP server using JSON string.
+    ///
+    /// @param config String holding server configuration in JSON format.
+    /// @param srv Instance of the server to be configured.
+    void configure(const std::string& config, NakedDhcpv4Srv& srv);
+
     /// @brief This function cleans up after the test.
     virtual void TearDown();
 

+ 378 - 0
src/bin/dhcp4/tests/inform_unittest.cc

@@ -0,0 +1,378 @@
+// 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 <config.h>
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/tests/dhcp4_client.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Set of JSON configurations used throughout the Rebind tests.
+///
+/// - Configuration 0:
+///   - Used for testing direct traffic
+///   - 1 subnet: 10.0.0.0/24
+///   - 1 pool: 10.0.0.10-10.0.0.100
+///   - Router option present: 10.0.0.200 and 10.0.0.201
+///   - Domain Name Server option present: 10.0.0.202, 10.0.0.203.
+///   - Log Servers option present: 192.0.2.200 and 192.0.2.201
+///   - Quotes Servers option present: 192.0.2.202, 192.0.2.203.
+///
+/// - Configuration 1:
+///   - Use for testing relayed messages
+///   - 1 subnet: 192.0.2.0/24
+///   - Router option present: 192.0.2.200 and 192.0.2.201
+///   - Domain Name Server option present: 192.0.2.202, 192.0.2.203.
+///   - Log Servers option present: 192.0.2.200 and 192.0.2.201
+///   - Quotes Servers option present: 192.0.2.202, 192.0.2.203.
+const char* INFORM_CONFIGS[] = {
+// Configuration 0
+    "{ \"interfaces\": [ \"all\" ],"
+        "\"valid-lifetime\": 600,"
+        "\"subnet4\": [ { "
+        "    \"subnet\": \"10.0.0.0/24\", "
+        "    \"pool\": [ \"10.0.0.10-10.0.0.100\" ],"
+        "    \"option-data\": [ {"
+        "        \"name\": \"routers\","
+        "        \"code\": 3,"
+        "        \"data\": \"10.0.0.200,10.0.0.201\","
+        "        \"csv-format\": true,"
+        "        \"space\": \"dhcp4\""
+        "    },"
+        "    {"
+        "        \"name\": \"domain-name-servers\","
+        "        \"code\": 6,"
+        "        \"data\": \"10.0.0.202,10.0.0.203\","
+        "        \"csv-format\": true,"
+        "        \"space\": \"dhcp4\""
+        "    },"
+        "    {"
+        "        \"name\": \"log-servers\","
+        "        \"code\": 7,"
+        "        \"data\": \"10.0.0.200,10.0.0.201\","
+        "        \"csv-format\": true,"
+        "        \"space\": \"dhcp4\""
+        "    },"
+        "    {"
+        "        \"name\": \"cookie-servers\","
+        "        \"code\": 8,"
+        "        \"data\": \"10.0.0.202,10.0.0.203\","
+        "        \"csv-format\": true,"
+        "        \"space\": \"dhcp4\""
+        "    } ]"
+        " } ]"
+    "}",
+
+// Configuration 1
+    "{ \"interfaces\": [ \"all\" ],"
+        "\"valid-lifetime\": 600,"
+        "\"subnet4\": [ { "
+        "    \"subnet\": \"192.0.2.0/24\", "
+        "    \"option-data\": [ {"
+        "        \"name\": \"routers\","
+        "        \"code\": 3,"
+        "        \"data\": \"192.0.2.200,192.0.2.201\","
+        "        \"csv-format\": true,"
+        "        \"space\": \"dhcp4\""
+        "    },"
+        "    {"
+        "        \"name\": \"domain-name-servers\","
+        "        \"code\": 6,"
+        "        \"data\": \"192.0.2.202,192.0.2.203\","
+        "        \"csv-format\": true,"
+        "        \"space\": \"dhcp4\""
+        "    },"
+        "    {"
+        "        \"name\": \"log-servers\","
+        "        \"code\": 7,"
+        "        \"data\": \"10.0.0.200,10.0.0.201\","
+        "        \"csv-format\": true,"
+        "        \"space\": \"dhcp4\""
+        "    },"
+        "    {"
+        "        \"name\": \"cookie-servers\","
+        "        \"code\": 8,"
+        "        \"data\": \"10.0.0.202,10.0.0.203\","
+        "        \"csv-format\": true,"
+        "        \"space\": \"dhcp4\""
+        "    } ]"
+        " } ]"
+    "}"
+};
+
+/// @brief Test fixture class for testing DHCPINFORM.
+class InformTest : public Dhcpv4SrvTest {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Sets up fake interfaces.
+    InformTest()
+        : Dhcpv4SrvTest(),
+          iface_mgr_test_config_(true) {
+        IfaceMgr::instance().openSockets4();
+    }
+
+    /// @brief Interface Manager's fake configuration control.
+    IfaceMgrTestConfig iface_mgr_test_config_;
+
+};
+
+// Test that directly connected client's DHCPINFORM message is processed and
+// DHCPACK message is sent back.
+TEST_F(InformTest, directClientBroadcast) {
+    Dhcp4Client client;
+    // Configure DHCP server.
+    configure(INFORM_CONFIGS[0], *client.getServer());
+    // Request some configuration when DHCPINFORM is sent.
+    client.requestOptions(DHO_LOG_SERVERS, DHO_COOKIE_SERVERS);
+    // Preconfigure the client with the IP address.
+    client.createLease(IOAddress("10.0.0.56"), 600);
+
+    // Send DHCPINFORM message to the server.
+    ASSERT_NO_THROW(client.doInform());
+
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    Pkt4Ptr resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Response should have been unicast to the ciaddr.
+    EXPECT_EQ(IOAddress("10.0.0.56"), resp->getLocalAddr());
+    // Response must not be relayed.
+    EXPECT_FALSE(resp->isRelayed());
+    // Make sure that the server id is present.
+    EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+
+    // Make sure that the Routers option has been received.
+    ASSERT_EQ(2, client.config_.routers_.size());
+    EXPECT_EQ("10.0.0.200", client.config_.routers_[0].toText());
+    EXPECT_EQ("10.0.0.201", client.config_.routers_[1].toText());
+    // Make sure that the DNS Servers option has been received.
+    ASSERT_EQ(2, client.config_.dns_servers_.size());
+    EXPECT_EQ("10.0.0.202", client.config_.dns_servers_[0].toText());
+    EXPECT_EQ("10.0.0.203", client.config_.dns_servers_[1].toText());
+    // Make sure that the Log Servers option has been received.
+    ASSERT_EQ(2, client.config_.quotes_servers_.size());
+    EXPECT_EQ("10.0.0.200", client.config_.routers_[0].toText());
+    EXPECT_EQ("10.0.0.201", client.config_.routers_[1].toText());
+    // Make sure that the Quotes Servers option has been received.
+    ASSERT_EQ(2, client.config_.log_servers_.size());
+    EXPECT_EQ("10.0.0.202", client.config_.dns_servers_[0].toText());
+    EXPECT_EQ("10.0.0.203", client.config_.dns_servers_[1].toText());
+
+    // Check that we can send another DHCPINFORM message using
+    // different ciaddr and we will get the configuration.
+    client.createLease(IOAddress("10.0.0.12"), 600);
+    // This time do not request Quotes Servers option and it should not
+    // be returned.
+    client.requestOptions(DHO_LOG_SERVERS);
+
+    // Send DHCPINFORM.
+    ASSERT_NO_THROW(client.doInform());
+
+    // Make sure that the server responded.
+    resp = client.getContext().response_;
+    ASSERT_TRUE(resp);
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Response should have been unicast to the ciaddr.
+    EXPECT_EQ(IOAddress("10.0.0.12"), resp->getLocalAddr());
+    // Response must not be relayed.
+    EXPECT_FALSE(resp->isRelayed());
+
+    // Make sure that the server id is present.
+    EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+    // Make sure that the Routers option has been received.
+    ASSERT_EQ(2, client.config_.routers_.size());
+    EXPECT_EQ("10.0.0.200", client.config_.routers_[0].toText());
+    EXPECT_EQ("10.0.0.201", client.config_.routers_[1].toText());
+    // Make sure that the DNS Servers option has been received.
+    ASSERT_EQ(2, client.config_.dns_servers_.size());
+    EXPECT_EQ("10.0.0.202", client.config_.dns_servers_[0].toText());
+    EXPECT_EQ("10.0.0.203", client.config_.dns_servers_[1].toText());
+    // Make sure that the Quotes Servers option hasn't been received.
+    ASSERT_TRUE(client.config_.quotes_servers_.empty());
+}
+
+// This test checks that the server drops DHCPINFORM message when the
+// source address and ciaddr is 0.
+TEST_F(InformTest, directClientBroadcastNoAddress) {
+    Dhcp4Client client;
+    // Configure DHCP server.
+    configure(INFORM_CONFIGS[0], *client.getServer());
+    // Request some configuration when DHCPINFORM is sent.
+    client.requestOptions(DHO_LOG_SERVERS, DHO_COOKIE_SERVERS);
+    // Send DHCPINFORM message to the server.
+    ASSERT_NO_THROW(client.doInform());
+    // Make sure that the server dropped the message.
+    ASSERT_FALSE(client.getContext().response_);
+}
+
+// Test that client's DHCPINFORM message sent to a unicast address
+// is received and processed by the server and that the DHCPACK is
+// is sent.
+TEST_F(InformTest, directClientUnicast) {
+    Dhcp4Client client;
+    // Configure DHCP server.
+    configure(INFORM_CONFIGS[0], *client.getServer());
+    // Preconfigure the client with the IP address.
+    client.createLease(IOAddress("10.0.0.56"), 600);
+    // Set remote address to unicast.
+    client.setDestAddress(IOAddress("10.0.0.1"));
+    // Send DHCPINFORM message to the server.
+    ASSERT_NO_THROW(client.doInform());
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    Pkt4Ptr resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Response should have been unicast to the ciaddr.
+    EXPECT_EQ(IOAddress("10.0.0.56"), resp->getLocalAddr());
+    // Response must not be relayed.
+    EXPECT_FALSE(resp->isRelayed());
+    // Make sure that the server id is present.
+    EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+    // Make sure that the Routers option has been received.
+    ASSERT_EQ(2, client.config_.routers_.size());
+    EXPECT_EQ("10.0.0.200", client.config_.routers_[0].toText());
+    EXPECT_EQ("10.0.0.201", client.config_.routers_[1].toText());
+    // Make sure that the DNS Servers option has been received.
+    ASSERT_EQ(2, client.config_.dns_servers_.size());
+    EXPECT_EQ("10.0.0.202", client.config_.dns_servers_[0].toText());
+    EXPECT_EQ("10.0.0.203", client.config_.dns_servers_[1].toText());
+}
+
+// This test checks that the server responds to the source address of the
+// packet received from the directly connected client if the client didn't
+// set the ciaddr.
+TEST_F(InformTest, directClientNoCiaddr) {
+    Dhcp4Client client;
+    // Configure DHCP server.
+    configure(INFORM_CONFIGS[0], *client.getServer());
+    // Preconfigure the client with the IP address.
+    client.createLease(IOAddress("10.0.0.56"), 600);
+    // Send DHCPINFORM message (with ciaddr not set) to the server.
+    ASSERT_NO_THROW(client.doInform(false));
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    Pkt4Ptr resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Response should have been unicast to the ciaddr.
+    EXPECT_EQ(IOAddress("10.0.0.56"), resp->getLocalAddr());
+    // Response must not be relayed.
+    EXPECT_FALSE(resp->isRelayed());
+    // Make sure that the server id is present.
+    EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+    // Make sure that the Routers option has been received.
+    ASSERT_EQ(2, client.config_.routers_.size());
+    EXPECT_EQ("10.0.0.200", client.config_.routers_[0].toText());
+    EXPECT_EQ("10.0.0.201", client.config_.routers_[1].toText());
+    // Make sure that the DNS Servers option has been received.
+    ASSERT_EQ(2, client.config_.dns_servers_.size());
+    EXPECT_EQ("10.0.0.202", client.config_.dns_servers_[0].toText());
+    EXPECT_EQ("10.0.0.203", client.config_.dns_servers_[1].toText());
+}
+
+// This test checks that the server receiving DHCPINFORM via relay, unicasts the
+// DHCPACK to the client (ciaddr).
+TEST_F(InformTest, relayedClient) {
+    Dhcp4Client client;
+    // Configure DHCP server.
+    configure(INFORM_CONFIGS[1], *client.getServer());
+    // Message is relayed.
+    client.useRelay();
+    // Request some configuration when DHCPINFORM is sent.
+    client.requestOptions(DHO_LOG_SERVERS, DHO_COOKIE_SERVERS);
+    // Preconfigure the client with the IP address.
+    client.createLease(IOAddress("192.0.2.56"), 600);
+    // Send DHCPINFORM message to the server.
+    ASSERT_NO_THROW(client.doInform());
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    Pkt4Ptr resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Response should have been unicast to the ciaddr.
+    EXPECT_EQ(IOAddress("192.0.2.56"), resp->getLocalAddr());
+    // Response is unicast to the client, so it must not be relayed.
+    EXPECT_FALSE(resp->isRelayed());
+    // Make sure that the server id is present.
+    EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+    // Make sure that the Routers option has been received.
+    ASSERT_EQ(2, client.config_.routers_.size());
+    EXPECT_EQ("192.0.2.200", client.config_.routers_[0].toText());
+    EXPECT_EQ("192.0.2.201", client.config_.routers_[1].toText());
+    // Make sure that the DNS Servers option has been received.
+    ASSERT_EQ(2, client.config_.dns_servers_.size());
+    EXPECT_EQ("192.0.2.202", client.config_.dns_servers_[0].toText());
+    EXPECT_EQ("192.0.2.203", client.config_.dns_servers_[1].toText());
+    // Make sure that the Log Servers option has been received.
+    ASSERT_EQ(2, client.config_.quotes_servers_.size());
+    EXPECT_EQ("192.0.2.200", client.config_.routers_[0].toText());
+    EXPECT_EQ("192.0.2.201", client.config_.routers_[1].toText());
+    // Make sure that the Quotes Servers option has been received.
+    ASSERT_EQ(2, client.config_.log_servers_.size());
+    EXPECT_EQ("192.0.2.202", client.config_.dns_servers_[0].toText());
+    EXPECT_EQ("192.0.2.203", client.config_.dns_servers_[1].toText());
+}
+
+// This test checks that the server can respond to the DHCPINFORM message
+// received via relay when the ciaddr is not set.
+TEST_F(InformTest, relayedClientNoCiaddr) {
+    Dhcp4Client client;
+    // Configure DHCP server.
+    configure(INFORM_CONFIGS[1], *client.getServer());
+    // Message is relayed.
+    client.useRelay();
+    // Send DHCPINFORM message to the server.
+    ASSERT_NO_THROW(client.doInform());
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    Pkt4Ptr resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Response should go through a relay as there is no ciaddr.
+    EXPECT_EQ(IOAddress("192.0.2.2"), resp->getLocalAddr());
+    EXPECT_EQ(IOAddress("192.0.2.2"), resp->getGiaddr());
+    EXPECT_EQ(1, resp->getHops());
+    // In the case when the client didn't set the ciaddr and the message
+    // was received via relay the server sets the Broadcast flag to help
+    // the relay forwarding the message (without yiaddr) to the client.
+    EXPECT_EQ(BOOTP_BROADCAST, resp->getFlags() & BOOTP_BROADCAST);
+    // Make sure that the server id is present.
+    EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+    // Make sure that the Routers option has been received.
+    ASSERT_EQ(2, client.config_.routers_.size());
+    EXPECT_EQ("192.0.2.200", client.config_.routers_[0].toText());
+    EXPECT_EQ("192.0.2.201", client.config_.routers_[1].toText());
+    // Make sure that the DNS Servers option has been received.
+    ASSERT_EQ(2, client.config_.dns_servers_.size());
+    EXPECT_EQ("192.0.2.202", client.config_.dns_servers_[0].toText());
+    EXPECT_EQ("192.0.2.203", client.config_.dns_servers_[1].toText());
+}
+
+
+} // end of anonymous namespace

+ 3 - 0
src/lib/dhcp/iface_mgr.cc

@@ -185,7 +185,10 @@ Iface::resizeReadBuffer(const size_t new_size) {
     read_buffer_ = static_cast<uint8_t*>(realloc(read_buffer_,
                                                  read_buffer_size_));
     if (read_buffer_ == NULL) {
+        free(read_buffer_);
         read_buffer_size_ = 0;
+        isc_throw(SocketConfigError, "failed to resize the socket read"
+                  " buffer");
     }
 }
 

+ 8 - 1
src/lib/dhcp/pkt_filter_bpf.cc

@@ -298,7 +298,14 @@ PktFilterBPF::openSocket(Iface& iface,
 
     // Everything is ok, allocate the read buffer and return the socket
     // (BPF device descriptor) to the caller.
-    iface.resizeReadBuffer(buf_len);
+    try {
+        iface.resizeReadBuffer(buf_len);
+
+    } catch (...) {
+        close(fallback);
+        close(sock);
+        throw;
+    }
     return (SocketInfo(addr, port, sock, fallback));
 }