Browse Source

[3251] Implemented PktFilterInet6 class to handle IP/UDP sockets for DHCPv6

Marcin Siodelski 11 years ago
parent
commit
f2f292a65d

+ 2 - 1
src/lib/dhcp/Makefile.am

@@ -42,8 +42,9 @@ 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 pkt_filter.cc
-libb10_dhcp___la_SOURCES += pkt_filter6.h
+libb10_dhcp___la_SOURCES += pkt_filter6.h pkt_filter6.cc
 libb10_dhcp___la_SOURCES += pkt_filter_inet.cc pkt_filter_inet.h
+libb10_dhcp___la_SOURCES += pkt_filter_inet6.cc pkt_filter_inet6.h
 
 if OS_LINUX
 libb10_dhcp___la_SOURCES += pkt_filter_lpf.cc pkt_filter_lpf.h

+ 42 - 0
src/lib/dhcp/pkt_filter6.cc

@@ -0,0 +1,42 @@
+// 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 <dhcp/pkt_filter6.h>
+
+namespace isc {
+namespace dhcp {
+
+bool
+PktFilter6::joinMulticast(int sock, const std::string& ifname,
+                          const std::string & mcast) {
+
+    struct ipv6_mreq mreq;
+    // Convert the multicast address to a binary form.
+    if (inet_pton(AF_INET6, mcast.c_str(), &mreq.ipv6mr_multiaddr) <= 0) {
+        return (false);
+    }
+    // Set the interface being used.
+    mreq.ipv6mr_interface = if_nametoindex(ifname.c_str());
+    // Join the multicast group.
+    if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+                   &mreq, sizeof(mreq)) < 0) {
+        return (false);
+    }
+
+    return (true);
+}
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace

+ 22 - 4
src/lib/dhcp/pkt_filter6.h

@@ -15,6 +15,9 @@
 #ifndef PKT_FILTER6_H
 #define PKT_FILTER6_H
 
+#include <asiolink/io_address.h>
+#include <dhcp/pkt6.h>
+
 namespace isc {
 namespace dhcp {
 
@@ -86,12 +89,10 @@ public:
     /// This function receives a single DHCPv6 message through using a socket
     /// open on a specified interface.
     ///
-    /// @param iface interface
-    /// @param socket_info structure holding socket information
+    /// @param socket_info A structure holding socket information.
     ///
     /// @return A pointer to received message.
-    virtual Pkt6Ptr receive(const Iface& iface,
-                            const SocketInfo& socket_info) = 0;
+    virtual Pkt6Ptr receive(const SocketInfo& socket_info) = 0;
 
     /// @brief Send DHCPv6 message through a specified interface and socket.
     ///
@@ -107,6 +108,23 @@ public:
     virtual int send(const Iface& iface, uint16_t sockfd,
                      const Pkt6Ptr& pkt) = 0;
 
+protected:
+
+    /// @brief Joins IPv6 multicast group on a socket.
+    ///
+    /// Socket must be created and bound to an address. Note that this
+    /// address is different than the multicast address. For example DHCPv6
+    /// server should bind its socket to link-local address (fe80::1234...)
+    /// and later join ff02::1:2 multicast group.
+    ///
+    /// @param sock A socket descriptor (socket must be bound).
+    /// @param ifname An interface name (for link-scoped multicast groups).
+    /// @param mcast A multicast address to join (e.g. "ff02::1:2").
+    ///
+    /// @return true if multicast join was successful
+    static bool joinMulticast(int sock, const std::string& ifname,
+                              const std::string & mcast);
+
 };
 
 

+ 289 - 0
src/lib/dhcp/pkt_filter_inet6.cc

@@ -0,0 +1,289 @@
+// 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 <dhcp/iface_mgr.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/pkt_filter_inet6.h>
+#include <util/io/pktinfo_utilities.h>
+
+#include <netinet/in.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+PktFilterInet6::PktFilterInet6()
+: control_buf_len_(CMSG_SPACE(sizeof(struct in6_pktinfo))),
+    control_buf_(new char[control_buf_len_]) {
+}
+
+SocketInfo
+PktFilterInet6::openSocket(const Iface& iface,
+                           const isc::asiolink::IOAddress& addr,
+                           const uint16_t port) {
+    struct sockaddr_in6 addr6;
+    memset(&addr6, 0, sizeof(addr6));
+    addr6.sin6_family = AF_INET6;
+    addr6.sin6_port = htons(port);
+    if (addr.toText() != "::1") {
+        addr6.sin6_scope_id = if_nametoindex(iface.getName().c_str());
+    }
+
+    memcpy(&addr6.sin6_addr, &addr.toBytes()[0], sizeof(addr6.sin6_addr));
+#ifdef HAVE_SA_LEN
+    addr6.sin6_len = sizeof(addr6);
+#endif
+
+    // TODO: use sockcreator once it becomes available
+
+    // make a socket
+    int sock = socket(AF_INET6, SOCK_DGRAM, 0);
+    if (sock < 0) {
+        isc_throw(SocketConfigError, "Failed to create UDP6 socket.");
+    }
+
+    // Set the REUSEADDR option so that we don't fail to start if
+    // we're being restarted.
+    int flag = 1;
+    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+                   (char *)&flag, sizeof(flag)) < 0) {
+        close(sock);
+        isc_throw(SocketConfigError, "Can't set SO_REUSEADDR option on dhcpv6 socket.");
+    }
+
+    if (bind(sock, (struct sockaddr *)&addr6, sizeof(addr6)) < 0) {
+        close(sock);
+        isc_throw(SocketConfigError, "Failed to bind socket " << sock << " to " << addr.toText()
+                  << "/port=" << port);
+    }
+#ifdef IPV6_RECVPKTINFO
+    // RFC3542 - a new way
+    if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+                   &flag, sizeof(flag)) != 0) {
+        close(sock);
+        isc_throw(SocketConfigError, "setsockopt: IPV6_RECVPKTINFO failed.");
+    }
+#else
+    // RFC2292 - an old way
+    if (setsockopt(sock, IPPROTO_IPV6, IPV6_PKTINFO,
+                   &flag, sizeof(flag)) != 0) {
+        close(sock);
+        isc_throw(SocketConfigError, "setsockopt: IPV6_PKTINFO: failed.");
+    }
+#endif
+
+    // multicast stuff
+    if (addr.getAddress().to_v6().is_multicast()) {
+        // both mcast (ALL_DHCP_RELAY_AGENTS_AND_SERVERS and ALL_DHCP_SERVERS)
+        // are link and site-scoped, so there is no sense to join those groups
+        // with global addresses.
+
+        if (!joinMulticast(sock, iface.getName(),
+                           std::string(ALL_DHCP_RELAY_AGENTS_AND_SERVERS))) {
+            close(sock);
+            isc_throw(SocketConfigError, "Failed to join " << ALL_DHCP_RELAY_AGENTS_AND_SERVERS
+                      << " multicast group.");
+        }
+    }
+
+    return (SocketInfo(addr, port, sock));
+}
+
+Pkt6Ptr
+PktFilterInet6::receive(const SocketInfo& socket_info) {
+    // Now we have a socket, let's get some data from it!
+    uint8_t buf[IfaceMgr::RCVBUFSIZE];
+    memset(&control_buf_[0], 0, control_buf_len_);
+    struct sockaddr_in6 from;
+    memset(&from, 0, sizeof(from));
+
+    // Initialize our message header structure.
+    struct msghdr m;
+    memset(&m, 0, sizeof(m));
+
+    // Point so we can get the from address.
+    m.msg_name = &from;
+    m.msg_namelen = sizeof(from);
+
+    // Set the data buffer we're receiving. (Using this wacky
+    // "scatter-gather" stuff... but we that doesn't really make
+    // sense for us, so we use a single vector entry.)
+    struct iovec v;
+    memset(&v, 0, sizeof(v));
+    v.iov_base = static_cast<void*>(buf);
+    v.iov_len = IfaceMgr::RCVBUFSIZE;
+    m.msg_iov = &v;
+    m.msg_iovlen = 1;
+
+    // Getting the interface is a bit more involved.
+    //
+    // We set up some space for a "control message". We have
+    // previously asked the kernel to give us packet
+    // information (when we initialized the interface), so we
+    // should get the destination address from that.
+    m.msg_control = &control_buf_[0];
+    m.msg_controllen = control_buf_len_;
+
+    int result = recvmsg(socket_info.sockfd_, &m, 0);
+
+    struct in6_addr to_addr;
+    memset(&to_addr, 0, sizeof(to_addr));
+
+    int ifindex = -1;
+    if (result >= 0) {
+        struct in6_pktinfo* pktinfo = NULL;
+
+
+        // If we did read successfully, then we need to loop
+        // through the control messages we received and
+        // find the one with our destination address.
+        //
+        // We also keep a flag to see if we found it. If we
+        // didn't, then we consider this to be an error.
+        bool found_pktinfo = false;
+        struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m);
+        while (cmsg != NULL) {
+            if ((cmsg->cmsg_level == IPPROTO_IPV6) &&
+                (cmsg->cmsg_type == IPV6_PKTINFO)) {
+                pktinfo = util::io::internal::convertPktInfo6(CMSG_DATA(cmsg));
+                to_addr = pktinfo->ipi6_addr;
+                ifindex = pktinfo->ipi6_ifindex;
+                found_pktinfo = true;
+                break;
+            }
+            cmsg = CMSG_NXTHDR(&m, cmsg);
+        }
+        if (!found_pktinfo) {
+            isc_throw(SocketReadError, "unable to find pktinfo");
+        }
+    } else {
+        isc_throw(SocketReadError, "failed to receive data");
+    }
+
+    // Let's create a packet.
+    Pkt6Ptr pkt;
+    try {
+        pkt = Pkt6Ptr(new Pkt6(buf, result));
+    } catch (const std::exception& ex) {
+        isc_throw(SocketReadError, "failed to create new packet");
+    }
+
+    pkt->updateTimestamp();
+
+    pkt->setLocalAddr(IOAddress::fromBytes(AF_INET6,
+                      reinterpret_cast<const uint8_t*>(&to_addr)));
+    pkt->setRemoteAddr(IOAddress::fromBytes(AF_INET6,
+                       reinterpret_cast<const uint8_t*>(&from.sin6_addr)));
+    pkt->setRemotePort(ntohs(from.sin6_port));
+    pkt->setIndex(ifindex);
+
+    Iface* received = IfaceMgr::instance().getIface(pkt->getIndex());
+    if (received) {
+        pkt->setIface(received->getName());
+    } else {
+        isc_throw(SocketReadError, "received packet over unknown interface"
+                  << "(ifindex=" << pkt->getIndex() << ")");
+    }
+
+    return (pkt);
+
+}
+
+int
+PktFilterInet6::send(const Iface&, uint16_t sockfd, const Pkt6Ptr& pkt) {
+
+    memset(&control_buf_[0], 0, control_buf_len_);
+
+    // Set the target address we're sending to.
+    sockaddr_in6 to;
+    memset(&to, 0, sizeof(to));
+    to.sin6_family = AF_INET6;
+    to.sin6_port = htons(pkt->getRemotePort());
+    memcpy(&to.sin6_addr,
+           &pkt->getRemoteAddr().toBytes()[0],
+           16);
+    to.sin6_scope_id = pkt->getIndex();
+
+    // Initialize our message header structure.
+    struct msghdr m;
+    memset(&m, 0, sizeof(m));
+    m.msg_name = &to;
+    m.msg_namelen = sizeof(to);
+
+    // Set the data buffer we're sending. (Using this wacky
+    // "scatter-gather" stuff... we only have a single chunk
+    // of data to send, so we declare a single vector entry.)
+
+    // As v structure is a C-style is used for both sending and
+    // receiving data, it is shared between sending and receiving
+    // (sendmsg and recvmsg). It is also defined in system headers,
+    // so we have no control over its definition. To set iov_base
+    // (defined as void*) we must use const cast from void *.
+    // Otherwise C++ compiler would complain that we are trying
+    // to assign const void* to void*.
+    struct iovec v;
+    memset(&v, 0, sizeof(v));
+    v.iov_base = const_cast<void *>(pkt->getBuffer().getData());
+    v.iov_len = pkt->getBuffer().getLength();
+    m.msg_iov = &v;
+    m.msg_iovlen = 1;
+
+    // Setting the interface is a bit more involved.
+    //
+    // We have to create a "control message", and set that to
+    // define the IPv6 packet information. We could set the
+    // source address if we wanted, but we can safely let the
+    // kernel decide what that should be.
+    m.msg_control = &control_buf_[0];
+    m.msg_controllen = control_buf_len_;
+    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&m);
+
+    // FIXME: Code below assumes that cmsg is not NULL, but
+    // CMSG_FIRSTHDR() is coded to return NULL as a possibility.  The
+    // following assertion should never fail, but if it did and you came
+    // here, fix the code. :)
+    assert(cmsg != NULL);
+
+    cmsg->cmsg_level = IPPROTO_IPV6;
+    cmsg->cmsg_type = IPV6_PKTINFO;
+    cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+    struct in6_pktinfo *pktinfo =
+        util::io::internal::convertPktInfo6(CMSG_DATA(cmsg));
+    memset(pktinfo, 0, sizeof(struct in6_pktinfo));
+    pktinfo->ipi6_ifindex = pkt->getIndex();
+    // According to RFC3542, section 20.2, the msg_controllen field
+    // may be set using CMSG_SPACE (which includes padding) or
+    // using CMSG_LEN. Both forms appear to work fine on Linux, FreeBSD,
+    // NetBSD, but OpenBSD appears to have a bug, discussed here:
+    // http://www.archivum.info/mailing.openbsd.bugs/2009-02/00017/
+    // kernel-6080-msg_controllen-of-IPV6_PKTINFO.html
+    // which causes sendmsg to return EINVAL if the CMSG_LEN is
+    // used to set the msg_controllen value.
+    m.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
+
+    pkt->updateTimestamp();
+
+    int result = sendmsg(sockfd, &m, 0);
+    if  (result < 0) {
+        isc_throw(SocketWriteError, "pkt6 send failed: sendmsg() returned"
+                  " with an error: " << strerror(errno));
+    }
+
+    return (result);
+}
+
+
+}
+}

+ 91 - 0
src/lib/dhcp/pkt_filter_inet6.h

@@ -0,0 +1,91 @@
+// 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 PKT_FILTER_INET6_H
+#define PKT_FILTER_INET6_H
+
+#include <dhcp/pkt_filter6.h>
+#include <boost/scoped_array.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief A DHCPv6 packet handling class using datagram sockets.
+///
+/// This class opens a datagram IPv6/UDPv6 socket. It also implements functions
+/// to send and receive DHCPv6 messages through this socket. It is a default
+/// class to be used by @c IfaceMgr to access IPv6 sockets.
+class PktFilterInet6 : public PktFilter6 {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Initializes a control buffer used in the message transmission.
+    PktFilterInet6();
+
+    /// @brief Open a socket.
+    ///
+    /// This function open an IPv6 socket on an interface and binds it to a
+    /// specified UDP port and IP address.
+    ///
+    /// @param iface Interface descriptor.
+    /// @param addr Address on the interface to be used to send packets.
+    /// @param port Port number.
+    ///
+    /// @return A structure describing a primary and fallback socket.
+    /// @throw isc::dhcp::SocketConfigError if error occured when opening
+    /// or configuring a socket.
+    virtual SocketInfo openSocket(const Iface& iface,
+                                  const isc::asiolink::IOAddress& addr,
+                                  const uint16_t port);
+
+    /// @brief Receive DHCPv6 message on the interface.
+    ///
+    /// This function receives a single DHCPv6 message through using a socket
+    /// open on a specified interface.
+    ///
+    /// @param socket_info A structure holding socket information.
+    ///
+    /// @return A pointer to received message.
+    /// @throw isc::dhcp::SocketReadError if error occurred during packet
+    /// reception.
+    virtual Pkt6Ptr receive(const SocketInfo& socket_info);
+
+    /// @brief Send DHCPv6 message through a specified interface and socket.
+    ///
+    /// Thie function sends a DHCPv6 message through a specified interface and
+    /// socket. In general, there may be multiple sockets open on a single
+    /// interface as a single interface may have multiple IPv6 addresses.
+    ///
+    /// @param iface Interface to be used to send packet.
+    /// @param sockfd A socket descriptor
+    /// @param pkt A packet to be sent.
+    ///
+    /// @return A result of sending the message. It is 0 if successful.
+    /// @throw isc::dhcp::SocketWriteError if error occured when sending a
+    /// packet.
+    virtual int send(const Iface& iface, uint16_t sockfd,
+                     const Pkt6Ptr& pkt);
+
+private:
+    /// Length of the control_buf_ array.
+    size_t control_buf_len_;
+    /// Control buffer, used in transmission and reception.
+    boost::scoped_array<char> control_buf_;
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER_INET6_H

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

@@ -50,7 +50,9 @@ libdhcp___unittests_SOURCES += pkt4_unittest.cc
 libdhcp___unittests_SOURCES += pkt6_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_inet_unittest.cc
+libdhcp___unittests_SOURCES += pkt_filter_inet6_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_test_utils.h pkt_filter_test_utils.cc
+libdhcp___unittests_SOURCES += pkt_filter6_test_utils.h pkt_filter6_test_utils.cc
 
 if OS_LINUX
 libdhcp___unittests_SOURCES += pkt_filter_lpf_unittest.cc

+ 181 - 0
src/lib/dhcp/tests/pkt_filter6_test_utils.cc

@@ -0,0 +1,181 @@
+// 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/pkt6.h>
+#include <dhcp/tests/pkt_filter6_test_utils.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+PktFilter6Test::PktFilter6Test(const uint16_t port)
+    : port_(port),
+      sock_info_(isc::asiolink::IOAddress("::1"), port, -1, -1),
+      send_msg_sock_(-1) {
+    // Initialize ifname_ and ifindex_.
+    loInit();
+    // Initialize test_message_.
+    initTestMessage();
+}
+
+PktFilter6Test::~PktFilter6Test() {
+    // Cleanup after each test. This guarantees
+    // that the sockets do not hang after a test.
+    if (sock_info_.sockfd_ >= 0) {
+        close(sock_info_.sockfd_);
+    }
+    if (sock_info_.fallbackfd_ >=0) {
+        close(sock_info_.fallbackfd_);
+    }
+    if (send_msg_sock_ >= 0) {
+        close(send_msg_sock_);
+    }
+}
+
+void
+PktFilter6Test::initTestMessage() {
+    // Let's create a DHCPv6 message instance.
+    test_message_.reset(new Pkt6(DHCPV6_ADVERTISE, 123));
+
+    // Set required fields.
+    test_message_->setLocalAddr(IOAddress("::1"));
+    test_message_->setRemoteAddr(IOAddress("::1"));
+    test_message_->setRemotePort(port_);
+    test_message_->setLocalPort(port_ + 1);
+    test_message_->setIndex(ifindex_);
+    test_message_->setIface(ifname_);
+
+    try {
+        test_message_->pack();
+    } catch (const isc::Exception& ex) {
+        ADD_FAILURE() << "failed to create test message for PktFilter6Test";
+    }
+}
+
+void
+PktFilter6Test::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();
+
+    }
+}
+
+void
+PktFilter6Test::sendMessage() {
+    // DHCPv6 message will be sent over loopback interface.
+    Iface iface(ifname_, ifindex_);
+    IOAddress addr("::1");
+
+    // Initialize the source address and port.
+    struct sockaddr_in6 addr6;
+    memset(&addr6, 0, sizeof(addr6));
+    addr6.sin6_family = AF_INET6;
+    addr6.sin6_port = htons(port_);
+    memcpy(&addr6.sin6_addr, &addr.toBytes()[0], sizeof(addr6.sin6_addr));
+
+    // Open socket and bind to source address and port.
+    send_msg_sock_ = socket(AF_INET6, SOCK_DGRAM, 0);
+    ASSERT_GE(send_msg_sock_, 0);
+
+    ASSERT_GE(bind(send_msg_sock_, (struct sockaddr *)&addr6,
+                   sizeof(addr6)), 0);
+
+    // Set the destination address and port.
+    struct sockaddr_in6 dest_addr6;
+    memset(&dest_addr6, 0, sizeof(sockaddr_in6));
+    dest_addr6.sin6_family = AF_INET6;
+    dest_addr6.sin6_port = htons(port_ + 1);
+    memcpy(&dest_addr6.sin6_addr, &addr.toBytes()[0], 16);
+
+    // Initialize the message header structure, required by sendmsg.
+    struct msghdr m;
+    memset(&m, 0, sizeof(m));
+    m.msg_name = &dest_addr6;
+    m.msg_namelen = sizeof(dest_addr6);
+    // The iovec structure holds the packet data.
+    struct iovec v;
+    memset(&v, 0, sizeof(v));
+    v.iov_base = const_cast<void *>(test_message_->getBuffer().getData());
+    v.iov_len = test_message_->getBuffer().getLength();
+    // Assign the iovec to msghdr structure.
+    m.msg_iov = &v;
+    m.msg_iovlen = 1;
+    // We should be able to send the whole message. The sendmsg function should
+    // return the number of bytes sent, which is equal to the size of our
+    // message.
+    ASSERT_EQ(sendmsg(send_msg_sock_, &m, 0),
+              test_message_->getBuffer().getLength());
+    close(send_msg_sock_);
+    send_msg_sock_ = -1;
+
+}
+
+void
+PktFilter6Test::testDgramSocket(const int sock) const {
+    // Check that socket has been opened.
+    ASSERT_GE(sock, 0);
+
+    // Verify that the socket belongs to AF_INET family.
+    sockaddr_in6 sock_address;
+    socklen_t sock_address_len = sizeof(sock_address);
+    ASSERT_EQ(0, getsockname(sock,
+                             reinterpret_cast<sockaddr*>(&sock_address),
+                             &sock_address_len));
+    EXPECT_EQ(AF_INET6, sock_address.sin6_family);
+
+    // Verify that the socket is bound the appropriate address.
+    char straddr[INET6_ADDRSTRLEN];
+    inet_ntop(AF_INET6, &sock_address.sin6_addr, straddr, sizeof(straddr));
+    std::string bind_addr(straddr);
+    EXPECT_EQ("::1", bind_addr);
+
+    // Verify that the socket is bound to appropriate port.
+    EXPECT_EQ(port_, ntohs(sock_address.sin6_port));
+
+    // Verify that the socket has SOCK_DGRAM type.
+    int sock_type;
+    socklen_t sock_type_len = sizeof(sock_type);
+    ASSERT_EQ(0, getsockopt(sock, SOL_SOCKET, SO_TYPE,
+                            &sock_type, &sock_type_len));
+    EXPECT_EQ(SOCK_DGRAM, sock_type);
+}
+
+void
+PktFilter6Test::testRcvdMessage(const Pkt6Ptr& rcvd_msg) const {
+    // Currently, we don't send any payload in the message.
+    // Let's just check that the transaction id matches so as we
+    // are sure that we received the message that we expected.
+    EXPECT_EQ(test_message_->getTransid(), rcvd_msg->getTransid());
+}
+
+} // end of isc::dhcp::test namespace
+} // end of isc::dhcp namespace
+} // end of isc namespace

+ 98 - 0
src/lib/dhcp/tests/pkt_filter6_test_utils.h

@@ -0,0 +1,98 @@
+// 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 PKT_FILTER6_TEST_UTILS_H
+#define PKT_FILTER6_TEST_UTILS_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter.h>
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test fixture class for testing classes derived from PktFilter6 class.
+///
+/// This class implements a simple algorithm checking presence of the loopback
+/// interface and initializing its index. It assumes that the loopback interface
+/// name is one of 'lo' or 'lo0'. If none of those interfaces is found, the
+/// constructor will report a failure.
+///
+/// @todo The interface detection algorithm should be more generic. This will
+/// be possible once the cross-OS interface detection is implemented.
+class PktFilter6Test : public ::testing::Test {
+public:
+
+    /// @brief Constructor
+    ///
+    /// This constructor initializes sock_info_ structure to a default value.
+    /// The socket descriptors should be set to a negative value to indicate
+    /// that no socket has been opened. Specific tests will reinitialize this
+    /// structure with the values of the open sockets. For non-negative socket
+    /// descriptors, the class destructor will close associated sockets.
+    PktFilter6Test(const uint16_t port);
+
+    /// @brief Destructor
+    ///
+    /// Closes open sockets (if any).
+    virtual ~PktFilter6Test();
+
+    /// @brief Initializes DHCPv6 message used by tests.
+    void initTestMessage();
+
+    /// @brief Detect loopback interface.
+    ///
+    /// @todo this function will be removed once cross-OS interface
+    /// detection is implemented
+    void loInit();
+
+    /// @brief Sends a single DHCPv6 message to the loopback address.
+    ///
+    /// This function opens a datagram socket and binds it to the local loopback
+    /// address and client port. The client's port is assumed to be port_ + 1.
+    /// The send_msg_sock_ member holds the socket descriptor so as the socket
+    /// is closed automatically in the destructor. If the function succeeds to
+    /// send a DHCPv6 message, the socket is closed so as the function can be
+    /// called again within the same test.
+    void sendMessage();
+
+    /// @brief Test that the datagram socket is opened correctly.
+    ///
+    /// This function is used by multiple tests.
+    ///
+    /// @param sock A descriptor of the open socket.
+    void testDgramSocket(const int sock) const;
+
+    /// @brief Checks if the received message matches the test_message_.
+    ///
+    /// @param rcvd_msg An instance of the message to be tested.
+    void testRcvdMessage(const Pkt6Ptr& rcvd_msg) const;
+
+    std::string ifname_;   ///< Loopback interface name
+    uint16_t ifindex_;     ///< Loopback interface index.
+    uint16_t port_;        ///< A port number used for the test.
+    isc::dhcp::SocketInfo sock_info_; ///< A structure holding socket info.
+    int send_msg_sock_;    ///< Holds a descriptor of the socket used by
+                           ///< sendMessage function.
+    Pkt6Ptr test_message_; ///< A DHCPv6 message used by tests.
+
+};
+
+}; // end of isc::dhcp::test namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // PKT_FILTER6_TEST_UTILS_H

+ 140 - 0
src/lib/dhcp/tests/pkt_filter_inet6_unittest.cc

@@ -0,0 +1,140 @@
+// 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/pkt6.h>
+#include <dhcp/pkt_filter_inet6.h>
+#include <dhcp/tests/pkt_filter6_test_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace {
+
+/// Port number used by tests.
+const uint16_t PORT = 10546;
+/// Size of the buffer holding received packets.
+const size_t RECV_BUF_SIZE = 2048;
+
+// Test fixture class inherits from the class common for all packet
+// filter tests.
+class PktFilterInet6Test : public isc::dhcp::test::PktFilter6Test {
+public:
+    PktFilterInet6Test() : PktFilter6Test(PORT) {
+    }
+};
+
+// This test verifies that the INET6 datagram socket is correctly opened and
+// bound to the appropriate address and port.
+TEST_F(PktFilterInet6Test, openSocket) {
+    // Create object representing loopback interface.
+    Iface iface(ifname_, ifindex_);
+    // Set loopback address.
+    IOAddress addr("::1");
+
+    // Try to open socket.
+    PktFilterInet6 pkt_filter;
+    sock_info_ = pkt_filter.openSocket(iface, addr, PORT);
+    // For the packet filter in use, the fallback socket shouldn't be opened.
+    // Fallback is typically opened for raw IPv4 sockets.
+    EXPECT_LT(sock_info_.fallbackfd_, 0);
+
+    // Test the primary socket.
+    testDgramSocket(sock_info_.sockfd_);
+}
+
+// This test verifies that the packet is correctly sent over the INET6
+// datagram socket.
+TEST_F(PktFilterInet6Test, send) {
+    // Packet will be sent over loopback interface.
+    Iface iface(ifname_, ifindex_);
+    IOAddress addr("::1");
+
+    // Create an instance of the class which we are testing.
+    PktFilterInet6 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.
+    sock_info_ = pkt_filter.openSocket(iface, addr, PORT);
+    ASSERT_GE(sock_info_.sockfd_, 0);
+
+    // Send the packet over the socket.
+    ASSERT_NO_THROW(pkt_filter.send(iface, sock_info_.sockfd_, test_message_));
+
+    // Read the data from socket.
+    fd_set readfds;
+    FD_ZERO(&readfds);
+    FD_SET(sock_info_.sockfd_, &readfds);
+
+    struct timeval timeout;
+    timeout.tv_sec = 5;
+    timeout.tv_usec = 0;
+    int result = select(sock_info_.sockfd_ + 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(sock_info_.sockfd_, rcv_buf, RECV_BUF_SIZE, 0);
+    ASSERT_GT(result, 0);
+
+    // Create the DHCPv6 packet from the received data.
+    Pkt6Ptr rcvd_pkt(new Pkt6(rcv_buf, result));
+    ASSERT_TRUE(rcvd_pkt);
+
+    // Parse the packet.
+    ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+    // Check if the received message is correct.
+    testRcvdMessage(rcvd_pkt);
+
+}
+
+// This test verifies that the DHCPv6 packet is correctly received via
+// INET6 datagram socket and that it matches sent packet.
+TEST_F(PktFilterInet6Test, receive) {
+
+    // Packet will be received over loopback interface.
+    Iface iface(ifname_, ifindex_);
+    IOAddress addr("::1");
+
+    // Create an instance of the class which we are testing.
+    PktFilterInet6 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.
+    sock_info_ = pkt_filter.openSocket(iface, addr, PORT + 1);
+    ASSERT_GE(sock_info_.sockfd_, 0);
+
+    // Send a DHCPv6 message to the local loopback address and server's port.
+    //    ASSERT_NO_THROW(pkt_filter.send(iface, sock_info_.sockfd_, test_message_));
+    sendMessage();
+
+    // Receive the packet.
+    Pkt6Ptr rcvd_pkt = pkt_filter.receive(sock_info_);
+    // Check that the packet has been correctly received.
+    ASSERT_TRUE(rcvd_pkt);
+
+    // Parse the packet.
+    ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+    // Check if the received message is correct.
+    testRcvdMessage(rcvd_pkt);
+    }
+
+} // anonymous namespace