Browse Source

[master] Merge branch 'trac2902'

Conflicts:
	src/lib/dhcp/Makefile.am
Marcin Siodelski 12 years ago
parent
commit
c2d40e3d42

+ 82 - 36
src/bin/dhcp4/dhcp4_srv.cc

@@ -57,12 +57,18 @@ static const char* SERVER_ID_FILE = "b10-dhcp4-serverid";
 // These are hardcoded parameters. Currently this is a skeleton server that only
 // grants those options and a single, fixed, hardcoded lease.
 
-Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast) {
+Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
+                     const bool direct_response_desired) {
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
     try {
         // First call to instance() will create IfaceMgr (it's a singleton)
-        // it may throw something if things go wrong
-        IfaceMgr::instance();
+        // it may throw something if things go wrong.
+        // The 'true' value of the call to setMatchingPacketFilter imposes
+        // that IfaceMgr will try to use the mechanism to respond directly
+        // to the client which doesn't have address assigned. This capability
+        // may be lacking on some OSes, so there is no guarantee that server
+        // will be able to respond directly.
+        IfaceMgr::instance().setMatchingPacketFilter(direct_response_desired);
 
         if (port) {
             // open sockets only if port is non-zero. Port 0 is used
@@ -199,9 +205,9 @@ Dhcpv4Srv::run() {
             }
 
             if (rsp) {
-                if (rsp->getRemoteAddr().toText() == "0.0.0.0") {
-                    rsp->setRemoteAddr(query->getRemoteAddr());
-                }
+
+                adjustRemoteAddr(query, rsp);
+
                 if (!rsp->getHops()) {
                     rsp->setRemotePort(DHCP4_CLIENT_PORT);
                 } else {
@@ -352,19 +358,27 @@ Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     // relay address
     answer->setGiaddr(question->getGiaddr());
 
-    if (question->getGiaddr().toText() != "0.0.0.0") {
-        // relayed traffic
-        answer->setRemoteAddr(question->getGiaddr());
-    } else {
-        // direct traffic
-        answer->setRemoteAddr(question->getRemoteAddr());
-    }
-
     // Let's copy client-id to response. See RFC6842.
     OptionPtr client_id = question->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
     if (client_id) {
         answer->addOption(client_id);
     }
+
+    // If src/dest HW addresses are used by the packet filtering class
+    // we need to copy them as well. There is a need to check that the
+    // address being set is not-NULL because an attempt to set the NULL
+    // HW would result in exception. If these values are not set, the
+    // the default HW addresses (zeroed) should be generated by the
+    // packet filtering class when creating Ethernet header for
+    // outgoing packet.
+    HWAddrPtr src_hw_addr = question->getLocalHWAddr();
+    if (src_hw_addr) {
+        answer->setLocalHWAddr(src_hw_addr);
+    }
+    HWAddrPtr dst_hw_addr = question->getRemoteHWAddr();
+    if (dst_hw_addr) {
+        answer->setRemoteHWAddr(dst_hw_addr);
+    }
 }
 
 void
@@ -517,28 +531,6 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
 
         answer->setYiaddr(lease->addr_);
 
-        // If remote address is not set, we are dealing with a directly
-        // connected client requesting new lease. We can send response to
-        // the address assigned in the lease, but first we have to make sure
-        // that IfaceMgr supports responding directly to the client when
-        // client doesn't have address assigned to its interface yet.
-        if (answer->getRemoteAddr().toText() == "0.0.0.0") {
-            if (IfaceMgr::instance().isDirectResponseSupported()) {
-                answer->setRemoteAddr(lease->addr_);
-            } else {
-                // Since IfaceMgr does not support direct responses to
-                // clients not having IP addresses, we have to send response
-                // to broadcast. We don't check whether the use_bcast flag
-                // was set in the constructor, because this flag is only used
-                // by unit tests to prevent opening broadcast sockets, as
-                // it requires root privileges. If this function is invoked by
-                // unit tests, we expect that it sets broadcast address if
-                // direct response is not supported, so as a test can verify
-                // function's behavior, regardless of the use_bcast flag's value.
-                answer->setRemoteAddr(IOAddress("255.255.255.255"));
-            }
-        }
-
         // IP Address Lease time (type 51)
         opt = OptionPtr(new Option(Option::V4, DHO_DHCP_LEASE_TIME));
         opt->setUint32(lease->valid_lft_);
@@ -573,6 +565,60 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     }
 }
 
+void
+Dhcpv4Srv::adjustRemoteAddr(const Pkt4Ptr& question, Pkt4Ptr& msg) {
+    // Let's create static objects representing zeroed and broadcast
+    // addresses. We will use them further in this function to test
+    // other addresses against them. Since they are static, they will
+    // be created only once.
+    static const IOAddress zero_addr("0.0.0.0");
+    static const IOAddress bcast_addr("255.255.255.255");
+
+    // If received relayed message, server responds to the relay address.
+    if (question->getGiaddr() != zero_addr) {
+        msg->setRemoteAddr(question->getGiaddr());
+
+    // If giaddr is 0 but client set ciaddr, server should unicast the
+    // response to ciaddr.
+    } else if (question->getCiaddr() != zero_addr) {
+        msg->setRemoteAddr(question->getCiaddr());
+
+    // We can't unicast the response to the client when sending NAK,
+    // because we haven't allocated address for him. Therefore,
+    // NAK is broadcast.
+    } else if (msg->getType() == DHCPNAK) {
+        msg->setRemoteAddr(bcast_addr);
+
+    // If yiaddr is set it means that we have created a lease for a client.
+    } else if (msg->getYiaddr() != zero_addr) {
+        // If the broadcast bit is set in the flags field, we have to
+        // send the response to broadcast address. Client may have requested it
+        // because it doesn't support reception of messages on the interface
+        // which doesn't have an address assigned. The other case when response
+        // must be broadcasted is when our server does not support responding
+        // directly to a client without address assigned.
+        const bool bcast_flag = ((question->getFlags() & Pkt4::FLAG_BROADCAST_MASK) != 0);
+        if (!IfaceMgr::instance().isDirectResponseSupported() || bcast_flag) {
+            msg->setRemoteAddr(bcast_addr);
+
+        // Client cleared the broadcast bit and we support direct responses
+        // so we should unicast the response to a newly allocated address -
+        // yiaddr.
+        } else {
+            msg->setRemoteAddr(msg->getYiaddr());
+
+        }
+
+    // In most cases, we should have the remote address found already. If we
+    // found ourselves at this point, the rational thing to do is to respond
+    // to the address we got the query from.
+    } else {
+        msg->setRemoteAddr(question->getRemoteAddr());
+
+    }
+}
+
+
 OptionPtr
 Dhcpv4Srv::getNetmaskOption(const Subnet4Ptr& subnet) {
     uint32_t netmask = getNetmask4(subnet->get().second);

+ 28 - 4
src/bin/dhcp4/dhcp4_srv.h

@@ -45,7 +45,7 @@ namespace dhcp {
 /// Dhcpv4Srv and other classes, see \ref dhcpv4Session.
 class Dhcpv4Srv : public boost::noncopyable {
 
-    public:
+public:
 
     /// @brief defines if certain option may, must or must not appear
     typedef enum {
@@ -61,15 +61,22 @@ class Dhcpv4Srv : public boost::noncopyable {
     /// network interaction. Will instantiate lease manager, and load
     /// old or create new DUID. It is possible to specify alternate
     /// port on which DHCPv4 server will listen on. That is mostly useful
-    /// for testing purposes.
+    /// for testing purposes. The Last two arguments of the constructor
+    /// should be left at default values for normal server operation.
+    /// They should be set to 'false' when creating an instance of this
+    /// class for unit testing because features they enable require
+    /// root privileges.
     ///
     /// @param port specifies port number to listen on
     /// @param dbconfig Lease manager configuration string.  The default
     ///        of the "memfile" manager is used for testing.
     /// @param use_bcast configure sockets to support broadcast messages.
+    /// @param direct_response_desired specifies if it is desired to
+    /// use direct V4 traffic.
     Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT,
               const char* dbconfig = "type=memfile",
-              const bool use_bcast = true);
+              const bool use_bcast = true,
+              const bool direct_response_desired = true);
 
     /// @brief Destructor. Used during DHCPv4 service shutdown.
     ~Dhcpv4Srv();
@@ -218,6 +225,23 @@ protected:
     /// @param msg_type specifies message type
     void appendDefaultOptions(Pkt4Ptr& msg, uint8_t msg_type);
 
+    /// @brief Sets remote addresses for outgoing packet.
+    ///
+    /// This method sets the local and remote addresses on outgoing packet.
+    /// The addresses being set depend on the following conditions:
+    /// - has incoming packet been relayed,
+    /// - is direct response to a client without address supported,
+    /// - type of the outgoing packet,
+    /// - broadcast flag set in the incoming packet.
+    ///
+    /// @warning This method does not check whether provided packet pointers
+    /// are valid. Make sure that pointers are correct before calling this
+    /// function.
+    ///
+    /// @param question instance of a packet received by a server.
+    /// @param [out] msg response packet which addresses are to be adjusted.
+    void adjustRemoteAddr(const Pkt4Ptr& question, Pkt4Ptr& msg);
+
     /// @brief Returns server-identifier option
     ///
     /// @return server-id option
@@ -272,7 +296,7 @@ protected:
     /// initiate server shutdown procedure.
     volatile bool shutdown_;
 
-    private:
+private:
 
     /// @brief Constructs netmask option based on subnet4
     /// @param subnet subnet for which the netmask will be calculated

+ 329 - 90
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -22,6 +22,8 @@
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_int_array.h>
+#include <dhcp/pkt_filter.h>
+#include <dhcp/pkt_filter_inet.h>
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcpsrv/cfgmgr.h>
@@ -50,14 +52,34 @@ public:
 
     /// @brief Constructor.
     ///
-    /// It disables configuration of broadcast options on
-    /// sockets that are opened by the Dhcpv4Srv constructor.
-    /// Setting broadcast options requires root privileges
-    /// which is not the case when running unit tests.
+    /// This constructor disables default modes of operation used by the
+    /// Dhcpv4Srv class:
+    /// - Send/receive broadcast messages through sockets on interfaces
+    /// which support broadcast traffic.
+    /// - Direct DHCPv4 traffic - communication with clients which do not
+    /// have IP address assigned yet.
+    ///
+    /// Enabling these modes requires root privilges so they must be
+    /// disabled for unit testing.
+    ///
+    /// Note, that disabling broadcast options on sockets does not impact
+    /// the operation of these tests because they use local loopback
+    /// interface which doesn't have broadcast capability anyway. It rather
+    /// prevents setting broadcast options on other (broadcast capable)
+    /// sockets which are opened on other interfaces in Dhcpv4Srv constructor.
+    ///
+    /// The Direct DHCPv4 Traffic capability can be disabled here because
+    /// it is tested with PktFilterLPFTest unittest. The tests which belong
+    /// to PktFilterLPFTest can be enabled on demand when root privileges can
+    /// be guaranteed.
+    ///
+    /// @param port port number to listen on; the default value 0 indicates
+    /// that sockets should not be opened.
     NakedDhcpv4Srv(uint16_t port = 0)
-        : Dhcpv4Srv(port, "type=memfile", false) {
+        : Dhcpv4Srv(port, "type=memfile", false, false) {
     }
 
+    using Dhcpv4Srv::adjustRemoteAddr;
     using Dhcpv4Srv::processDiscover;
     using Dhcpv4Srv::processRequest;
     using Dhcpv4Srv::processRelease;
@@ -73,6 +95,41 @@ public:
 
 static const char* SRVID_FILE = "server-id-test.txt";
 
+/// @brief Dummy Packet Filtering class.
+///
+/// This class reports capability to respond directly to the
+/// client which doesn't have address configured yet.
+///
+/// All packet and socket handling functions do nothing because
+/// they are not used in unit tests.
+class PktFilterTest : public PktFilter {
+public:
+
+    /// @brief Reports 'direct response' capability.
+    ///
+    /// @return always true.
+    virtual bool isDirectResponseSupported() const {
+        return (true);
+    }
+
+    /// Does nothing.
+    virtual int openSocket(const Iface&, const IOAddress&, const uint16_t,
+                           const bool, const bool) {
+        return (0);
+    }
+
+    /// Does nothing.
+    virtual Pkt4Ptr receive(const Iface&, const SocketInfo&) {
+        return Pkt4Ptr();
+    }
+
+    /// Does nothing.
+    virtual int send(const Iface&, uint16_t, const Pkt4Ptr&) {
+        return (0);
+    }
+
+};
+
 class Dhcpv4SrvTest : public ::testing::Test {
 public:
 
@@ -171,6 +228,11 @@ public:
         EXPECT_EQ(q->getIface(),  a->getIface());
         EXPECT_EQ(q->getIndex(),  a->getIndex());
         EXPECT_EQ(q->getGiaddr(), a->getGiaddr());
+        // When processing an incoming packet the remote address
+        // is copied as a src address, and the source address is
+        // copied as a remote address to the response.
+        EXPECT_TRUE(q->getLocalHWAddr() == a->getLocalHWAddr());
+        EXPECT_TRUE(q->getRemoteHWAddr() == a->getRemoteHWAddr());
 
         // Check that bare minimum of required options are there.
         // We don't check options requested by a client. Those
@@ -360,26 +422,43 @@ public:
     /// @brief Tests if Discover or Request message is processed correctly
     ///
     /// @param msg_type DHCPDISCOVER or DHCPREQUEST
-    /// @param client_addr client address
-    /// @param relay_addr relay address
-    void testDiscoverRequest(const uint8_t msg_type,
-                             const IOAddress& client_addr,
-                             const IOAddress& relay_addr) {
-
+    void testDiscoverRequest(const uint8_t msg_type) {
+        // Create an instance of the tested class.
         boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
+
+        // Initialize the source HW address.
         vector<uint8_t> mac(6);
-        for (int i = 0; i < 6; i++) {
-            mac[i] = i*10;
+        for (int i = 0; i < 6; ++i) {
+            mac[i] = i * 10;
         }
-
+        // Initialized the destination HW address.
+        vector<uint8_t> dst_mac(6);
+        for (int i = 0; i < 6; ++i) {
+            dst_mac[i] = i * 20;
+        }
+        // Create a DHCP message. It will be used to simulate the
+        // incoming message.
         boost::shared_ptr<Pkt4> req(new Pkt4(msg_type, 1234));
+        // Create a response message. It will hold a reponse packet.
+        // Initially, set it to NULL.
         boost::shared_ptr<Pkt4> rsp;
-
+        // Set the name of the interface on which packet is received.
         req->setIface("eth0");
+        // Set the interface index. It is just a dummy value and will
+        // not be interpreted.
         req->setIndex(17);
+        // Set the target HW address. This value is normally used to
+        // construct the data link layer header.
+        req->setRemoteHWAddr(1, 6, dst_mac);
+        // Set the HW address. This value is set on DHCP level (in chaddr).
         req->setHWAddr(1, 6, mac);
-        req->setRemoteAddr(IOAddress(client_addr));
-        req->setGiaddr(relay_addr);
+        // Set local HW address. It is used to construct the data link layer
+        // header.
+        req->setLocalHWAddr(1, 6, mac);
+        // Set target IP address.
+        req->setRemoteAddr(IOAddress("192.0.2.55"));
+        // Set relay address.
+        req->setGiaddr(IOAddress("192.0.2.10"));
 
         // We are going to test that certain options are returned
         // in the response message when requested using 'Parameter
@@ -407,33 +486,6 @@ public:
 
         }
 
-        if (relay_addr.toText() != "0.0.0.0") {
-            // This is relayed message. It should be sent brsp to relay address.
-            EXPECT_EQ(req->getGiaddr().toText(),
-                      rsp->getRemoteAddr().toText());
-
-        } else if (client_addr.toText() != "0.0.0.0") {
-            // This is a message from a client having an IP address.
-            EXPECT_EQ(req->getRemoteAddr().toText(),
-                      rsp->getRemoteAddr().toText());
-
-        } else {
-            // This is a message from a client having no IP address yet.
-            // If IfaceMgr supports direct traffic the response should
-            // be sent to the new address assigned to the client.
-            if (IfaceMgr::instance().isDirectResponseSupported()) {
-                EXPECT_EQ(rsp->getYiaddr(),
-                          rsp->getRemoteAddr().toText());
-
-            // If direct response to the client having no IP address is
-            // not supported, response should go to broadcast.
-            } else {
-                EXPECT_EQ("255.255.255.255", rsp->getRemoteAddr().toText());
-
-            }
-
-        }
-
         messageCheck(req, rsp);
 
         // We did not request any options so these should not be present
@@ -471,12 +523,35 @@ public:
 
     }
 
-    ~Dhcpv4SrvTest() {
+    /// @brief This function cleans up after the test.
+    virtual void TearDown() {
+
         CfgMgr::instance().deleteSubnets4();
 
         // Let's clean up if there is such a file.
         unlink(SRVID_FILE);
-    };
+
+        // Close all open sockets.
+        IfaceMgr::instance().closeSockets();
+
+        // Some unit tests override the default packet filtering class, used
+        // by the IfaceMgr. The dummy class, called PktFilterTest, reports the
+        // capability to directly respond to the clients without IP address
+        // assigned. This capability is not supported by the default packet
+        // filtering class: PktFilterInet. Therefore setting the dummy class
+        // allows to test scenarios, when server responds to the broadcast address
+        // on client's request, despite having support for direct response.
+        // The following call restores the use of original packet filtering class
+        // after the test.
+        try {
+            IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterInet()));
+
+        } catch (const Exception& ex) {
+            FAIL() << "Failed to restore the default (PktFilterInet) packet filtering"
+                   << " class after the test. Exception has been caught: "
+                   << ex.what();
+        }
+    }
 
     /// @brief A subnet used in most tests
     Subnet4Ptr subnet_;
@@ -494,20 +569,220 @@ TEST_F(Dhcpv4SrvTest, basic) {
 
     // Check that the base class can be instantiated
     boost::scoped_ptr<Dhcpv4Srv> srv;
-    ASSERT_NO_THROW(srv.reset(new Dhcpv4Srv(DHCP4_SERVER_PORT + 10000)));
+    ASSERT_NO_THROW(srv.reset(new Dhcpv4Srv(DHCP4_SERVER_PORT + 10000, "type=memfile",
+                                            false, false)));
     srv.reset();
+    // We have to close open sockets because further in this test we will
+    // call the Dhcpv4Srv constructor again. This constructor will try to
+    // set the appropriate packet filter class for IfaceMgr. This requires
+    // that all sockets are closed.
+    IfaceMgr::instance().closeSockets();
 
     // Check that the derived class can be instantiated
     boost::scoped_ptr<NakedDhcpv4Srv> naked_srv;
     ASSERT_NO_THROW(
-            naked_srv.reset(new NakedDhcpv4Srv(DHCP4_SERVER_PORT + 10000)));
+        naked_srv.reset(new NakedDhcpv4Srv(DHCP4_SERVER_PORT + 10000)));
     EXPECT_TRUE(naked_srv->getServerID());
+    // Close sockets again for the next test.
+    IfaceMgr::instance().closeSockets();
 
     ASSERT_NO_THROW(naked_srv.reset(new NakedDhcpv4Srv(0)));
     EXPECT_TRUE(naked_srv->getServerID());
 }
 
-// Verifies that DISCOVER received via relay can be processed correctly,
+// This test verifies that the destination address of the response
+// message is set to giaddr, when giaddr is set to non-zero address
+// in the received message.
+TEST_F(Dhcpv4SrvTest, adjustRemoteAddressRelay) {
+    boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
+
+    // Create the instance of the incoming packet.
+    boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
+    // Set the giaddr to non-zero address as if it was relayed.
+    req->setGiaddr(IOAddress("192.0.2.1"));
+    // Set ciaddr to zero. This simulates the client which applies
+    // for the new lease.
+    req->setCiaddr(IOAddress("0.0.0.0"));
+    // Clear broadcast flag.
+    req->setFlags(0x0000);
+
+    // Create a response packet. Assume that the new lease have
+    // been created and new address allocated. This address is
+    // stored in yiaddr field.
+    boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
+    resp->setYiaddr(IOAddress("192.0.2.100"));
+    // Clear the remote address.
+    resp->setRemoteAddr(IOAddress("0.0.0.0"));
+
+    // This function never throws.
+    ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+
+    // Now the destination address should be relay's address.
+    EXPECT_EQ("192.0.2.1", resp->getRemoteAddr().toText());
+
+    // Let's do another test and set other fields: ciaddr and
+    // flags. By doing it, we want to make sure that the relay
+    // address will take precedence.
+    req->setGiaddr(IOAddress("192.0.2.50"));
+    req->setCiaddr(IOAddress("192.0.2.11"));
+    req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
+
+    resp->setYiaddr(IOAddress("192.0.2.100"));
+    // Clear remote address.
+    resp->setRemoteAddr(IOAddress("0.0.0.0"));
+
+    ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+
+    // Response should be sent back to the relay address.
+    EXPECT_EQ("192.0.2.50", resp->getRemoteAddr().toText());
+}
+
+// This test verifies that the destination address of the response message
+// is set to ciaddr when giaddr is set to zero and the ciaddr is set to
+// non-zero address in the received message. This is the case when the
+// client is in Renew or Rebind state.
+TEST_F(Dhcpv4SrvTest, adjustRemoteAddressRenewRebind) {
+    boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
+
+    // Create instance of the incoming packet.
+    boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
+
+    // Clear giaddr to simulate direct packet.
+    req->setGiaddr(IOAddress("0.0.0.0"));
+    // Set ciaddr to non-zero address. The response should be sent to this
+    // address as the client is in renewing or rebinding state (it is fully
+    // configured).
+    req->setCiaddr(IOAddress("192.0.2.15"));
+    // Let's configure broadcast flag. It should be ignored because
+    // we are responding directly to the client having an address
+    // and trying to extend his lease. Broadcast flag is only used
+    // when new lease is acquired and server must make a decision
+    // whether to unicast the response to the acquired address or
+    // broadcast it.
+    req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
+
+    // Create a response.
+    boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
+    // Let's extend the lease for the client in such a way that
+    // it will actually get different address. The response
+    // should not be sent to this address but rather to ciaddr
+    // as client still have ciaddr configured.
+    resp->setYiaddr(IOAddress("192.0.2.13"));
+    // Clear the remote address.
+    resp->setRemoteAddr(IOAddress("0.0.0.0"));
+
+    ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+
+    // Check that server responds to ciaddr
+    EXPECT_EQ("192.0.2.15", resp->getRemoteAddr().toText());
+}
+
+// This test verifies that the destination address of the response message
+// is set correctly when giaddr and ciaddr is zeroed in the received message
+// and the new lease is acquired. The lease address is carried in the
+// response message in the yiaddr field. In this case destination address
+// of the response should be set to yiaddr if server supports direct responses
+// to the client which doesn't have an address yet or broadcast if the server
+// doesn't support direct responses.
+TEST_F(Dhcpv4SrvTest, adjustRemoteAddressSelect) {
+    boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
+
+    // Create instance of the incoming packet.
+    boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
+
+    // Clear giaddr to simulate direct packet.
+    req->setGiaddr(IOAddress("0.0.0.0"));
+    // Clear client address as it hasn't got any address configured yet.
+    req->setCiaddr(IOAddress("0.0.0.0"));
+
+    // Let's clear the broadcast flag.
+    req->setFlags(0);
+
+    // Create a response.
+    boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
+    // Assign some new address for this client.
+    resp->setYiaddr(IOAddress("192.0.2.13"));
+
+    // Clear the remote address.
+    resp->setRemoteAddr(IOAddress("0.0.0.0"));
+
+    // When running unit tests, the IfaceMgr is using the default Packet
+    // Filtering class, PktFilterInet. This class does not support direct
+    // responses to clients without address assigned. When giaddr and ciaddr
+    // are zero and client has just got new lease, the assigned address is
+    // carried in yiaddr. In order to send this address to the client,
+    // server must broadcast its response.
+    ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+
+    // Check that the response is sent to broadcast address as the
+    // server doesn't have capability to respond directly.
+    EXPECT_EQ("255.255.255.255", resp->getRemoteAddr().toText());
+
+    // We also want to test the case when the server has capability to
+    // respond directly to the client which is not configured. Server
+    // makes decision whether it responds directly or broadcast its
+    // response based on the capability reported by IfaceMgr. In order
+    // to set this capability we have to provide a dummy Packet Filter
+    // class which would report the support for direct responses.
+    // This class is called PktFilterTest.
+    IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterTest()));
+
+    // Now we expect that the server will send its response to the
+    // address assigned for the client.
+    ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+
+    EXPECT_EQ("192.0.2.13", resp->getRemoteAddr().toText());
+}
+
+// This test verifies that the destination address of the response message
+// is set to broadcast address when client set broadcast flag in its
+// query. Client sets this flag to indicate that it can't receive direct
+// responses from the server when it doesn't have its interface configured.
+// Server must respect broadcast flag.
+TEST_F(Dhcpv4SrvTest, adjustRemoteAddressBroadcast) {
+    boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
+
+    // Create instance of the incoming packet.
+    boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
+
+    // Clear giaddr to simulate direct packet.
+    req->setGiaddr(IOAddress("0.0.0.0"));
+    // Clear client address as it hasn't got any address configured yet.
+    req->setCiaddr(IOAddress("0.0.0.0"));
+
+    // Let's set the broadcast flag.
+    req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
+
+    // Create a response.
+    boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
+    // Assign some new address for this client.
+    resp->setYiaddr(IOAddress("192.0.2.13"));
+
+    // Clear the remote address.
+    resp->setRemoteAddr(IOAddress("0.0.0.0"));
+
+    // When running unit tests, the IfaceMgr is using the default Packet
+    // Filtering class, PktFilterInet. This class does not support direct
+    // responses to the clients without address assigned. If giaddr and
+    // ciaddr are zero and client has just got the new lease, the assigned
+    // address is carried in yiaddr. In order to send this address to the
+    // client, server must send the response to the broadcast address when
+    // direct response is not supported. This conflicts with the purpose
+    // of this test which is supposed to verify that responses are sent
+    // to broadcast address only, when broadcast flag is set. Therefore,
+    // in order to simulate that direct responses are supported we have
+    // to replace the default packet filtering class with a dummy class
+    // which reports direct response capability.
+    IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterTest()));
+
+    ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+
+    // Server must repond to broadcast address when client desired that
+    // by setting the broadcast flag in its request.
+    EXPECT_EQ("255.255.255.255", resp->getRemoteAddr().toText());
+}
+
+// Verifies that DISCOVER message can be processed correctly,
 // that the OFFER message generated in response is valid and
 // contains necessary options.
 //
@@ -515,29 +790,11 @@ TEST_F(Dhcpv4SrvTest, basic) {
 // are other tests that verify correctness of the allocation
 // engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
 // and DiscoverInvalidHint.
-TEST_F(Dhcpv4SrvTest, processDiscoverRelay) {
-    testDiscoverRequest(DHCPDISCOVER,
-                        IOAddress("192.0.2.56"),
-                        IOAddress("192.0.2.67"));
-}
-
-// Verifies that the non-relayed DISCOVER is processed correctly when
-// client source address is specified.
-TEST_F(Dhcpv4SrvTest, processDiscoverNoRelay) {
-    testDiscoverRequest(DHCPDISCOVER,
-                        IOAddress("0.0.0.0"),
-                        IOAddress("192.0.2.67"));
+TEST_F(Dhcpv4SrvTest, processDiscover) {
+    testDiscoverRequest(DHCPDISCOVER);
 }
 
-// Verified that the non-relayed DISCOVER is processed correctly when
-// client source address is not specified.
-TEST_F(Dhcpv4SrvTest, processDiscoverNoClientAddr) {
-    testDiscoverRequest(DHCPDISCOVER,
-                        IOAddress("0.0.0.0"),
-                        IOAddress("0.0.0.0"));
-}
-
-// Verifies that REQUEST received via relay can be processed correctly,
+// Verifies that REQUEST message can be processed correctly,
 // that the OFFER message generated in response is valid and
 // contains necessary options.
 //
@@ -545,26 +802,8 @@ TEST_F(Dhcpv4SrvTest, processDiscoverNoClientAddr) {
 // are other tests that verify correctness of the allocation
 // engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
 // and DiscoverInvalidHint.
-TEST_F(Dhcpv4SrvTest, processRequestRelay) {
-    testDiscoverRequest(DHCPREQUEST,
-                        IOAddress("192.0.2.56"),
-                        IOAddress("192.0.2.67"));
-}
-
-// Verifies that the non-relayed REQUEST is processed correctly when
-// client source address is specified.
-TEST_F(Dhcpv4SrvTest, processRequestNoRelay) {
-    testDiscoverRequest(DHCPREQUEST,
-                        IOAddress("0.0.0.0"),
-                        IOAddress("192.0.2.67"));
-}
-
-// Verified that the non-relayed REQUEST is processed correctly when
-// client source address is not specified.
-TEST_F(Dhcpv4SrvTest, processRequestNoClientAddr) {
-    testDiscoverRequest(DHCPREQUEST,
-                        IOAddress("0.0.0.0"),
-                        IOAddress("0.0.0.0"));
+TEST_F(Dhcpv4SrvTest, processRequest) {
+    testDiscoverRequest(DHCPREQUEST);
 }
 
 TEST_F(Dhcpv4SrvTest, processRelease) {

+ 5 - 0
src/lib/dhcp/Makefile.am

@@ -34,11 +34,16 @@ libb10_dhcp___la_SOURCES += option_data_types.cc option_data_types.h
 libb10_dhcp___la_SOURCES += option_definition.cc option_definition.h
 libb10_dhcp___la_SOURCES += option_space.cc option_space.h
 libb10_dhcp___la_SOURCES += option_string.cc option_string.h
+libb10_dhcp___la_SOURCES += protocol_util.cc protocol_util.h
 libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
 libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
 libb10_dhcp___la_SOURCES += pkt_filter.h
 libb10_dhcp___la_SOURCES += pkt_filter_inet.cc pkt_filter_inet.h
+
+if OS_LINUX
 libb10_dhcp___la_SOURCES += pkt_filter_lpf.cc pkt_filter_lpf.h
+endif
+
 libb10_dhcp___la_SOURCES += std_option_defs.h
 
 libb10_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS)

+ 5 - 1
src/lib/dhcp/hwaddr.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013 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
@@ -27,6 +27,10 @@ namespace dhcp {
 /// @brief Hardware type that represents information from DHCPv4 packet
 struct HWAddr {
 public:
+
+    /// @brief Size of an ethernet hardware address.
+    static const size_t ETHERNET_HWADDR_LEN = 6;
+
     /// @brief Maximum size of a hardware address.
     static const size_t MAX_HWADDR_LEN = 20;
 

+ 82 - 5
src/lib/dhcp/iface_mgr.cc

@@ -58,11 +58,44 @@ Iface::Iface(const std::string& name, int ifindex)
 
 void
 Iface::closeSockets() {
-    for (SocketCollection::iterator sock = sockets_.begin();
-         sock != sockets_.end(); ++sock) {
-        close(sock->sockfd_);
+    // Close IPv4 sockets.
+    closeSockets(AF_INET);
+    // Close IPv6 sockets.
+    closeSockets(AF_INET6);
+}
+
+void
+Iface::closeSockets(const uint16_t family) {
+    // Check that the correect 'family' value has been specified.
+    // The possible values are AF_INET or AF_INET6. Note that, in
+    // the current code they are used to differentiate that the
+    // socket is used to transmit IPv4 or IPv6 traffic. However,
+    // the actual family types of the sockets may be different,
+    // e.g. for LPF we are using raw sockets of AF_PACKET family.
+    //
+    // @todo Consider replacing the AF_INET and AF_INET6 with some
+    // enum which will not be confused with the actual socket type.
+    if ((family != AF_INET) && (family != AF_INET6)) {
+        isc_throw(BadValue, "Invalid socket family " << family
+                  << " specified when requested to close all sockets"
+                  << " which belong to this family");
+    }
+    // Search for the socket of the specific type.
+    SocketCollection::iterator sock = sockets_.begin();
+    while (sock != sockets_.end()) {
+        if (sock->family_ == family) {
+            // Close and delete the socket and move to the
+            // next one.
+            close(sock->sockfd_);
+            sockets_.erase(sock++);
+
+        } else {
+            // Different type of socket. Let's move
+            // to the next one.
+            ++sock;
+
+        }
     }
-    sockets_.clear();
 }
 
 std::string
@@ -150,6 +183,14 @@ void IfaceMgr::closeSockets() {
     }
 }
 
+void
+IfaceMgr::closeSockets(const uint16_t family) {
+    for (IfaceCollection::iterator iface = ifaces_.begin();
+         iface != ifaces_.end(); ++iface) {
+        iface->closeSockets(family);
+    }
+}
+
 IfaceMgr::~IfaceMgr() {
     // control_buf_ is deleted automatically (scoped_ptr)
     control_buf_len_ = 0;
@@ -157,6 +198,42 @@ IfaceMgr::~IfaceMgr() {
     closeSockets();
 }
 
+bool
+IfaceMgr::isDirectResponseSupported() const {
+    return (packet_filter_->isDirectResponseSupported());
+}
+
+void
+IfaceMgr::setPacketFilter(const boost::shared_ptr<PktFilter>& packet_filter) {
+    // Do not allow NULL pointer.
+    if (!packet_filter) {
+        isc_throw(InvalidPacketFilter, "NULL packet filter object specified");
+    }
+    // Different packet filters use different socket types. It does not make
+    // sense to allow the change of packet filter when there are IPv4 sockets
+    // open because they can't be used by the receive/send functions of the
+    // new packet filter. Below, we check that there are no open IPv4 sockets.
+    // If we find at least one, we have to fail. However, caller still has a
+    // chance to replace the packet filter if he closes sockets explicitly.
+    for (IfaceCollection::const_iterator iface = ifaces_.begin();
+         iface != ifaces_.end(); ++iface) {
+        const Iface::SocketCollection& sockets = iface->getSockets();
+        for (Iface::SocketCollection::const_iterator sock = sockets.begin();
+             sock != sockets.end(); ++sock) {
+            if (sock->family_ == AF_INET) {
+            // There is at least one socket open, so we have to fail.
+                isc_throw(PacketFilterChangeDenied,
+                          "it is not allowed to set new packet"
+                          << " filter when there are open IPv4 sockets - need"
+                          << " to close them first");
+            }
+        }
+    }
+    // Everything is fine, so replace packet filter.
+    packet_filter_ = packet_filter;
+}
+
+
 void IfaceMgr::stubDetectIfaces() {
     string ifaceName;
     const string v4addr("127.0.0.1"), v6addr("::1");
@@ -758,7 +835,7 @@ IfaceMgr::send(const Pkt4Ptr& pkt) {
 
     // Skip checking if packet filter is non-NULL because it has been
     // already checked when packet filter was set.
-    return (packet_filter_->send(getSocket(*pkt), pkt));
+    return (packet_filter_->send(*iface, getSocket(*pkt), pkt));
 }
 
 

+ 109 - 45
src/lib/dhcp/iface_mgr.h

@@ -39,10 +39,10 @@ public:
         isc::Exception(file, line, what) { };
 };
 
-/// @brief IfaceMgr exception thrown when invalid packet filter object specified.
-class InvalidPacketFilter : public Exception {
+/// @brief Exception thrown when it is not allowed to set new Packet Filter.
+class PacketFilterChangeDenied : public Exception {
 public:
-    InvalidPacketFilter(const char* file, size_t line, const char* what) :
+    PacketFilterChangeDenied(const char* file, size_t line, const char* what) :
         isc::Exception(file, line, what) { };
 };
 
@@ -88,7 +88,7 @@ struct SocketInfo {
 };
 
 
-/// @brief represents a single network interface
+/// @brief Represents a single network interface
 ///
 /// Iface structure represents network interface with all useful
 /// information, like name, interface index, MAC address and
@@ -96,13 +96,20 @@ struct SocketInfo {
 class Iface {
 public:
 
-    /// maximum MAC address length (Infiniband uses 20 bytes)
+    /// Maximum MAC address length (Infiniband uses 20 bytes)
     static const unsigned int MAX_MAC_LEN = 20;
 
-    /// type that defines list of addresses
+    /// Type that defines list of addresses
     typedef std::vector<isc::asiolink::IOAddress> AddressCollection;
 
-    /// type that holds a list of socket informations
+    /// @brief Type that holds a list of socket information.
+    ///
+    /// @warning The type of the container used here must guarantee
+    /// that the iterators do not invalidate when erase() is called.
+    /// This is because, the \ref closeSockets function removes
+    /// elements selectively by calling erase on the element to be
+    /// removed and further iterates through remaining elements.
+    ///
     /// @todo: Add SocketCollectionConstIter type
     typedef std::list<SocketInfo> SocketCollection;
 
@@ -117,6 +124,27 @@ public:
     /// @brief Closes all open sockets on interface.
     void closeSockets();
 
+    /// @brief Closes all IPv4 or IPv6 sockets.
+    ///
+    /// This function closes sockets of the specific 'type' and closes them.
+    /// The 'type' of the socket indicates whether it is used to send IPv4
+    /// or IPv6 packets. The allowed values of the parameter are AF_INET and
+    /// AF_INET6 for IPv4 and IPv6 packets respectively. It is important
+    /// to realize that the actual types of sockets may be different than
+    /// AF_INET for IPv4 packets. This is because, historically the IfaceMgr
+    /// always used AF_INET sockets for IPv4 traffic. This is no longer the
+    /// case when the Direct IPv4 traffic must be supported. In order to support
+    /// direct traffic, the IfaceMgr operates on raw sockets, e.g. AF_PACKET
+    /// family sockets on Linux.
+    ///
+    /// @todo Replace the AF_INET and AF_INET6 values with an enum
+    /// which will not be confused with the actual socket type.
+    ///
+    /// @param family type of the sockets to be closed (AF_INET or AF_INET6)
+    ///
+    /// @throw BadValue if family value is different than AF_INET or AF_INET6.
+    void closeSockets(const uint16_t family);
+
     /// @brief Returns full interface name as "ifname/ifindex" string.
     ///
     /// @return string with interface name
@@ -146,7 +174,7 @@ public:
 
     /// @brief Sets flag_*_ fields based on bitmask value returned by OS
     ///
-    /// Note: Implementation of this method is OS-dependent as bits have
+    /// @note Implementation of this method is OS-dependent as bits have
     /// different meaning on each OS.
     ///
     /// @param flags bitmask value returned by OS in interface detection
@@ -237,53 +265,53 @@ public:
     const SocketCollection& getSockets() const { return sockets_; }
 
 protected:
-    /// socket used to sending data
+    /// Socket used to send data.
     SocketCollection sockets_;
 
-    /// network interface name
+    /// Network interface name.
     std::string name_;
 
-    /// interface index (a value that uniquely indentifies an interface)
+    /// Interface index (a value that uniquely indentifies an interface).
     int ifindex_;
 
-    /// list of assigned addresses
+    /// List of assigned addresses.
     AddressCollection addrs_;
 
-    /// link-layer address
+    /// Link-layer address.
     uint8_t mac_[MAX_MAC_LEN];
 
-    /// length of link-layer address (usually 6)
+    /// Length of link-layer address (usually 6).
     size_t mac_len_;
 
-    /// hardware type
+    /// Hardware type.
     uint16_t hardware_type_;
 
 public:
     /// @todo: Make those fields protected once we start supporting more
     /// than just Linux
 
-    /// specifies if selected interface is loopback
+    /// Specifies if selected interface is loopback.
     bool flag_loopback_;
 
-    /// specifies if selected interface is up
+    /// Specifies if selected interface is up.
     bool flag_up_;
 
-    /// flag specifies if selected interface is running
-    /// (e.g. cable plugged in, wifi associated)
+    /// Flag specifies if selected interface is running
+    /// (e.g. cable plugged in, wifi associated).
     bool flag_running_;
 
-    /// flag specifies if selected interface is multicast capable
+    /// Flag specifies if selected interface is multicast capable.
     bool flag_multicast_;
 
-    /// flag specifies if selected interface is broadcast capable
+    /// Flag specifies if selected interface is broadcast capable.
     bool flag_broadcast_;
 
-    /// interface flags (this value is as is returned by OS,
-    /// it may mean different things on different OSes)
+    /// Interface flags (this value is as is returned by OS,
+    /// it may mean different things on different OSes).
     uint32_t flags_;
 };
 
-/// @brief handles network interfaces, transmission and reception
+/// @brief Handles network interfaces, transmission and reception.
 ///
 /// IfaceMgr is an interface manager class that detects available network
 /// interfaces, configured addresses, link-local addresses, and provides
@@ -291,7 +319,7 @@ public:
 ///
 class IfaceMgr : public boost::noncopyable {
 public:
-    /// defines callback used when commands are received over control session
+    /// Defines callback used when commands are received over control session.
     typedef void (*SessionCallback) (void);
 
     /// @brief Packet reception buffer size
@@ -307,7 +335,7 @@ public:
     //      2 maps (ifindex-indexed and name-indexed) and
     //      also hide it (make it public make tests easier for now)
 
-    /// type that holds a list of interfaces
+    /// Type that holds a list of interfaces.
     typedef std::list<Iface> IfaceCollection;
 
     /// IfaceMgr is a singleton class. This method returns reference
@@ -324,7 +352,7 @@ public:
     /// the client.
     ///
     /// @return true if direct response is supported.
-    bool isDirectResponseSupported();
+    bool isDirectResponseSupported() const;
 
     /// @brief Returns interface with specified interface index
     ///
@@ -380,11 +408,10 @@ public:
     /// @return a socket descriptor
     uint16_t getSocket(const isc::dhcp::Pkt4& pkt);
 
-    /// debugging method that prints out all available interfaces
+    /// Debugging method that prints out all available interfaces.
     ///
     /// @param out specifies stream to print list of interfaces to
-    void
-    printIfaces(std::ostream& out = std::cout);
+    void printIfaces(std::ostream& out = std::cout);
 
     /// @brief Sends an IPv6 packet.
     ///
@@ -542,10 +569,31 @@ public:
                       const bool use_bcast = true);
 
     /// @brief Closes all open sockets.
-    /// Is used in destructor, but also from Dhcpv4_srv and Dhcpv6_srv classes.
+    /// Is used in destructor, but also from Dhcpv4Srv and Dhcpv6Srv classes.
     void closeSockets();
 
-    /// @brief returns number of detected interfaces
+    /// @brief Closes all IPv4 or IPv6 sockets.
+    ///
+    /// This function closes sockets of the specific 'type' and closes them.
+    /// The 'type' of the socket indicates whether it is used to send IPv4
+    /// or IPv6 packets. The allowed values of the parameter are AF_INET and
+    /// AF_INET6 for IPv4 and IPv6 packets respectively. It is important
+    /// to realize that the actual types of sockets may be different than
+    /// AF_INET for IPv4 packets. This is because, historically the IfaceMgr
+    /// always used AF_INET sockets for IPv4 traffic. This is no longer the
+    /// case when the Direct IPv4 traffic must be supported. In order to support
+    /// direct traffic, the IfaceMgr operates on raw sockets, e.g. AF_PACKET
+    /// family sockets on Linux.
+    ///
+    /// @todo Replace the AF_INET and AF_INET6 values with an enum
+    /// which will not be confused with the actual socket type.
+    ///
+    /// @param family type of the sockets to be closed (AF_INET or AF_INET6)
+    ///
+    /// @throw BadValue if family value is different than AF_INET or AF_INET6.
+    void closeSockets(const uint16_t family);
+
+    /// @brief Returns number of detected interfaces.
     ///
     /// @return number of detected interfaces
     uint16_t countIfaces() { return ifaces_.size(); }
@@ -567,18 +615,34 @@ public:
     /// Packet Filters expose low-level functions handling sockets opening
     /// and sending/receiving packets through those sockets. This function
     /// sets custom Packet Filter (represented by a class derived from PktFilter)
-    /// to be used by IfaceMgr.
+    /// to be used by IfaceMgr. Note that there must be no IPv4 sockets open
+    /// when this function is called. Call closeSockets(AF_INET) to close
+    /// all hanging IPv4 sockets opened by the current packet filter object.
     ///
     /// @param packet_filter new packet filter to be used by IfaceMgr to send/receive
     /// packets and open sockets.
     ///
     /// @throw InvalidPacketFilter if provided packet filter object is NULL.
-    void setPacketFilter(const boost::shared_ptr<PktFilter>& packet_filter) {
-        if (!packet_filter) {
-            isc_throw(InvalidPacketFilter, "NULL packet filter object specified");
-        }
-        packet_filter_ = packet_filter;
-    }
+    /// @throw PacketFilterChangeDenied if there are open IPv4 sockets
+    void setPacketFilter(const PktFilterPtr& packet_filter);
+
+    /// @brief Set Packet Filter object to handle send/receive packets.
+    ///
+    /// This function sets Packet Filter object to be used by IfaceMgr,
+    /// appropriate for the current OS. Setting the argument to 'true'
+    /// indicates that function should set a packet filter class
+    /// which supports direct responses to clients having no address
+    /// assigned yet. Filters picked by this function will vary, depending
+    /// on the OS being used. There is no guarantee that there is an
+    /// implementation that supports this feature on a particular OS.
+    /// If there isn't, the PktFilterInet object will be set. If the
+    /// argument is set to 'false', PktFilterInet object instance will
+    /// be set as the Packet Filter regrdaless of the OS type.
+    ///
+    /// @param direct_response_desired specifies whether the Packet Filter
+    /// object being set should support direct traffic to the host
+    /// not having address assigned.
+    void setMatchingPacketFilter(const bool direct_response_desired = false);
 
     /// A value of socket descriptor representing "not specified" state.
     static const int INVALID_SOCKET = -1;
@@ -660,14 +724,14 @@ protected:
     //int recvsock_; // TODO: should be fd_set eventually, but we have only
     //int sendsock_; // 2 sockets for now. Will do for until next release
 
-    // we can't use the same socket, as receiving socket
+    // We can't use the same socket, as receiving socket
     // is bound to multicast address. And we all know what happens
     // to people who try to use multicast as source address.
 
-    /// length of the control_buf_ array
+    /// Length of the control_buf_ array
     size_t control_buf_len_;
 
-    /// control-buffer, used in transmission and reception
+    /// Control-buffer, used in transmission and reception.
     boost::scoped_array<char> control_buf_;
 
     /// @brief A wrapper for OS-specific operations before sending IPv4 packet
@@ -687,10 +751,10 @@ protected:
     /// @return true if successful, false otherwise
     bool os_receive4(struct msghdr& m, Pkt4Ptr& pkt);
 
-    /// socket descriptor of the session socket
+    /// Socket descriptor of the session socket.
     int session_socket_;
 
-    /// a callback that will be called when data arrives over session_socket_
+    /// A callback that will be called when data arrives over session_socket_.
     SessionCallback session_callback_;
 private:
 
@@ -737,7 +801,7 @@ private:
     /// families, e.g. AF_INET, AF_PACKET etc. Another possible type of
     /// Packet Filter is the one used for unit testing, which doesn't
     /// open sockets but rather mimics their behavior (mock object).
-    boost::shared_ptr<PktFilter> packet_filter_;
+    PktFilterPtr packet_filter_;
 };
 
 }; // namespace isc::dhcp

+ 9 - 5
src/lib/dhcp/iface_mgr_bsd.cc

@@ -17,6 +17,7 @@
 #if defined(OS_BSD)
 
 #include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter_inet.h>
 #include <exceptions/exceptions.h>
 
 using namespace std;
@@ -34,11 +35,6 @@ IfaceMgr::detectIfaces() {
     stubDetectIfaces();
 }
 
-bool
-IfaceMgr::isDirectResponseSupported() {
-    return (false);
-}
-
 void IfaceMgr::os_send4(struct msghdr& /*m*/,
                         boost::scoped_array<char>& /*control_buf*/,
                         size_t /*control_buf_len*/,
@@ -54,6 +50,14 @@ bool IfaceMgr::os_receive4(struct msghdr& /*m*/, Pkt4Ptr& /*pkt*/) {
   return (true); // pretend that we have everything set up for reception.
 }
 
+void
+IfaceMgr::setMatchingPacketFilter(const bool /* direct_response_desired */) {
+    // @todo Currently we ignore the preference to use direct traffic
+    // because it hasn't been implemented for BSD systems.
+    setPacketFilter(PktFilterPtr(new PktFilterInet()));
+}
+
+
 } // end of isc::dhcp namespace
 } // end of dhcp namespace
 

+ 12 - 5
src/lib/dhcp/iface_mgr_linux.cc

@@ -33,6 +33,8 @@
 
 #include <asiolink/io_address.h>
 #include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter_inet.h>
+#include <dhcp/pkt_filter_lpf.h>
 #include <exceptions/exceptions.h>
 #include <util/io/sockaddr_util.h>
 
@@ -494,11 +496,6 @@ void IfaceMgr::detectIfaces() {
     nl.release_list(addr_info);
 }
 
-bool
-IfaceMgr::isDirectResponseSupported() {
-    return (false);
-}
-
 /// @brief sets flag_*_ fields.
 ///
 /// This implementation is OS-specific as bits have different meaning
@@ -515,6 +512,16 @@ void Iface::setFlags(uint32_t flags) {
     flag_broadcast_ = flags & IFF_BROADCAST;
 }
 
+void
+IfaceMgr::setMatchingPacketFilter(const bool direct_response_desired) {
+    if (direct_response_desired) {
+        setPacketFilter(PktFilterPtr(new PktFilterLPF()));
+
+    } else {
+        setPacketFilter(PktFilterPtr(new PktFilterInet()));
+
+    }
+}
 
 void IfaceMgr::os_send4(struct msghdr&, boost::scoped_array<char>&,
                         size_t, const Pkt4Ptr&) {

+ 8 - 5
src/lib/dhcp/iface_mgr_sun.cc

@@ -17,6 +17,7 @@
 #if defined(OS_SUN)
 
 #include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter_inet.h>
 #include <exceptions/exceptions.h>
 
 using namespace std;
@@ -34,11 +35,6 @@ IfaceMgr::detectIfaces() {
     stubDetectIfaces();
 }
 
-bool
-IfaceMgr::isDirectResponseSupported() {
-    return (false);
-}
-
 void IfaceMgr::os_send4(struct msghdr& /*m*/,
                         boost::scoped_array<char>& /*control_buf*/,
                         size_t /*control_buf_len*/,
@@ -54,6 +50,13 @@ bool IfaceMgr::os_receive4(struct msghdr& /*m*/, Pkt4Ptr& /*pkt*/) {
   return (true); // pretend that we have everything set up for reception.
 }
 
+void
+IfaceMgr::setMatchingPacketFilter(const bool /* direct_response_desired */) {
+    // @todo Currently we ignore the preference to use direct traffic
+    // because it hasn't been implemented for Solaris.
+    setPacketFilter(PktFilterPtr(new PktFilterInet()));
+}
+
 } // end of isc::dhcp namespace
 } // end of dhcp namespace
 

+ 44 - 6
src/lib/dhcp/pkt4.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013  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
@@ -276,8 +276,24 @@ Pkt4::toText() {
 }
 
 void
-Pkt4::setHWAddr(uint8_t hType, uint8_t hlen,
+Pkt4::setHWAddr(uint8_t htype, uint8_t hlen,
                 const std::vector<uint8_t>& mac_addr) {
+    setHWAddrMember(htype, hlen, mac_addr, hwaddr_);
+}
+
+void
+Pkt4::setHWAddr(const HWAddrPtr& addr) {
+    if (!addr) {
+        isc_throw(BadValue, "Setting DHCPv4 chaddr field to NULL"
+                  << " is forbidden");
+    }
+    hwaddr_ = addr;
+}
+
+void
+Pkt4::setHWAddrMember(const uint8_t htype, const uint8_t hlen,
+                      const std::vector<uint8_t>& mac_addr,
+                      HWAddrPtr& hw_addr) {
     /// @todo Rewrite this once support for client-identifier option
     /// is implemented (ticket 1228?)
     if (hlen > MAX_CHADDR_LEN) {
@@ -288,15 +304,37 @@ Pkt4::setHWAddr(uint8_t hType, uint8_t hlen,
         isc_throw(OutOfRange, "Invalid HW Address specified");
     }
 
-    hwaddr_.reset(new HWAddr(mac_addr, hType));
+    hw_addr.reset(new HWAddr(mac_addr, htype));
 }
 
 void
-Pkt4::setHWAddr(const HWAddrPtr& addr) {
+Pkt4::setLocalHWAddr(const uint8_t htype, const uint8_t hlen,
+                      const std::vector<uint8_t>& mac_addr) {
+    setHWAddrMember(htype, hlen, mac_addr, local_hwaddr_);
+}
+
+void
+Pkt4::setLocalHWAddr(const HWAddrPtr& addr) {
     if (!addr) {
-        isc_throw(BadValue, "Setting hw address to NULL is forbidden");
+        isc_throw(BadValue, "Setting local HW address to NULL is"
+                  << " forbidden.");
     }
-    hwaddr_ = addr;
+    local_hwaddr_ = addr;
+}
+
+void
+Pkt4::setRemoteHWAddr(const uint8_t htype, const uint8_t hlen,
+                      const std::vector<uint8_t>& mac_addr) {
+    setHWAddrMember(htype, hlen, mac_addr, remote_hwaddr_);
+}
+
+void
+Pkt4::setRemoteHWAddr(const HWAddrPtr& addr) {
+    if (!addr) {
+        isc_throw(BadValue, "Setting remote HW address to NULL is"
+                  << " forbidden.");
+    }
+    remote_hwaddr_ = addr;
 }
 
 void

+ 97 - 3
src/lib/dhcp/pkt4.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 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
@@ -47,6 +47,10 @@ public:
     /// specifies DHCPv4 packet header length (fixed part)
     const static size_t DHCPV4_PKT_HDR_LEN = 236;
 
+    /// Mask for the value of flags field in the DHCPv4 message
+    /// to check whether client requested broadcast response.
+    const static uint16_t FLAG_BROADCAST_MASK = 0x8000;
+
     /// Constructor, used in replying to a message.
     ///
     /// @param msg_type type of message (e.g. DHCPDISOVER=1)
@@ -267,10 +271,10 @@ public:
     ///
     /// Note: mac_addr must be a buffer of at least hlen bytes.
     ///
-    /// @param hType hardware type (will be sent in htype field)
+    /// @param htype hardware type (will be sent in htype field)
     /// @param hlen hardware length (will be sent in hlen field)
     /// @param mac_addr pointer to hardware address
-    void setHWAddr(uint8_t hType, uint8_t hlen,
+    void setHWAddr(uint8_t htype, uint8_t hlen,
                    const std::vector<uint8_t>& mac_addr);
 
     /// @brief Sets hardware address
@@ -363,6 +367,72 @@ public:
     /// @return interface index
     uint32_t getIndex() const { return (ifindex_); };
 
+    /// @brief Sets remote HW address.
+    ///
+    /// Sets the destination HW address for the outgoing packet
+    /// or source HW address for the incoming packet. When this
+    /// is an outgoing packet this address will be used to construct
+    /// the link layer header.
+    ///
+    /// @note mac_addr must be a buffer of at least hlen bytes.
+    ///
+    /// @param htype hardware type (will be sent in htype field)
+    /// @param hlen hardware length (will be sent in hlen field)
+    /// @param mac_addr pointer to hardware address
+    void setRemoteHWAddr(const uint8_t htype, const uint8_t hlen,
+                         const std::vector<uint8_t>& mac_addr);
+
+    /// @brief Sets remote HW address.
+    ///
+    /// Sets hardware address from an existing HWAddr structure.
+    /// The remote address is a destination address for outgoing
+    /// packet and source address for incoming packet. When this
+    /// is an outgoing packet, this address will be used to
+    /// construct the link layer header.
+    ///
+    /// @param addr structure representing HW address.
+    ///
+    /// @throw BadValue if addr is null
+    void setRemoteHWAddr(const HWAddrPtr& addr);
+
+    /// @brief Returns the remote HW address.
+    ///
+    /// @return remote HW address.
+    HWAddrPtr getRemoteHWAddr() const {
+        return (remote_hwaddr_);
+    }
+
+    /// @brief Sets local HW address.
+    ///
+    /// Sets the source HW address for the outgoing packet or
+    /// destination HW address for the incoming packet.
+    ///
+    /// @note mac_addr must be a buffer of at least hlen bytes.
+    ///
+    /// @param htype hardware type (will be sent in htype field)
+    /// @param hlen hardware length (will be sent in hlen field)
+    /// @param mac_addr pointer to hardware address
+    void setLocalHWAddr(const uint8_t htype, const uint8_t hlen,
+                        const std::vector<uint8_t>& mac_addr);
+
+    /// @brief Sets local HW address.
+    ///
+    /// Sets hardware address from an existing HWAddr structure.
+    /// The local address is a source address for outgoing
+    /// packet and destination address for incoming packet.
+    ///
+    /// @param addr structure representing HW address.
+    ///
+    /// @throw BadValue if addr is null
+    void setLocalHWAddr(const HWAddrPtr& addr);
+
+    /// @brief Returns local HW address.
+    ///
+    /// @return local HW addr.
+    HWAddrPtr getLocalHWAddr() const {
+        return (local_hwaddr_);
+    }
+
     /// @brief Sets remote address.
     ///
     /// @param remote specifies remote address
@@ -419,6 +489,23 @@ public:
     /// @throw isc::Unexpected if timestamp update failed
     void updateTimestamp();
 
+private:
+
+    /// @brief Generic method that validates and sets HW address.
+    ///
+    /// This is a generic method used by all modifiers of this class
+    /// which set class members representing HW address.
+    ///
+    /// @param htype hardware type.
+    /// @param hlen hardware length.
+    /// @param mac_addr pointer to actual hardware address.
+    /// @param [out] hw_addr pointer to a class member to be modified.
+    ///
+    /// @trow isc::OutOfRange if invalid HW address specified.
+    void setHWAddrMember(const uint8_t htype, const uint8_t hlen,
+                         const std::vector<uint8_t>& mac_addr,
+                         HWAddrPtr& hw_addr);
+
 protected:
 
     /// converts DHCP message type to BOOTP op type
@@ -429,6 +516,12 @@ protected:
     uint8_t
     DHCPTypeToBootpType(uint8_t dhcpType);
 
+    /// local HW address (dst if receiving packet, src if sending packet)
+    HWAddrPtr local_hwaddr_;
+
+    // remote HW address (src if receiving packet, dst if sending packet)
+    HWAddrPtr remote_hwaddr_;
+
     /// local address (dst if receiving packet, src if sending packet)
     isc::asiolink::IOAddress local_addr_;
 
@@ -533,6 +626,7 @@ protected:
 
     /// packet timestamp
     boost::posix_time::ptime timestamp_;
+
 }; // Pkt4 class
 
 typedef boost::shared_ptr<Pkt4> Pkt4Ptr;

+ 28 - 1
src/lib/dhcp/pkt_filter.h

@@ -15,11 +15,21 @@
 #ifndef PKT_FILTER_H
 #define PKT_FILTER_H
 
+#include <dhcp/pkt4.h>
 #include <asiolink/io_address.h>
+#include <boost/shared_ptr.hpp>
 
 namespace isc {
 namespace dhcp {
 
+/// @brief Exception thrown when invalid packet filter object specified.
+class InvalidPacketFilter : public Exception {
+public:
+    InvalidPacketFilter(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// Forward declaration to the structure describing a socket.
 struct SocketInfo;
 
 /// Forward declaration to the class representing interface
@@ -45,6 +55,18 @@ public:
     /// @brief Virtual Destructor
     virtual ~PktFilter() { }
 
+    /// @brief Check if packet can be sent to the host without address directly.
+    ///
+    /// Checks if the Packet Filter class has capability to send a packet
+    /// directly to the client having no address assigned. This capability
+    /// is used by DHCPv4 servers which respond to the clients they assign
+    /// addresses to. Not all classes derived from PktFilter support this
+    /// because it requires injection of the destination host HW address to
+    /// the link layer header of the packet.
+    ///
+    /// @return true of the direct response is supported.
+    virtual bool isDirectResponseSupported() const = 0;
+
     /// @brief Open socket.
     ///
     /// @param iface interface descriptor
@@ -71,13 +93,18 @@ public:
 
     /// @brief Send packet over specified socket.
     ///
+    /// @param iface interface to be used to send packet
     /// @param sockfd socket descriptor
     /// @param pkt packet to be sent
     ///
     /// @return result of sending the packet. It is 0 if successful.
-    virtual int send(uint16_t sockfd, const Pkt4Ptr& pkt) = 0;
+    virtual int send(const Iface& iface, uint16_t sockfd,
+                     const Pkt4Ptr& pkt) = 0;
 };
 
+/// Pointer to a PktFilter object.
+typedef boost::shared_ptr<PktFilter> PktFilterPtr;
+
 } // namespace isc::dhcp
 } // namespace isc
 

+ 5 - 17
src/lib/dhcp/pkt_filter_inet.cc

@@ -28,25 +28,12 @@ PktFilterInet::PktFilterInet()
 {
 }
 
-// iface is only used when SO_BINDTODEVICE is defined and thus
-// the code section using this variable is compiled.
-#ifdef SO_BINDTODEVICE
 int PktFilterInet::openSocket(const Iface& iface,
                               const isc::asiolink::IOAddress& addr,
                               const uint16_t port,
                               const bool receive_bcast,
                               const bool send_bcast) {
 
-#else
-int PktFilterInet::openSocket(const Iface&,
-                              const isc::asiolink::IOAddress& addr,
-                              const uint16_t port,
-                              const bool receive_bcast,
-                              const bool send_bcast) {
-
-
-#endif
-
     struct sockaddr_in addr4;
     memset(&addr4, 0, sizeof(sockaddr));
     addr4.sin_family = AF_INET;
@@ -54,7 +41,7 @@ int PktFilterInet::openSocket(const Iface&,
 
     // If we are to receive broadcast messages we have to bind
     // to "ANY" address.
-    if (receive_bcast) {
+    if (receive_bcast && iface.flag_broadcast_) {
         addr4.sin_addr.s_addr = INADDR_ANY;
     } else {
         addr4.sin_addr.s_addr = htonl(addr);
@@ -66,7 +53,7 @@ int PktFilterInet::openSocket(const Iface&,
     }
 
 #ifdef SO_BINDTODEVICE
-    if (receive_bcast) {
+    if (receive_bcast && iface.flag_broadcast_) {
         // Bind to device so as we receive traffic on a specific interface.
         if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, iface.getName().c_str(),
                        iface.getName().length() + 1) < 0) {
@@ -77,7 +64,7 @@ int PktFilterInet::openSocket(const Iface&,
     }
 #endif
 
-    if (send_bcast) {
+    if (send_bcast && iface.flag_broadcast_) {
         // Enable sending to broadcast address.
         int flag = 1;
         if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag)) < 0) {
@@ -198,7 +185,8 @@ PktFilterInet::receive(const Iface& iface, const SocketInfo& socket_info) {
 }
 
 int
-PktFilterInet::send(uint16_t sockfd, const Pkt4Ptr& pkt) {
+PktFilterInet::send(const Iface&, uint16_t sockfd,
+                    const Pkt4Ptr& pkt) {
     memset(&control_buf_[0], 0, control_buf_len_);
 
     // Set the target address we're sending to.

+ 15 - 1
src/lib/dhcp/pkt_filter_inet.h

@@ -16,6 +16,7 @@
 #define PKT_FILTER_INET_H
 
 #include <dhcp/pkt_filter.h>
+#include <boost/scoped_array.hpp>
 
 namespace isc {
 namespace dhcp {
@@ -32,6 +33,17 @@ public:
     /// Allocates control buffer.
     PktFilterInet();
 
+    /// @brief Check if packet can be sent to the host without address directly.
+    ///
+    /// This Packet Filter sends packets through AF_INET datagram sockets, so
+    /// it can't inject the HW address of the destionation host into the packet.
+    /// Therefore this class does not support direct responses.
+    ///
+    /// @return false always.
+    virtual bool isDirectResponseSupported() const {
+        return (false);
+    }
+
     /// @brief Open socket.
     ///
     /// @param iface interface descriptor
@@ -57,11 +69,13 @@ public:
 
     /// @brief Send packet over specified socket.
     ///
+    /// @param iface interface to be used to send packet
     /// @param sockfd socket descriptor
     /// @param pkt packet to be sent
     ///
     /// @return result of sending a packet. It is 0 if successful.
-    virtual int send(uint16_t sockfd, const Pkt4Ptr& pkt);
+    virtual int send(const Iface& iface, uint16_t sockfd,
+                     const Pkt4Ptr& pkt);
 
 private:
     /// Length of the control_buf_ array.

+ 233 - 10
src/lib/dhcp/pkt_filter_lpf.cc

@@ -13,31 +13,254 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <config.h>
+#include <dhcp/dhcp4.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/pkt_filter_lpf.h>
+#include <dhcp/protocol_util.h>
+#include <exceptions/exceptions.h>
+#include <linux/filter.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <net/ethernet.h>
+
+namespace {
+
+using namespace isc::dhcp;
+
+/// The following structure defines a Berkely Packet Filter program to perform
+/// packet filtering. The program operates on Ethernet packets.  To help with
+/// interpretation of the program, for the types of Ethernet packets we are
+/// interested in, the header layout is:
+///
+///   6 bytes  Destination Ethernet Address
+///   6 bytes  Source Ethernet Address
+///   2 bytes  Ethernet packet type
+///
+///  20 bytes  Fixed part of IP header
+///  variable  Variable part of IP header
+///
+///   2 bytes  UDP Source port
+///   2 bytes  UDP destination port
+///   4 bytes  Rest of UDP header
+///
+/// @todo We may want to extend the filter to receive packets sent
+/// to the particular IP address assigned to the interface or
+/// broadcast address.
+struct sock_filter dhcp_sock_filter [] = {
+    // Make sure this is an IP packet: check the half-word (two bytes)
+    // at offset 12 in the packet (the Ethernet packet type).  If it
+    // is, advance to the next instruction.  If not, advance 8
+    // instructions (which takes execution to the last instruction in
+    // the sequence: "drop it").
+    BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ETHERNET_PACKET_TYPE_OFFSET),
+    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8),
+
+    // Make sure it's a UDP packet.  The IP protocol is at offset
+    // 9 in the IP header so, adding the Ethernet packet header size
+    // of 14 bytes gives an absolute byte offset in the packet of 23.
+    BPF_STMT(BPF_LD + BPF_B + BPF_ABS, ETHERNET_HEADER_LEN + IP_PROTO_TYPE_OFFSET),
+    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6),
+
+    // Make sure this isn't a fragment by checking that the fragment
+    // offset field in the IP header is zero.  This field is the
+    // least-significant 13 bits in the bytes at offsets 6 and 7 in
+    // the IP header, so the half-word at offset 20 (6 + size of
+    // Ethernet header) is loaded and an appropriate mask applied.
+    BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ETHERNET_HEADER_LEN + IP_FLAGS_OFFSET),
+    BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),
+
+    // Get the IP header length.  This is achieved by the following
+    // (special) instruction that, given the offset of the start
+    // of the IP header (offset 14) loads the IP header length.
+    BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, ETHERNET_HEADER_LEN),
+
+    // Make sure it's to the right port.  The following instruction
+    // adds the previously extracted IP header length to the given
+    // offset to locate the correct byte.  The given offset of 16
+    // comprises the length of the Ethernet header (14) plus the offset
+    // of the UDP destination port (2) within the UDP header.
+    BPF_STMT(BPF_LD + BPF_H + BPF_IND, ETHERNET_HEADER_LEN + UDP_DEST_PORT),
+    // The following instruction tests against the default DHCP server port,
+    // but the action port is actually set in PktFilterLPF::openSocket().
+    // N.B. The code in that method assumes that this instruction is at
+    // offset 8 in the program.  If this is changed, openSocket() must be
+    // updated.
+    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP4_SERVER_PORT, 0, 1),
+
+    // If we passed all the tests, ask for the whole packet.
+    BPF_STMT(BPF_RET + BPF_K, (u_int)-1),
+
+    // Otherwise, drop it.
+    BPF_STMT(BPF_RET + BPF_K, 0),
+};
+
+}
+
+using namespace isc::util;
 
 namespace isc {
 namespace dhcp {
 
 int
-PktFilterLPF::openSocket(const Iface&, const isc::asiolink::IOAddress&,
-                         const uint16_t, const bool,
+PktFilterLPF::openSocket(const Iface& iface, const isc::asiolink::IOAddress&,
+                         const uint16_t port, const bool,
                          const bool) {
-    isc_throw(isc::NotImplemented,
-              "Linux Packet Filtering is not implemented yet");
+
+    int sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
+    if (sock < 0) {
+        isc_throw(SocketConfigError, "Failed to create raw LPF socket");
+    }
+
+    // Create socket filter program. This program will only allow incoming UDP
+    // traffic which arrives on the specific (DHCP) port). It will also filter
+    // out all fragmented packets.
+    struct sock_fprog filter_program;
+    memset(&filter_program, 0, sizeof(filter_program));
+
+    filter_program.filter = dhcp_sock_filter;
+    filter_program.len = sizeof(dhcp_sock_filter) / sizeof(struct sock_filter);
+    // Override the default port value.
+    dhcp_sock_filter[8].k = port;
+    // Apply the filter.
+    if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter_program,
+                   sizeof(filter_program)) < 0) {
+        close(sock);
+        isc_throw(SocketConfigError, "Failed to install packet filtering program"
+                  << " on the socket " << sock);
+    }
+
+    struct sockaddr_ll sa;
+    memset(&sa, 0, sizeof(sockaddr_ll));
+    sa.sll_family = AF_PACKET;
+    sa.sll_ifindex = iface.getIndex();
+
+    // For raw sockets we construct IP headers on our own, so we don't bind
+    // socket to IP address but to the interface. We will later use the
+    // Linux Packet Filtering to filter out these packets that we are
+    // interested in.
+    if (bind(sock, reinterpret_cast<const struct sockaddr*>(&sa),
+             sizeof(sa)) < 0) {
+        close(sock);
+        isc_throw(SocketConfigError, "Failed to bind LPF socket '" << sock
+                  << "' to interface '" << iface.getName() << "'");
+    }
+
+    return (sock);
+
 }
 
 Pkt4Ptr
-PktFilterLPF::receive(const Iface&, const SocketInfo&) {
-    isc_throw(isc::NotImplemented,
-              "Linux Packet Filtering is not implemented yet");
+PktFilterLPF::receive(const Iface& iface, const SocketInfo& socket_info) {
+    uint8_t raw_buf[IfaceMgr::RCVBUFSIZE];
+    int data_len = read(socket_info.sockfd_, raw_buf, sizeof(raw_buf));
+    // If negative value is returned by read(), it indicates that an
+    // error occured. If returned value is 0, no data was read from the
+    // socket. In both cases something has gone wrong, because we expect
+    // that a chunk of data is there. We signal the lack of data by
+    // returing an empty packet.
+    if (data_len <= 0) {
+        return Pkt4Ptr();
+    }
+
+    InputBuffer buf(raw_buf, data_len);
+
+    // @todo: This is awkward way to solve the chicken and egg problem
+    // whereby we don't know the offset where DHCP data start in the
+    // received buffer when we create the packet object. In general case,
+    // the IP header has variable length. The information about its length
+    // is stored in one of its fields. Therefore, we have to decode the
+    // packet to get the offset of the DHCP data. The dummy object is
+    // created so as we can pass it to the functions which decode IP stack
+    // and find actual offset of the DHCP data.
+    // Once we find the offset we can create another Pkt4 object from
+    // the reminder of the input buffer and set the IP addresses and
+    // ports from the dummy packet. We should consider doing it
+    // in some more elegant way.
+    Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0));
+
+    // Decode ethernet, ip and udp headers.
+    decodeEthernetHeader(buf, dummy_pkt);
+    decodeIpUdpHeader(buf, dummy_pkt);
+
+    // Read the DHCP data.
+    std::vector<uint8_t> dhcp_buf;
+    buf.readVector(dhcp_buf, buf.getLength() - buf.getPosition());
+
+    // Decode DHCP data into the Pkt4 object.
+    Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(&dhcp_buf[0], dhcp_buf.size()));
+
+    // Set the appropriate packet members using data collected from
+    // the decoded headers.
+    pkt->setIndex(iface.getIndex());
+    pkt->setIface(iface.getName());
+    pkt->setLocalAddr(dummy_pkt->getLocalAddr());
+    pkt->setRemoteAddr(dummy_pkt->getRemoteAddr());
+    pkt->setLocalPort(dummy_pkt->getLocalPort());
+    pkt->setRemotePort(dummy_pkt->getRemotePort());
+    pkt->setLocalHWAddr(dummy_pkt->getLocalHWAddr());
+    pkt->setRemoteHWAddr(dummy_pkt->getRemoteHWAddr());
+
+    return (pkt);
 }
 
 int
-PktFilterLPF::send(uint16_t, const Pkt4Ptr&) {
-    isc_throw(isc::NotImplemented,
-              "Linux Packet Filtering is not implemented yet");
+PktFilterLPF::send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt) {
+
+    OutputBuffer buf(14);
+
+    HWAddrPtr hwaddr(new HWAddr(iface.getMac(), iface.getMacLen(),
+                                iface.getHWType()));
+    pkt->setLocalHWAddr(hwaddr);
+
+
+    // Ethernet frame header.
+    // Note that we don't validate whether HW addresses in 'pkt'
+    // are valid because they are checked by the function called.
+    writeEthernetHeader(pkt, buf);
+
+    // This object represents broadcast address. We will compare the
+    // local packet address with it a few lines below. Having static
+    // variable guarantees that this object is created only once, not
+    // every time this function is called.
+    static const isc::asiolink::IOAddress bcast_addr("255.255.255.255");
+
+    // It is likely that the local address in pkt object is set to
+    // broadcast address. This is the case if server received the
+    // client's packet on broadcast address. Therefore, we need to
+    // correct it here and assign the actual source address.
+    if (pkt->getLocalAddr() == bcast_addr) {
+        const Iface::SocketCollection& sockets = iface.getSockets();
+        for (Iface::SocketCollection::const_iterator it = sockets.begin();
+             it != sockets.end(); ++it) {
+            if (sockfd == it->sockfd_) {
+                pkt->setLocalAddr(it->addr_);
+            }
+        }
+    }
+
+    // IP and UDP header
+    writeIpUdpHeader(pkt, buf);
+
+    // DHCPv4 message
+    buf.writeData(pkt->getBuffer().getData(), pkt->getBuffer().getLength());
+
+    sockaddr_ll sa;
+    sa.sll_family = AF_PACKET;
+    sa.sll_ifindex = iface.getIndex();
+    sa.sll_protocol = htons(ETH_P_IP);
+    sa.sll_halen = 6;
+
+    int result = sendto(sockfd, buf.getData(), buf.getLength(), 0,
+                        reinterpret_cast<const struct sockaddr*>(&sa),
+                        sizeof(sockaddr_ll));
+    if (result < 0) {
+        isc_throw(SocketWriteError, "failed to send DHCPv4 packet, errno="
+                  << errno << " (check errno.h)");
+    }
+
+    return (0);
+
 }
 
 

+ 14 - 1
src/lib/dhcp/pkt_filter_lpf.h

@@ -17,6 +17,8 @@
 
 #include <dhcp/pkt_filter.h>
 
+#include <util/buffer.h>
+
 namespace isc {
 namespace dhcp {
 
@@ -30,6 +32,15 @@ namespace dhcp {
 class PktFilterLPF : public PktFilter {
 public:
 
+    /// @brief Check if packet can be sent to the host without address directly.
+    ///
+    /// This class supports direct responses to the host without address.
+    ///
+    /// @return true always.
+    virtual bool isDirectResponseSupported() const {
+        return (true);
+    }
+
     /// @brief Open socket.
     ///
     /// @param iface interface descriptor
@@ -57,12 +68,14 @@ public:
 
     /// @brief Send packet over specified socket.
     ///
+    /// @param iface interface to be used to send packet
     /// @param sockfd socket descriptor
     /// @param pkt packet to be sent
     ///
     /// @throw isc::NotImplemented always
     /// @return result of sending a packet. It is 0 if successful.
-    virtual int send(uint16_t sockfd, const Pkt4Ptr& pkt);
+    virtual int send(const Iface& iface, uint16_t sockfd,
+                     const Pkt4Ptr& pkt);
 
 };
 

+ 243 - 0
src/lib/dhcp/protocol_util.cc

@@ -0,0 +1,243 @@
+// Copyright (C) 2013 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 <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/protocol_util.h>
+#include <boost/static_assert.hpp>
+// in_systm.h is required on some some BSD systems
+// complaining that n_time is undefined but used
+// in ip.h.
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+void
+decodeEthernetHeader(InputBuffer& buf, Pkt4Ptr& pkt) {
+    // The size of the buffer to be parsed must not be lower
+    // then the size of the Ethernet frame header.
+    if (buf.getLength() - buf.getPosition() < ETHERNET_HEADER_LEN) {
+        isc_throw(InvalidPacketHeader, "size of ethernet header in received "
+                  << "packet is invalid, expected at least "
+                  << ETHERNET_HEADER_LEN << " bytes, received "
+                  << buf.getLength() - buf.getPosition() << " bytes");
+    }
+    // Packet object must not be NULL. We want to output some values
+    // to this object.
+    if (!pkt) {
+        isc_throw(BadValue, "NULL packet object provided when parsing ethernet"
+                  " frame header");
+    }
+
+    // The size of the single address is always lower then the size of
+    // the header that holds this address. Otherwise, it is a programming
+    // error that we want to detect in the compilation time.
+    BOOST_STATIC_ASSERT(ETHERNET_HEADER_LEN > HWAddr::ETHERNET_HWADDR_LEN);
+
+    // Remember initial position.
+    size_t start_pos = buf.getPosition();
+
+    // Read the destination HW address.
+    std::vector<uint8_t> dest_addr;
+    buf.readVector(dest_addr, HWAddr::ETHERNET_HWADDR_LEN);
+    pkt->setLocalHWAddr(HWTYPE_ETHERNET, HWAddr::ETHERNET_HWADDR_LEN, dest_addr);
+    // Read the source HW address.
+    std::vector<uint8_t> src_addr;
+    buf.readVector(src_addr, HWAddr::ETHERNET_HWADDR_LEN);
+    pkt->setRemoteHWAddr(HWTYPE_ETHERNET, HWAddr::ETHERNET_HWADDR_LEN, src_addr);
+    // Move the buffer read pointer to the end of the Ethernet frame header.
+    buf.setPosition(start_pos + ETHERNET_HEADER_LEN);
+}
+
+void
+decodeIpUdpHeader(InputBuffer& buf, Pkt4Ptr& pkt) {
+    // The size of the buffer must be at least equal to the minimal size of
+    // the IPv4 packet header plus UDP header length.
+    if (buf.getLength() - buf.getPosition() < MIN_IP_HEADER_LEN + UDP_HEADER_LEN) {
+        isc_throw(InvalidPacketHeader, "the total size of the IP and UDP headers in "
+                  << "received packet is invalid, expected at least "
+                  << MIN_IP_HEADER_LEN + UDP_HEADER_LEN
+                  << " bytes, received " << buf.getLength() - buf.getPosition()
+                  << " bytes");
+    }
+
+    // Packet object must not be NULL.
+    if (!pkt) {
+        isc_throw(BadValue, "NULL packet object provided when parsing IP and UDP"
+                  " packet headers");
+    }
+
+    BOOST_STATIC_ASSERT(IP_SRC_ADDR_OFFSET < MIN_IP_HEADER_LEN);
+
+    // Remember initial position of the read pointer.
+    size_t start_pos = buf.getPosition();
+
+    // Read IP header length (mask most significant bits as they indicate IP version).
+    uint8_t ip_len = buf.readUint8() & 0xF;
+    // IP length is the number of 4 byte chunks that construct IPv4 header.
+    // It must not be lower than 5 because first 20 bytes are fixed.
+    if (ip_len < 5) {
+        isc_throw(InvalidPacketHeader, "Value of the length of the IP header must not be"
+                  << " lower than 5 words. The length of the received header is "
+                  << ip_len << ".");
+    }
+
+    // Seek to the position of source IP address.
+    buf.setPosition(start_pos + IP_SRC_ADDR_OFFSET);
+    // Read source address.
+    pkt->setRemoteAddr(IOAddress(buf.readUint32()));
+    // Read destination address.
+    pkt->setLocalAddr(IOAddress(buf.readUint32()));
+
+    // Skip IP header options (if any) to start of the
+    // UDP header.
+    buf.setPosition(start_pos + ip_len * 4);
+
+    // Read source port from UDP header.
+    pkt->setRemotePort(buf.readUint16());
+    // Read destination port from UDP header.
+    pkt->setLocalPort(buf.readUint16());
+
+    // Set the pointer position to the first byte o the
+    // UDP payload (DHCP packet).
+    buf.setPosition(start_pos + ip_len * 4 + UDP_HEADER_LEN);
+}
+
+void
+writeEthernetHeader(const Pkt4Ptr& pkt, OutputBuffer& out_buf) {
+    // Set destination HW address.
+    HWAddrPtr remote_addr = pkt->getRemoteHWAddr();
+    if (remote_addr) {
+        if (remote_addr->hwaddr_.size() == HWAddr::ETHERNET_HWADDR_LEN) {
+            out_buf.writeData(&remote_addr->hwaddr_[0],
+                              HWAddr::ETHERNET_HWADDR_LEN);
+        } else {
+            isc_throw(BadValue, "invalid size of the remote HW address "
+                      << remote_addr->hwaddr_.size() << " when constructing"
+                      << " an ethernet frame header; expected size is"
+                      << " " << HWAddr::ETHERNET_HWADDR_LEN);
+        }
+    } else {
+        // HW address has not been specified. This is possible when receiving
+        // packet through a logical interface (e.g. lo). In such cases, we
+        // don't want to fail but rather provide a default HW address, which
+        // consists of zeros.
+        out_buf.writeData(&std::vector<uint8_t>(HWAddr::ETHERNET_HWADDR_LEN)[0],
+                          HWAddr::ETHERNET_HWADDR_LEN);
+    }
+
+    // Set source HW address.
+    HWAddrPtr local_addr = pkt->getLocalHWAddr();
+    if (local_addr) {
+        if (local_addr->hwaddr_.size() == HWAddr::ETHERNET_HWADDR_LEN) {
+            out_buf.writeData(&local_addr->hwaddr_[0],
+                              HWAddr::ETHERNET_HWADDR_LEN);
+        } else {
+            isc_throw(BadValue, "invalid size of the local HW address "
+                      << local_addr->hwaddr_.size() << " when constructing"
+                      << " an ethernet frame header; expected size is"
+                      << " " << HWAddr::ETHERNET_HWADDR_LEN);
+        }
+    } else {
+        // Provide default HW address.
+        out_buf.writeData(&std::vector<uint8_t>(HWAddr::ETHERNET_HWADDR_LEN)[0],
+                          HWAddr::ETHERNET_HWADDR_LEN);
+    }
+
+    // Type IP.
+    out_buf.writeUint16(ETHERNET_TYPE_IP);
+}
+
+void
+writeIpUdpHeader(const Pkt4Ptr& pkt, util::OutputBuffer& out_buf) {
+
+    out_buf.writeUint8(0x45); // IP version 4, IP header length 5
+    out_buf.writeUint8(IPTOS_LOWDELAY); // DSCP and ECN
+    out_buf.writeUint16(28 + pkt->getBuffer().getLength()); // Total length.
+    out_buf.writeUint16(0); // Identification
+    out_buf.writeUint16(0x4000); // Disable fragmentation.
+    out_buf.writeUint8(128); // TTL
+    out_buf.writeUint8(IPPROTO_UDP); // Protocol UDP.
+    out_buf.writeUint16(0); // Temporarily set checksum to 0.
+    out_buf.writeUint32(pkt->getLocalAddr()); // Source address.
+    out_buf.writeUint32(pkt->getRemoteAddr()); // Destination address.
+
+    // Calculate pseudo header checksum. It will be necessary to compute
+    // UDP checksum.
+    // Get the UDP length. This includes udp header's and data length.
+    uint32_t udp_len = 8 + pkt->getBuffer().getLength();
+    // The magic number "8" indicates the offset where the source address
+    // is stored in the buffer. This offset is counted here from the
+    // current tail of the buffer. Starting from this offset we calculate
+    // the checksum using 8 following bytes of data. This will include
+    // 4 bytes of source address and 4 bytes of destination address.
+    // The IPPROTO_UDP and udp_len are also added up to the checksum.
+    uint16_t pseudo_hdr_checksum =
+        calcChecksum(static_cast<const uint8_t*>(out_buf.getData()) + out_buf.getLength() - 8,
+                     8, IPPROTO_UDP + udp_len);
+
+    // Calculate IP header checksum.
+    uint16_t ip_checksum = ~calcChecksum(static_cast<const uint8_t*>(out_buf.getData())
+                                         + out_buf.getLength() - 20, 20);
+    // Write checksum in the IP header. The offset of the checksum is 10 bytes
+    // back from the tail of the current buffer.
+    out_buf.writeUint16At(ip_checksum, out_buf.getLength() - 10);
+
+    // Start UDP header.
+    out_buf.writeUint16(pkt->getLocalPort()); // Source port.
+    out_buf.writeUint16(pkt->getRemotePort()); // Destination port.
+    out_buf.writeUint16(udp_len); // Length of the header and data.
+
+    // Checksum is calculated from the contents of UDP header, data and pseudo ip header.
+    // The magic number "6" indicates that the UDP header starts at offset 6 from the
+    // tail of the current buffer. These 6 bytes contain source and destination port
+    // as well as the length of the header.
+    uint16_t udp_checksum =
+        ~calcChecksum(static_cast<const uint8_t*>(out_buf.getData()) + out_buf.getLength() - 6, 6,
+                      calcChecksum(static_cast<const uint8_t*>(pkt->getBuffer().getData()),
+                                   pkt->getBuffer().getLength(),
+                                   pseudo_hdr_checksum));
+    // Write UDP checksum.
+    out_buf.writeUint16(udp_checksum);
+}
+
+uint16_t
+calcChecksum(const uint8_t* buf, const uint32_t buf_size, uint32_t sum) {
+    uint32_t i;
+    for (i = 0; i < (buf_size & ~1U); i += 2) {
+        uint16_t chunk = buf[i] << 8 | buf[i + 1];
+        sum += chunk;
+        if (sum > 0xFFFF) {
+            sum -= 0xFFFF;
+        }
+    }
+    // If one byte has left, we also need to add it to the checksum.
+    if (i < buf_size) {
+        sum += buf[i] << 8;
+        if (sum > 0xFFFF) {
+            sum -= 0xFFFF;
+        }
+    }
+
+    return (sum);
+
+}
+
+}
+}

+ 153 - 0
src/lib/dhcp/protocol_util.h

@@ -0,0 +1,153 @@
+// Copyright (C) 2013  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 PROTOCOL_UTIL_H
+#define PROTOCOL_UTIL_H
+
+#include <dhcp/pkt4.h>
+#include <util/buffer.h>
+
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when error occured during parsing packet's headers.
+///
+/// This exception is thrown when parsing link, Internet or Transport layer
+/// header has failed.
+class InvalidPacketHeader : public Exception {
+public:
+    InvalidPacketHeader(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// Size of the Ethernet frame header.
+static const size_t ETHERNET_HEADER_LEN = 14;
+/// Offset of the 2-byte word in the Ethernet packet which
+/// holds the type of the protocol it encapsulates.
+static const size_t ETHERNET_PACKET_TYPE_OFFSET = 12;
+/// This value is held in the Ethertype field of Ethernet frame
+/// and indicates that an IP packet is encapsulated with this
+/// frame. In the standard headers, there is an ETHERTYPE_IP,
+/// constant which serves the same purpose. However, it is more
+/// convenient to have our constant because we avoid
+/// inclusion of additional headers, which have different names
+/// and locations on different OSes.
+static const uint16_t ETHERNET_TYPE_IP = 0x0800;
+
+/// Minimal IPv4 header length.
+static const size_t MIN_IP_HEADER_LEN = 20;
+/// Offset in the IP header where the flags field starts.
+static const size_t IP_FLAGS_OFFSET = 6;
+/// Offset of the byte in IP header which holds the type
+/// of the protocol it encapsulates.
+static const size_t IP_PROTO_TYPE_OFFSET = 9;
+/// Offset of source address in the IPv4 header.
+static const size_t IP_SRC_ADDR_OFFSET = 12;
+
+/// UDP header length.
+static const size_t UDP_HEADER_LEN = 8;
+/// Offset within UDP header where destination port is held.
+static const size_t UDP_DEST_PORT = 2;
+
+/// @brief Decode the Ethernet header.
+///
+/// This function reads Ethernet frame header from the provided
+/// buffer at the current read position. The source HW address
+/// is read from the header and assigned as client address in
+/// the pkt object. The buffer read pointer is set to the end
+/// of the Ethernet frame header if read was successful.
+///
+/// @warning This function does not check that the provided 'pkt'
+/// pointer is valid. Caller must make sure that pointer is
+/// allocated.
+///
+/// @param buf input buffer holding header to be parsed.
+/// @param [out] pkt packet object receiving HW source address read from header.
+///
+/// @throw InvalidPacketHeader if packet header is truncated
+/// @throw BadValue if pkt object is NULL.
+void decodeEthernetHeader(util::InputBuffer& buf, Pkt4Ptr& pkt);
+
+/// @brief Decode IP and UDP header.
+///
+/// This function reads IP and UDP headers from the provided buffer
+/// at the current read position. The source and destination IP
+/// addresses and ports and read from these headers and stored in
+/// the appropriate members of the pkt object.
+///
+/// @warning This function does not check that the provided 'pkt'
+/// pointer is valid. Caller must make sure that pointer is
+/// allocated.
+///
+/// @param buf input buffer holding headers to be parsed.
+/// @param [out] pkt packet object where IP addresses and ports
+/// are stored.
+///
+/// @throw InvalidPacketHeader if packet header is truncated
+/// @throw BadValue if pkt object is NULL.
+void decodeIpUdpHeader(util::InputBuffer& buf, Pkt4Ptr& pkt);
+
+/// @brief Writes ethernet frame header into a buffer.
+///
+/// @warning This function does not check that the provided 'pkt'
+/// pointer is valid. Caller must make sure that pointer is
+/// allocated.
+///
+/// @param pkt packet object holding source and destination HW address.
+/// @param [out] out_buf buffer where a header is written.
+void writeEthernetHeader(const Pkt4Ptr& pkt,
+                         util::OutputBuffer& out_buf);
+
+/// @brief Writes both IP and UDP header into output buffer
+///
+/// This utility function assembles IP and UDP packet headers for the
+/// provided DHCPv4 message. The source and destination addreses and
+/// ports stored in the pkt object are copied as source and destination
+/// addresses and ports into IP/UDP headers.
+///
+/// @warning This function does not check that the provided 'pkt'
+/// pointer is valid. Caller must make sure that pointer is
+/// allocated.
+///
+/// @param pkt DHCPv4 packet to be sent in IP packet
+/// @param [out] out_buf buffer where an IP header is written
+void writeIpUdpHeader(const Pkt4Ptr& pkt, util::OutputBuffer& out_buf);
+
+/// @brief Calculates checksum for provided buffer
+///
+/// This function returns the sum of 16-bit values from the provided
+/// buffer. If the third parameter is specified, it indicates the
+/// initial checksum value. This parameter can be a result of
+/// calcChecksum function's invocation on different data buffer.
+/// The IP or UDP checksum value is a complement of the result returned
+/// by this function. However, this function does not compute complement
+/// of the summed values. It must be calculated outside of this function
+/// before writing the value to the packet buffer.
+///
+/// The IP header checksum calculation algorithm has been defined in
+/// <a href="https://tools.ietf.org/html/rfc791#page-14">RFC 791</a>
+///
+/// @param buf buffer for which the checksum is calculated.
+/// @param buf_size size of the buffer for which checksum is calculated.
+/// @param sum initial checksum value, other values will be added to it.
+///
+/// @return calculated checksum.
+uint16_t calcChecksum(const uint8_t* buf, const uint32_t buf_size,
+                      uint32_t sum = 0);
+
+}
+}
+#endif // PROTOCOL_UTIL_H

+ 7 - 0
src/lib/dhcp/tests/Makefile.am

@@ -44,6 +44,13 @@ libdhcp___unittests_SOURCES += option_space_unittest.cc
 libdhcp___unittests_SOURCES += option_string_unittest.cc
 libdhcp___unittests_SOURCES += pkt4_unittest.cc
 libdhcp___unittests_SOURCES += pkt6_unittest.cc
+libdhcp___unittests_SOURCES += pkt_filter_inet_unittest.cc
+
+if OS_LINUX
+libdhcp___unittests_SOURCES += pkt_filter_lpf_unittest.cc
+endif
+
+libdhcp___unittests_SOURCES += protocol_util_unittest.cc
 libdhcp___unittests_SOURCES += duid_unittest.cc
 
 libdhcp___unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)

+ 164 - 19
src/lib/dhcp/tests/iface_mgr_unittest.cc

@@ -74,6 +74,10 @@ public:
         : open_socket_called_(false) {
     }
 
+    virtual bool isDirectResponseSupported() const {
+        return (false);
+    }
+
     /// Pretends to open socket. Only records a call to this function.
     virtual int openSocket(const Iface&,
                            const isc::asiolink::IOAddress&,
@@ -91,7 +95,7 @@ public:
     }
 
     /// Does nothing
-    virtual int send(uint16_t, const Pkt4Ptr&) {
+    virtual int send(const Iface&, uint16_t, const Pkt4Ptr&) {
         return (0);
     }
 
@@ -103,7 +107,8 @@ public:
 class NakedIfaceMgr: public IfaceMgr {
     // "Naked" Interface Manager, exposes internal fields
 public:
-    NakedIfaceMgr() { }
+    NakedIfaceMgr() {
+    }
     IfaceCollection & getIfacesLst() { return ifaces_; }
 };
 
@@ -117,6 +122,24 @@ public:
     ~IfaceMgrTest() {
     }
 
+    // Get ther number of IPv4 or IPv6 sockets on the loopback interface
+    int getOpenSocketsCount(const Iface& iface, uint16_t family) const {
+        // Get all sockets.
+        Iface::SocketCollection sockets = iface.getSockets();
+
+        // Loop through sockets and try to find the ones which match the
+        // specified type.
+        int sockets_count = 0;
+        for (Iface::SocketCollection::const_iterator sock = sockets.begin();
+             sock != sockets.end(); ++sock) {
+            // Match found, increase the counter.
+            if (sock->family_ == family) {
+                ++sockets_count;
+            }
+        }
+        return (sockets_count);
+    }
+
 };
 
 // We need some known interface to work reliably. Loopback interface is named
@@ -208,6 +231,66 @@ TEST_F(IfaceMgrTest, basic) {
     ASSERT_TRUE(&ifacemgr != 0);
 }
 
+
+// This test verifies that sockets can be closed selectively, i.e. all
+// IPv4 sockets can be closed first and all IPv6 sockets remain open.
+TEST_F(IfaceMgrTest, closeSockets) {
+    // Will be using local loopback addresses for this test.
+    IOAddress loaddr("127.0.0.1");
+    IOAddress loaddr6("::1");
+
+    // Create instance of IfaceMgr.
+    boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr());
+    ASSERT_TRUE(iface_mgr);
+
+    // Out constructor does not detect interfaces by itself. We need
+    // to create one and add.
+    int ifindex = if_nametoindex(LOOPBACK);
+    ASSERT_GT(ifindex, 0);
+    Iface lo_iface(LOOPBACK, ifindex);
+    iface_mgr->getIfacesLst().push_back(lo_iface);
+
+    // Create set of V4 and V6 sockets on the loopback interface.
+    // They must differ by a port they are bound to.
+    for (int i = 0; i < 6; ++i) {
+        // Every other socket will be IPv4.
+        if (i % 2) {
+            ASSERT_NO_THROW(
+                iface_mgr->openSocket(LOOPBACK, loaddr, 10000 + i)
+            );
+        } else {
+            ASSERT_NO_THROW(
+                iface_mgr->openSocket(LOOPBACK, loaddr6, 10000 + i)
+            );
+        }
+    }
+
+    // At the end we should have 3 IPv4 and 3 IPv6 sockets open.
+    Iface* iface = iface_mgr->getIface(LOOPBACK);
+    ASSERT_TRUE(iface != NULL);
+
+    int v4_sockets_count = getOpenSocketsCount(*iface, AF_INET);
+    ASSERT_EQ(3, v4_sockets_count);
+    int v6_sockets_count = getOpenSocketsCount(*iface, AF_INET6);
+    ASSERT_EQ(3, v6_sockets_count);
+
+    // Let's try to close only IPv4 sockets.
+    ASSERT_NO_THROW(iface_mgr->closeSockets(AF_INET));
+    v4_sockets_count = getOpenSocketsCount(*iface, AF_INET);
+    EXPECT_EQ(0, v4_sockets_count);
+    // The IPv6 sockets should remain open.
+    v6_sockets_count = getOpenSocketsCount(*iface, AF_INET6);
+    EXPECT_EQ(3, v6_sockets_count);
+
+    // Let's try to close IPv6 sockets.
+    ASSERT_NO_THROW(iface_mgr->closeSockets(AF_INET6));
+    v4_sockets_count = getOpenSocketsCount(*iface, AF_INET);
+    EXPECT_EQ(0, v4_sockets_count);
+    // They should have been closed now.
+    v6_sockets_count = getOpenSocketsCount(*iface, AF_INET6);
+    EXPECT_EQ(0, v6_sockets_count);
+}
+
 TEST_F(IfaceMgrTest, ifaceClass) {
     // Basic tests for Iface inner class
 
@@ -597,19 +680,11 @@ TEST_F(IfaceMgrTest, socketsFromRemoteAddress) {
     // open sockets on the same ports.
     ifacemgr->closeSockets();
 
-    // The following test is currently disabled for OSes other than
-    // Linux because interface detection is not implemented on them.
-    // @todo enable this test for all OSes once interface detection
-    // is implemented.
-#if defined(OS_LINUX)
-    // Open v4 socket to connect to broadcast address.
-    int socket3  = 0;
-    IOAddress bcastAddr("255.255.255.255");
-    EXPECT_NO_THROW(
-        socket3 = ifacemgr->openSocketFromRemoteAddress(bcastAddr, PORT2);
-    );
-    EXPECT_GT(socket3, 0);
-#endif
+    // There used to be a check here that verified the ability to open
+    // suitable socket for sending broadcast request. However,
+    // there is no guarantee for such test to work on all systems
+    // because some systems may have no broadcast capable interfaces at all.
+    // Thus, this check has been removed.
 
     // Do not call closeSockets() because it is called by IfaceMgr's
     // virtual destructor.
@@ -714,14 +789,12 @@ TEST_F(IfaceMgrTest, sendReceive4) {
 
     // let's assume that every supported OS have lo interface
     IOAddress loAddr("127.0.0.1");
-    int socket1 = 0, socket2 = 0;
+    int socket1 = 0;
     EXPECT_NO_THROW(
         socket1 = ifacemgr->openSocket(LOOPBACK, loAddr, DHCP4_SERVER_PORT + 10000);
-        socket2 = ifacemgr->openSocket(LOOPBACK, loAddr, DHCP4_SERVER_PORT + 10000 + 1);
     );
 
     EXPECT_GE(socket1, 0);
-    EXPECT_GE(socket2, 0);
 
     boost::shared_ptr<Pkt4> sendPkt(new Pkt4(DHCPDISCOVER, 1234) );
 
@@ -754,7 +827,7 @@ TEST_F(IfaceMgrTest, sendReceive4) {
 
     boost::shared_ptr<Pkt4> rcvPkt;
 
-    EXPECT_EQ(true, ifacemgr->send(sendPkt));
+    EXPECT_NO_THROW(ifacemgr->send(sendPkt));
 
     ASSERT_NO_THROW(rcvPkt = ifacemgr->receive4(10));
     ASSERT_TRUE(rcvPkt); // received our own packet
@@ -826,8 +899,80 @@ TEST_F(IfaceMgrTest, setPacketFilter) {
     EXPECT_TRUE(custom_packet_filter->open_socket_called_);
     // This function always returns fake socket descriptor equal to 1024.
     EXPECT_EQ(1024, socket1);
+
+    // Replacing current packet filter object while there are IPv4
+    // sockets open is not allowed!
+    EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter),
+                 PacketFilterChangeDenied);
+
+    // So, let's close the open IPv4 sockets and retry. Now it should succeed.
+    iface_mgr->closeSockets(AF_INET);
+    EXPECT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter));
+}
+
+#if defined OS_LINUX
+
+// This Linux specific test checks whether it is possible to use
+// IfaceMgr to figure out which Pakcket Filter object should be
+// used when direct responses to hosts, having no address assigned
+// are desired or not desired.
+TEST_F(IfaceMgrTest, setMatchingPacketFilter) {
+
+    // Create an instance of IfaceMgr.
+    boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr());
+    ASSERT_TRUE(iface_mgr);
+
+    // Let IfaceMgr figure out which Packet Filter to use when
+    // direct response capability is not desired. It should pick
+    // PktFilterInet.
+    EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(false));
+    // The PktFilterInet is supposed to report lack of direct
+    // response capability.
+    EXPECT_FALSE(iface_mgr->isDirectResponseSupported());
+
+    // There is working implementation of direct responses on Linux
+    // in PktFilterLPF. It uses Linux Packet Filtering as underlying
+    // mechanism. When direct responses are desired the object of
+    // this class should be set.
+    EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(true));
+    // This object should report that direct responses are supported.
+    EXPECT_TRUE(iface_mgr->isDirectResponseSupported());
 }
 
+#else
+
+// This non-Linux specific test checks whether it is possible to use
+// IfaceMgr to figure out which Pakcket Filter object should be
+// used when direct responses to hosts, having no address assigned
+// are desired or not desired. Since direct responses aren't supported
+// on systems other than Linux the function under test should always
+// set object of PktFilterInet type as current Packet Filter. This
+// object does not support direct responses. Once implementation is
+// added on non-Linux systems the OS specific version of the test
+// will be removed.
+TEST_F(IfaceMgrTest, setMatchingPacketFilter) {
+
+    // Create an instance of IfaceMgr.
+    boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr());
+    ASSERT_TRUE(iface_mgr);
+
+    // Let IfaceMgr figure out which Packet Filter to use when
+    // direct response capability is not desired. It should pick
+    // PktFilterInet.
+    EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(false));
+    // The PktFilterInet is supposed to report lack of direct
+    // response capability.
+    EXPECT_FALSE(iface_mgr->isDirectResponseSupported());
+
+    // On non-Linux systems, we are missing the direct traffic
+    // implementation. Therefore, we expect that PktFilterInet
+    // object will be set.
+    EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(true));
+    // This object should report lack of direct response capability.
+    EXPECT_FALSE(iface_mgr->isDirectResponseSupported());
+}
+
+#endif
 
 TEST_F(IfaceMgrTest, socket4) {
 

+ 45 - 0
src/lib/dhcp/tests/pkt4_unittest.cc

@@ -653,4 +653,49 @@ TEST(Pkt4Test, hwaddr) {
     EXPECT_TRUE(hwaddr == pkt->getHWAddr());
 }
 
+// This test verifies that the packet remte and local HW address can
+// be set and returned.
+TEST(Pkt4Test, hwaddrSrcRemote) {
+    scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
+    const uint8_t src_hw[] = { 1, 2, 3, 4, 5, 6 };
+    const uint8_t dst_hw[] = { 7, 8, 9, 10, 11, 12 };
+    const uint8_t hw_type = 123;
+
+    HWAddrPtr dst_hwaddr(new HWAddr(dst_hw, sizeof(src_hw), hw_type));
+    HWAddrPtr src_hwaddr(new HWAddr(src_hw, sizeof(src_hw), hw_type));
+
+    // Check that we can set the local address.
+    EXPECT_NO_THROW(pkt->setLocalHWAddr(dst_hwaddr));
+    EXPECT_TRUE(dst_hwaddr == pkt->getLocalHWAddr());
+
+    // Check that we can set the remote address.
+    EXPECT_NO_THROW(pkt->setRemoteHWAddr(src_hwaddr));
+    EXPECT_TRUE(src_hwaddr == pkt->getRemoteHWAddr());
+
+    // Can't set the NULL addres.
+    EXPECT_THROW(pkt->setRemoteHWAddr(HWAddrPtr()), BadValue);
+    EXPECT_THROW(pkt->setLocalHWAddr(HWAddrPtr()), BadValue);
+
+    // Test alternative way to set local address.
+    const uint8_t dst_hw2[] = { 19, 20, 21, 22, 23, 24 };
+    std::vector<uint8_t> dst_hw_vec(dst_hw2, dst_hw2 + sizeof(dst_hw2));
+    const uint8_t hw_type2 = 234;
+    EXPECT_NO_THROW(pkt->setLocalHWAddr(hw_type2, sizeof(dst_hw2), dst_hw_vec));
+    HWAddrPtr local_addr = pkt->getLocalHWAddr();
+    ASSERT_TRUE(local_addr);
+    EXPECT_EQ(hw_type2, local_addr->htype_);
+    EXPECT_TRUE(std::equal(dst_hw_vec.begin(), dst_hw_vec.end(),
+                           local_addr->hwaddr_.begin()));
+
+    // Set remote address.
+    const uint8_t src_hw2[] = { 25, 26, 27, 28, 29, 30 };
+    std::vector<uint8_t> src_hw_vec(src_hw2, src_hw2 + sizeof(src_hw2));
+    EXPECT_NO_THROW(pkt->setRemoteHWAddr(hw_type2, sizeof(src_hw2), src_hw_vec));
+    HWAddrPtr remote_addr = pkt->getRemoteHWAddr();
+    ASSERT_TRUE(remote_addr);
+    EXPECT_EQ(hw_type2, remote_addr->htype_);
+    EXPECT_TRUE(std::equal(src_hw_vec.begin(), src_hw_vec.end(),
+                           remote_addr->hwaddr_.begin()));
+}
+
 } // end of anonymous namespace

+ 269 - 0
src/lib/dhcp/tests/pkt_filter_inet_unittest.cc

@@ -0,0 +1,269 @@
+// Copyright (C) 2013 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 <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt_filter_inet.h>
+
+#include <gtest/gtest.h>
+
+#include <sys/socket.h>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace {
+
+/// Port number used by tests.
+const uint16_t PORT = 10067;
+/// Size of the buffer holding received packets.
+const size_t RECV_BUF_SIZE = 2048;
+
+/// This class handles has simple algorithm checking
+/// presence of loopback interface and initializing
+/// its index.
+class PktFilterInetTest : public ::testing::Test {
+public:
+    PktFilterInetTest() {
+        // Initialize ifname_ and ifindex_.
+        loInit();
+    }
+
+    ~PktFilterInetTest() {
+        // Cleanup after each test. This guarantees
+        // that the socket does not hang after a test.
+        close(socket_);
+    }
+
+    /// @brief Detect loopback interface.
+    ///
+    /// @todo this function will be removed once cross-OS interface
+    /// detection is implemented
+    void loInit() {
+        if (if_nametoindex("lo") > 0) {
+            ifname_ = "lo";
+            ifindex_ = if_nametoindex("lo");
+
+        } else if (if_nametoindex("lo0") > 0) {
+            ifname_ = "lo0";
+            ifindex_ = if_nametoindex("lo0");
+
+        } else {
+            std::cout << "Failed to detect loopback interface. Neither "
+                      << "lo nor lo0 worked. Giving up." << std::endl;
+            FAIL();
+
+
+
+        }
+    }
+
+    std::string ifname_; ///< Loopback interface name
+    uint16_t ifindex_;   ///< Loopback interface index.
+    int socket_;         ///< Socket descriptor.
+
+};
+
+// This test verifies that the PktFilterInet class reports its lack
+// of capability to send packets to the host having no IP address
+// assigned.
+TEST_F(PktFilterInetTest, isDirectResponseSupported) {
+    // Create object under test.
+    PktFilterInet pkt_filter;
+    // This Packet Filter class does not support direct responses
+    // under any conditions.
+    EXPECT_FALSE(pkt_filter.isDirectResponseSupported());
+}
+
+// This test verifies that the INET datagram socket is correctly opened and
+// bound to the appropriate address and port.
+TEST_F(PktFilterInetTest, openSocket) {
+    // Create object representing loopback interface.
+    Iface iface(ifname_, ifindex_);
+    // Set loopback address.
+    IOAddress addr("127.0.0.1");
+
+    // Try to open socket.
+    PktFilterInet pkt_filter;
+    socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+    // Check that socket has been opened.
+    ASSERT_GE(socket_, 0);
+
+    // Verify that the socket belongs to AF_INET family.
+    sockaddr_in sock_address;
+    socklen_t sock_address_len = sizeof(sock_address);
+    ASSERT_EQ(0, getsockname(socket_, reinterpret_cast<sockaddr*>(&sock_address),
+                             &sock_address_len));
+    EXPECT_EQ(AF_INET, sock_address.sin_family);
+
+    // Verify that the socket is bound the appropriate address.
+    const std::string bind_addr(inet_ntoa(sock_address.sin_addr));
+    EXPECT_EQ("127.0.0.1", bind_addr);
+
+    // Verify that the socket is bound to appropriate port.
+    EXPECT_EQ(PORT, ntohs(sock_address.sin_port));
+
+    // Verify that the socket has SOCK_DGRAM type.
+    int sock_type;
+    socklen_t sock_type_len = sizeof(sock_type);
+    ASSERT_EQ(0, getsockopt(socket_, SOL_SOCKET, SO_TYPE, &sock_type, &sock_type_len));
+    EXPECT_EQ(SOCK_DGRAM, sock_type);
+}
+
+// This test verifies that the packet is correctly sent over the INET
+// datagram socket.
+TEST_F(PktFilterInetTest, send) {
+    // Let's create a DHCPv4 packet.
+    Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
+    ASSERT_TRUE(pkt);
+
+    // Set required fields.
+    pkt->setLocalAddr(IOAddress("127.0.0.1"));
+    pkt->setRemoteAddr(IOAddress("127.0.0.1"));
+    pkt->setRemotePort(PORT);
+    pkt->setLocalPort(PORT + 1);
+    pkt->setIndex(ifindex_);
+    pkt->setIface(ifname_);
+    pkt->setHops(6);
+    pkt->setSecs(42);
+    pkt->setCiaddr(IOAddress("192.0.2.1"));
+    pkt->setSiaddr(IOAddress("192.0.2.2"));
+    pkt->setYiaddr(IOAddress("192.0.2.3"));
+    pkt->setGiaddr(IOAddress("192.0.2.4"));
+
+    // Create the on-wire data.
+    ASSERT_NO_THROW(pkt->pack());
+
+    // Packet will be sent over loopback interface.
+    Iface iface(ifname_, ifindex_);
+    IOAddress addr("127.0.0.1");
+
+    // Create an instance of the class which we are testing.
+    PktFilterInet pkt_filter;
+    // Open socket. We don't check that the socket has appropriate
+    // options and family set because we have checked that in the
+    // openSocket test already.
+    socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+    ASSERT_GE(socket_, 0);
+
+    // Send the packet over the socket.
+    ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
+
+    // Read the data from socket.
+    fd_set readfds;
+    FD_ZERO(&readfds);
+    FD_SET(socket_, &readfds);
+    
+    struct timeval timeout;
+    timeout.tv_sec = 5;
+    timeout.tv_usec = 0;
+    int result = select(socket_ + 1, &readfds, NULL, NULL, &timeout);
+    // We should receive some data from loopback interface.
+    ASSERT_GT(result, 0);
+
+    // Get the actual data.
+    uint8_t rcv_buf[RECV_BUF_SIZE];
+    result = recv(socket_, rcv_buf, RECV_BUF_SIZE, 0);
+    ASSERT_GT(result, 0);
+
+    // Create the DHCPv4 packet from the received data.
+    Pkt4Ptr rcvd_pkt(new Pkt4(rcv_buf, result));
+    ASSERT_TRUE(rcvd_pkt);
+
+    // Parse the packet.
+    ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+    // Verify that the received packet matches sent packet.
+    EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
+    EXPECT_EQ(pkt->getOp(),   rcvd_pkt->getOp());
+    EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
+    EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
+    EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
+    EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
+    EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
+    EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
+    EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
+    EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
+    EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
+    EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
+    EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
+}
+
+// This test verifies that the DHCPv4 packet is correctly received via
+// INET datagram socket and that it matches sent packet.
+TEST_F(PktFilterInetTest, receive) {
+    // Let's create a DHCPv4 packet.
+    Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
+    ASSERT_TRUE(pkt);
+
+    // Set required fields.
+    pkt->setLocalAddr(IOAddress("127.0.0.1"));
+    pkt->setRemoteAddr(IOAddress("127.0.0.1"));
+    pkt->setRemotePort(PORT);
+    pkt->setLocalPort(PORT + 1);
+    pkt->setIndex(ifindex_);
+    pkt->setIface(ifname_);
+    pkt->setHops(6);
+    pkt->setSecs(42);
+    pkt->setCiaddr(IOAddress("192.0.2.1"));
+    pkt->setSiaddr(IOAddress("192.0.2.2"));
+    pkt->setYiaddr(IOAddress("192.0.2.3"));
+    pkt->setGiaddr(IOAddress("192.0.2.4"));
+
+    // Create the on-wire data.
+    ASSERT_NO_THROW(pkt->pack());
+
+    // Packet will be sent over loopback interface.
+    Iface iface(ifname_, ifindex_);
+    IOAddress addr("127.0.0.1");
+
+    // Create an instance of the class which we are testing.
+    PktFilterInet pkt_filter;
+    // Open socket. We don't check that the socket has appropriate
+    // options and family set because we have checked that in the
+    // openSocket test already.
+    socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+    ASSERT_GE(socket_, 0);
+
+    // Send the packet over the socket.
+    ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
+
+    // Receive the packet.
+    SocketInfo socket_info(socket_, IOAddress("127.0.0.1"), PORT);
+    Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, socket_info);
+    // Check that the packet has been correctly received.
+    ASSERT_TRUE(rcvd_pkt);
+
+    // Parse the packet.
+    ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+    // Verify that the received packet matches sent packet.
+    EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
+    EXPECT_EQ(pkt->getOp(),   rcvd_pkt->getOp());
+    EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
+    EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
+    EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
+    EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
+    EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
+    EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
+    EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
+    EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
+    EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
+    EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
+    EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
+}
+
+} // anonymous namespace

+ 293 - 0
src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc

@@ -0,0 +1,293 @@
+// Copyright (C) 2013 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 <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt_filter_lpf.h>
+#include <dhcp/protocol_util.h>
+#include <util/buffer.h>
+
+#include <gtest/gtest.h>
+
+#include <linux/if_packet.h>
+#include <sys/socket.h>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+/// Port number used by tests.
+const uint16_t PORT = 10067;
+/// Size of the buffer holding received packets.
+const size_t RECV_BUF_SIZE = 2048;
+
+/// This class handles has simple algorithm checking
+/// presence of loopback interface and initializing
+/// its index.
+class PktFilterLPFTest : public ::testing::Test {
+public:
+    PktFilterLPFTest() {
+        // Initialize ifname_ and ifindex_.
+        loInit();
+    }
+
+    ~PktFilterLPFTest() {
+        // Cleanup after each test. This guarantees
+        // that the socket does not hang after a test.
+        close(socket_);
+    }
+
+    /// @brief Detect loopback interface.
+    ///
+    /// @todo this function will be removed once cross-OS interface
+    /// detection is implemented
+    void loInit() {
+        if (if_nametoindex("lo") > 0) {
+            ifname_ = "lo";
+            ifindex_ = if_nametoindex("lo");
+
+        } else if (if_nametoindex("lo0") > 0) {
+            ifname_ = "lo0";
+            ifindex_ = if_nametoindex("lo0");
+
+        } else {
+            std::cout << "Failed to detect loopback interface. Neither "
+                      << "lo nor lo0 worked. Giving up." << std::endl;
+            FAIL();
+
+
+
+        }
+    }
+
+    std::string ifname_; ///< Loopback interface name
+    uint16_t ifindex_;   ///< Loopback interface index.
+    int socket_;         ///< Socket descriptor.
+
+};
+
+// This test verifies that the PktFilterLPF class reports its capability
+// to send packets to the host having no IP address assigned.
+TEST_F(PktFilterLPFTest, isDirectResponseSupported) {
+    // Create object under test.
+    PktFilterLPF pkt_filter;
+    // Must support direct responses.
+    EXPECT_TRUE(pkt_filter.isDirectResponseSupported());
+}
+
+// All tests below require root privileges to execute successfully. If
+// they are run as non-root user they will fail due to insufficient privileges
+// to open raw network sockets. Therefore, they should remain disabled by default
+// and "DISABLED_" tags should not be removed. If one is willing to run these
+// tests please run "make check" as root and enable execution of disabled tests
+// by setting GTEST_ALSO_RUN_DISABLED_TESTS to a value other than 0. In order
+// to run tests from this particular file, set the GTEST_FILTER environmental
+// variable to "PktFilterLPFTest.*" apart from GTEST_ALSO_RUN_DISABLED_TESTS
+// setting.
+
+// This test verifies that the raw AF_PACKET family socket can
+// be opened and bound to the specific interface.
+TEST_F(PktFilterLPFTest, DISABLED_openSocket) {
+    // Create object representing loopback interface.
+    Iface iface(ifname_, ifindex_);
+    // Set loopback address.
+    IOAddress addr("127.0.0.1");
+
+    // Try to open socket.
+    PktFilterLPF pkt_filter;
+    socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+    // Check that socket has been opened.
+    ASSERT_GE(socket_, 0);
+
+    // Verify that the socket belongs to AF_PACKET family.
+    sockaddr_ll sock_address;
+    socklen_t sock_address_len = sizeof(sock_address);
+    ASSERT_EQ(0, getsockname(socket_, reinterpret_cast<sockaddr*>(&sock_address),
+                             &sock_address_len));
+    EXPECT_EQ(AF_PACKET, sock_address.sll_family);
+
+    // Verify that the socket is bound to appropriate interface.
+    EXPECT_EQ(ifindex_, sock_address.sll_ifindex);
+
+    // Verify that the socket has SOCK_RAW type.
+    int sock_type;
+    socklen_t sock_type_len = sizeof(sock_type);
+    ASSERT_EQ(0, getsockopt(socket_, SOL_SOCKET, SO_TYPE, &sock_type, &sock_type_len));
+    EXPECT_EQ(SOCK_RAW, sock_type);
+}
+
+// This test verifies correctness of sending DHCP packet through the raw
+// socket, whereby all IP stack headers are hand-crafted.
+TEST_F(PktFilterLPFTest, DISABLED_send) {
+        // Let's create a DHCPv4 packet.
+    Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
+    ASSERT_TRUE(pkt);
+
+    // Set required fields.
+    // By setting the local address to broadcast we simulate the
+    // typical scenario when client's request was send to broadcast
+    // address and server by default used it as a source address
+    // in its response. The send() function should be able to detect
+    // it and correct the source address.
+    pkt->setLocalAddr(IOAddress("255.255.255.255"));
+    pkt->setRemoteAddr(IOAddress("127.0.0.1"));
+    pkt->setRemotePort(PORT);
+    pkt->setLocalPort(PORT + 1);
+    pkt->setIndex(ifindex_);
+    pkt->setIface(ifname_);
+    pkt->setHops(6);
+    pkt->setSecs(42);
+    pkt->setCiaddr(IOAddress("192.0.2.1"));
+    pkt->setSiaddr(IOAddress("192.0.2.2"));
+    pkt->setYiaddr(IOAddress("192.0.2.3"));
+    pkt->setGiaddr(IOAddress("192.0.2.4"));
+
+    // Create the on-wire data.
+    ASSERT_NO_THROW(pkt->pack());
+
+    // Packet will be sent over loopback interface.
+    Iface iface(ifname_, ifindex_);
+    IOAddress addr("127.0.0.1");
+
+    // Create an instance of the class which we are testing.
+    PktFilterLPF pkt_filter;
+    // Open socket. We don't check that the socket has appropriate
+    // options and family set because we have checked that in the
+    // openSocket test already.
+    socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+    ASSERT_GE(socket_, 0);
+
+    // Send the packet over the socket.
+    ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
+
+    // Read the data from socket.
+    fd_set readfds;
+    FD_ZERO(&readfds);
+    FD_SET(socket_, &readfds);
+    
+    struct timeval timeout;
+    timeout.tv_sec = 5;
+    timeout.tv_usec = 0;
+    int result = select(socket_ + 1, &readfds, NULL, NULL, &timeout);
+    // We should receive some data from loopback interface.
+    ASSERT_GT(result, 0);
+
+    // Get the actual data.
+    uint8_t rcv_buf[RECV_BUF_SIZE];
+    result = recv(socket_, rcv_buf, RECV_BUF_SIZE, 0);
+    ASSERT_GT(result, 0);
+
+    Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0));
+
+    InputBuffer buf(rcv_buf, result);
+
+    // Decode ethernet, ip and udp headers.
+    decodeEthernetHeader(buf, dummy_pkt);
+    decodeIpUdpHeader(buf, dummy_pkt);
+
+    // Create the DHCPv4 packet from the received data.
+    std::vector<uint8_t> dhcp_buf;
+    buf.readVector(dhcp_buf, buf.getLength() - buf.getPosition());
+    Pkt4Ptr rcvd_pkt(new Pkt4(&dhcp_buf[0], dhcp_buf.size()));
+    ASSERT_TRUE(rcvd_pkt);
+
+    // Parse the packet.
+    ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+    // Verify that the received packet matches sent packet.
+    EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
+    EXPECT_EQ(pkt->getOp(),   rcvd_pkt->getOp());
+    EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
+    EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
+    EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
+    EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
+    EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
+    EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
+    EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
+    EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
+    EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
+    EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
+    EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
+}
+
+// This test verifies correctness of reception of the DHCP packet over
+// raw socket, whereby all IP stack headers are hand-crafted.
+TEST_F(PktFilterLPFTest, DISABLED_receive) {
+
+    // Let's create a DHCPv4 packet.
+    Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
+    ASSERT_TRUE(pkt);
+
+    // Set required fields.
+    pkt->setLocalAddr(IOAddress("127.0.0.1"));
+    pkt->setRemoteAddr(IOAddress("127.0.0.1"));
+    pkt->setRemotePort(PORT);
+    pkt->setLocalPort(PORT + 1);
+    pkt->setIndex(ifindex_);
+    pkt->setIface(ifname_);
+    pkt->setHops(6);
+    pkt->setSecs(42);
+    pkt->setCiaddr(IOAddress("192.0.2.1"));
+    pkt->setSiaddr(IOAddress("192.0.2.2"));
+    pkt->setYiaddr(IOAddress("192.0.2.3"));
+    pkt->setGiaddr(IOAddress("192.0.2.4"));
+
+    // Create the on-wire data.
+    ASSERT_NO_THROW(pkt->pack());
+
+    // Packet will be sent over loopback interface.
+    Iface iface(ifname_, ifindex_);
+    IOAddress addr("127.0.0.1");
+
+    // Create an instance of the class which we are testing.
+    PktFilterLPF pkt_filter;
+    // Open socket. We don't check that the socket has appropriate
+    // options and family set because we have checked that in the
+    // openSocket test already.
+    socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+    ASSERT_GE(socket_, 0);
+
+    // Send the packet over the socket.
+    ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
+
+    // Receive the packet.
+    SocketInfo socket_info(socket_, IOAddress("127.0.0.1"), PORT);
+    Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, socket_info);
+    // Check that the packet has been correctly received.
+    ASSERT_TRUE(rcvd_pkt);
+
+    // Parse the packet.
+    ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+    // Verify that the received packet matches sent packet.
+    EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
+    EXPECT_EQ(pkt->getOp(),   rcvd_pkt->getOp());
+    EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
+    EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
+    EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
+    EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
+    EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
+    EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
+    EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
+    EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
+    EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
+    EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
+    EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
+}
+
+} // anonymous namespace

+ 390 - 0
src/lib/dhcp/tests/protocol_util_unittest.cc

@@ -0,0 +1,390 @@
+// Copyright (C) 2013 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 <dhcp/dhcp6.h>
+#include <dhcp/hwaddr.h>
+#include <dhcp/protocol_util.h>
+#include <util/buffer.h>
+#include <gtest/gtest.h>
+// in_systm.h is required on some some BSD systems
+// complaining that n_time is undefined but used
+// in ip.h.
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+    /*/// @brief OptionCustomTest test class.
+class OptionCustomTest : public ::testing::Test {
+public:
+};*/
+
+/// The purpose of this test is to verify that the IP header checksum
+/// is calculated correctly.
+TEST(ProtocolUtilTest, checksum) {
+    // IPv4 header to be used to calculate checksum.
+    const uint8_t hdr[] = {
+        0x45,                      // IP version and header length
+        0x00,                      // TOS
+        0x00, 0x3c,                // Total length of the IP packet.
+        0x1c, 0x46,                // Identification field.
+        0x40, 0x00,                // Fragmentation.
+        0x40,                      // TTL
+        0x06,                      // Protocol
+        0x00, 0x00,                // Checksum (reset to 0x00).
+        0xac, 0x10, 0x0a, 0x63,    // Source IP address.
+        0xac, 0x10, 0x0a, 0x0c     // Destination IP address.
+    };
+    // Calculate size of the header array.
+    const uint32_t hdr_size = sizeof(hdr) / sizeof(hdr[0]);
+    // Get the actual checksum.
+    uint16_t chksum = ~calcChecksum(hdr, hdr_size);
+    // The 0xb1e6 value has been calculated by other means.
+    EXPECT_EQ(0xb1e6, chksum);
+    // Tested function may also take the initial value of the sum.
+    // Let's set it to 2 and see whether it is included in the
+    // calculation.
+    chksum = ~calcChecksum(hdr, hdr_size, 2);
+    // The checkum value should change.
+    EXPECT_EQ(0xb1e4, chksum);
+}
+
+// The purpose of this test is to verify that the Ethernet frame header
+// can be decoded correctly. In particular it verifies that the source
+// HW address can be extracted from it.
+TEST(ProtocolUtilTest, decodeEthernetHeader) {
+    // Source HW address, 6 bytes.
+    const uint8_t src_hw_addr[6] = {
+        0x10, 0x11, 0x12, 0x13, 0x14, 0x15
+    };
+    // Destination HW address, 6 bytes.
+    const uint8_t dest_hw_addr[6] = {
+        0x20, 0x31, 0x42, 0x53, 0x64, 0x75
+    };
+
+    // Prepare a buffer holding Ethernet frame header and 4 bytes of
+    // dummy data.
+    OutputBuffer buf(1);
+    buf.writeData(dest_hw_addr, sizeof(dest_hw_addr));
+    buf.writeData(src_hw_addr, sizeof(src_hw_addr));
+    buf.writeUint16(ETHERNET_TYPE_IP);
+    // Append dummy data. We will later check that this data is not
+    // removed or corrupted when reading the ethernet header.
+    buf.writeUint32(0x01020304);
+
+    // Create a buffer with truncated ethernet frame header..
+    InputBuffer in_buf_truncated(buf.getData(), buf.getLength() - 6);
+    // But provide valid packet object to make sure that the function
+    // under test does not throw due to NULL pointer packet.
+    Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0));
+    // Function should throw because header data is truncated.
+    EXPECT_THROW(decodeEthernetHeader(in_buf_truncated, pkt),
+                 InvalidPacketHeader);
+
+    // Get not truncated buffer.
+    InputBuffer in_buf(buf.getData(), buf.getLength());
+    // But provide NULL packet object instead.
+    pkt.reset();
+    // It should throw again but a different exception.
+    EXPECT_THROW(decodeEthernetHeader(in_buf, pkt),
+                 BadValue);
+    // Now provide, correct data.
+    pkt.reset(new Pkt4(DHCPDISCOVER, 0));
+    // It should not throw now.
+    ASSERT_NO_THROW(decodeEthernetHeader(in_buf, pkt));
+    // Verify that the destination HW address has been initialized...
+    HWAddrPtr checked_dest_hwaddr = pkt->getLocalHWAddr();
+    ASSERT_TRUE(checked_dest_hwaddr);
+    // and is correct.
+    EXPECT_EQ(HWTYPE_ETHERNET, checked_dest_hwaddr->htype_);
+    ASSERT_EQ(sizeof(dest_hw_addr), checked_dest_hwaddr->hwaddr_.size());
+    EXPECT_TRUE(std::equal(dest_hw_addr, dest_hw_addr + sizeof(dest_hw_addr),
+                           checked_dest_hwaddr->hwaddr_.begin()));
+
+    // Verify that the HW address of the source has been initialized.
+    HWAddrPtr checked_src_hwaddr = pkt->getRemoteHWAddr();
+    ASSERT_TRUE(checked_src_hwaddr);
+    // And that it is correct.
+    EXPECT_EQ(HWTYPE_ETHERNET, checked_src_hwaddr->htype_);
+    ASSERT_EQ(sizeof(src_hw_addr), checked_src_hwaddr->hwaddr_.size());
+    EXPECT_TRUE(std::equal(src_hw_addr, src_hw_addr + sizeof(src_hw_addr),
+                           checked_src_hwaddr->hwaddr_.begin()));
+
+    // The entire ethernet packet header should have been read. This means
+    // that the internal buffer pointer should now point to its tail.
+    ASSERT_EQ(ETHERNET_HEADER_LEN, in_buf.getPosition());
+    // And the dummy data should be still readable and correct.
+    uint32_t dummy_data = in_buf.readUint32();
+    EXPECT_EQ(0x01020304, dummy_data);
+}
+
+/// The purpose of this test is to verify that the IP and UDP header
+/// is decoded correctly and appropriate values of IP addresses and
+/// ports are assigned to a Pkt4 object.
+TEST(ProtocolUtilTest, decodeIpUdpHeader) {
+    // IPv4 header to be parsed.
+    const uint8_t hdr[] = {
+        0x45,                      // IP version and header length
+        0x00,                      // TOS
+        0x00, 0x3c,                // Total length of the IP packet.
+        0x1c, 0x46,                // Identification field.
+        0x40, 0x00,                // Fragmentation.
+        0x40,                      // TTL
+        IPPROTO_UDP,               // Protocol
+        0x00, 0x00,                // Checksum (reset to 0x00).
+        0xc0, 0x00, 0x02, 0x63,    // Source IP address.
+        0xc0, 0x00, 0x02, 0x0c,    // Destination IP address.
+        0x27, 0x54,                // Source port
+        0x27, 0x53,                // Destination port
+        0x00, 0x08,                // UDP length
+        0x00, 0x00                 // Checksum
+    };
+
+    // Write header data to the buffer.
+    OutputBuffer buf(1);
+    buf.writeData(hdr, sizeof(hdr));
+    // Append some dummy data.
+    buf.writeUint32(0x01020304);
+
+    // Create an input buffer holding truncated headers.
+    InputBuffer in_buf_truncated(buf.getData(), buf.getLength() - 10);
+    // Create non NULL Pkt4 object to make sure that the function under
+    // test does not throw due to invalid Pkt4 object.
+    Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0));
+    // Function should throw because buffer holds truncated data.
+    EXPECT_THROW(decodeIpUdpHeader(in_buf_truncated, pkt), InvalidPacketHeader);
+
+    // Create a valid input buffer (not truncated).
+    InputBuffer in_buf(buf.getData(), buf.getLength());
+    // Set NULL Pkt4 object to verify that function under test will
+    // return exception as expected.
+    pkt.reset();
+    // And check whether it throws exception.
+    EXPECT_THROW(decodeIpUdpHeader(in_buf, pkt), BadValue);
+
+    // Now, let's provide valid arguments and make sure it doesn't throw.
+    pkt.reset(new Pkt4(DHCPDISCOVER, 0));
+    ASSERT_TRUE(pkt);
+    EXPECT_NO_THROW(decodeIpUdpHeader(in_buf, pkt));
+
+    // Verify the source address and port.
+    EXPECT_EQ("192.0.2.99", pkt->getRemoteAddr().toText());
+    EXPECT_EQ(10068, pkt->getRemotePort());
+
+    // Verify the destination address and port.
+    EXPECT_EQ("192.0.2.12", pkt->getLocalAddr().toText());
+    EXPECT_EQ(10067, pkt->getLocalPort());
+
+    // Verify that the dummy data has not been corrupted and that the
+    // internal read pointer has been moved to the tail of the UDP
+    // header.
+    ASSERT_EQ(MIN_IP_HEADER_LEN + UDP_HEADER_LEN, in_buf.getPosition());
+    EXPECT_EQ(0x01020304, in_buf.readUint32());
+}
+
+/// The purpose of this test is to verify that the ethernet
+/// header is correctly constructed from the destination and
+/// hardware addresses.
+TEST(ProtocolUtilTest, writeEthernetHeader) {
+    // Source HW address, 6 bytes.
+    const uint8_t src_hw_addr[6] = {
+        0x10, 0x11, 0x12, 0x13, 0x14, 0x15
+    };
+    // Destination HW address, 6 bytes.
+    const uint8_t dest_hw_addr[6] = {
+        0x20, 0x31, 0x42, 0x53, 0x64, 0x75
+    };
+
+    // Create output buffer.
+    OutputBuffer buf(1);
+    Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0));
+
+    HWAddrPtr local_hw_addr(new HWAddr(src_hw_addr, 6, 1));
+    ASSERT_NO_THROW(pkt->setLocalHWAddr(local_hw_addr));
+
+    // Set invalid length (7) of the hw address.
+    HWAddrPtr remote_hw_addr(new HWAddr(&std::vector<uint8_t>(1, 7)[0], 7, 1));
+    ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr));
+    // HW address is too long, so it should fail.
+    EXPECT_THROW(writeEthernetHeader(pkt, buf), BadValue);
+
+    // Finally, set a valid HW address.
+    remote_hw_addr.reset(new HWAddr(dest_hw_addr, 6, 1));
+    ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr));
+
+    // Construct the ethernet header using HW addresses stored
+    // in the pkt object.
+    writeEthernetHeader(pkt, buf);
+
+    // The resulting ethernet header consists of destination
+    // and src HW address (each 6 bytes long) and two bytes
+    // of encapsulated protocol type.
+    ASSERT_EQ(14, buf.getLength());
+
+    // Verify that first 6 bytes comprise valid destination
+    // HW address. Instead of using memory comparison functions
+    // we check bytes one-by-one. In case of mismatch we will
+    // get exact values that are mismatched. If memcmp was used
+    // the error message would not indicate the values of
+    // mismatched bytes.
+    for (int i = 0; i < 6; ++i) {
+        EXPECT_EQ(dest_hw_addr[i], buf[i]);
+    }
+    // Verify that following 6 bytes comprise the valid source
+    // HW address.
+    for (int i = 0; i < 6; ++i) {
+        EXPECT_EQ(src_hw_addr[i], buf[i + 6]);
+    }
+
+    // The last two bytes comprise the encapsulated protocol type.
+    // We expect IPv4 protocol type which is specified by 0x0800.
+    EXPECT_EQ(0x08, buf[12]);
+    EXPECT_EQ(0x0, buf[13]);
+}
+
+TEST(ProtocolUtilTest, writeIpUdpHeader) {
+    // Create DHCPv4 packet. Some values held by this object are
+    // used by the utility function under test to figure out the
+    // contents of the IP and UDP headers, e.g. source and
+    // destination IP address or port number.
+    Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
+    ASSERT_TRUE(pkt);
+
+    // Set local and remote address and port.
+    pkt->setLocalAddr(IOAddress("192.0.2.1"));
+    pkt->setRemoteAddr(IOAddress("192.0.2.111"));
+    pkt->setLocalPort(DHCP4_SERVER_PORT);
+    pkt->setRemotePort(DHCP4_CLIENT_PORT);
+
+    // Pack the contents of the packet.
+    ASSERT_NO_THROW(pkt->pack());
+
+    // Create output buffer. The headers will be written to it.
+    OutputBuffer buf(1);
+    // Write some dummy data to the buffer. We will check that the
+    // function under test appends to this data, not overrides it.
+    buf.writeUint16(0x0102);
+
+    // Write both IP and UDP headers.
+    writeIpUdpHeader(pkt, buf);
+
+    // The resulting size of the buffer must be 30. The 28 bytes are
+    // consumed by the IP and UDP headers. The other 2 bytes are dummy
+    // data at the beginning of the buffer.
+    ASSERT_EQ(30, buf.getLength());
+
+    // Make sure that the existing data in the buffer was not corrupted
+    // by the function under test.
+    EXPECT_EQ(0x01, buf[0]);
+    EXPECT_EQ(0x02, buf[1]);
+
+    // Copy the contents of the buffer to InputBuffer object. This object
+    // exposes convenient functions for reading.
+    InputBuffer in_buf(buf.getData(), buf.getLength());
+
+    // Check dummy data.
+    uint16_t dummy_data = in_buf.readUint16();
+    EXPECT_EQ(0x0102, dummy_data);
+
+    // The IP version and IHL are stored in the same octet (4 bits each).
+    uint8_t ver_len = in_buf.readUint8();
+    // The most significant bits define IP version.
+    uint8_t ip_ver = ver_len >> 4;
+    EXPECT_EQ(4, ip_ver);
+    // The least significant bits define header length (in 32-bits chunks).
+    uint8_t ip_len = ver_len & 0x0F;
+    EXPECT_EQ(5, ip_len);
+
+    // Get Differentiated Services Codepoint and Explicit Congestion
+    // Notification field.
+    uint8_t dscp_ecn = in_buf.readUint8();
+    EXPECT_EQ(IPTOS_LOWDELAY, dscp_ecn);
+
+    // Total length of IP packet. Includes UDP header and payload.
+    uint16_t total_len = in_buf.readUint16();
+    EXPECT_EQ(28 + pkt->getBuffer().getLength(), total_len);
+
+    // Identification field.
+    uint16_t ident = in_buf.readUint16();
+    EXPECT_EQ(0, ident);
+
+    // Fragmentation.
+    uint16_t fragment = in_buf.readUint16();
+    // Setting second most significant bit means no fragmentation.
+    EXPECT_EQ(0x4000, fragment);
+
+    // Get TTL
+    uint8_t ttl = in_buf.readUint8();
+    // Expect non-zero TTL.
+    EXPECT_GE(ttl, 1);
+
+    // Protocol type is UDP.
+    uint8_t proto = in_buf.readUint8();
+    EXPECT_EQ(IPPROTO_UDP, proto);
+
+    // Check that the checksum is correct. The reference checksum value
+    // has been calculated manually.
+    uint16_t ip_checksum = in_buf.readUint16();
+    EXPECT_EQ(0x755c, ip_checksum);
+
+    // Validate source address.
+    // Initializing it to IPv6 address guarantees that it is not initialized
+    // to the value that we expect to be read from a header since the value
+    // read from a header will be IPv4.
+    IOAddress src_addr("::1");
+    // Read src address as an array of bytes because it is easely convertible
+    // to IOAddress object.
+    uint8_t src_addr_data[4];
+    ASSERT_NO_THROW(
+        in_buf.readData(src_addr_data, 4);
+        src_addr = IOAddress::fromBytes(AF_INET, src_addr_data);
+    );
+    EXPECT_EQ(IOAddress("192.0.2.1").toText(), src_addr.toText());
+
+    // Validate destination address.
+    IOAddress dest_addr("::1");
+    uint8_t dest_addr_data[4];
+    ASSERT_NO_THROW(
+        in_buf.readData(dest_addr_data, 4);
+        dest_addr = IOAddress::fromBytes(AF_INET, dest_addr_data);
+    );
+    EXPECT_EQ(IOAddress("192.0.2.111").toText(), dest_addr.toText());
+
+    // UDP header starts here.
+
+    // Check source port.
+    uint16_t src_port = in_buf.readUint16();
+    EXPECT_EQ(pkt->getLocalPort(), src_port);
+
+    // Check destination port.
+    uint16_t dest_port = in_buf.readUint16();
+    EXPECT_EQ(pkt->getRemotePort(), dest_port);
+
+    // UDP header and data length.
+    uint16_t udp_len = in_buf.readUint16();
+    EXPECT_EQ(8 + pkt->getBuffer().getLength(), udp_len);
+
+    // Verify UDP checksum. The reference checksum has been calculated manually.
+    uint16_t udp_checksum = in_buf.readUint16();
+    EXPECT_EQ(0x8817, udp_checksum);
+}
+
+} // anonymous namespace