mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-31 05:55:28 +00:00
[master] Merge branch 'trac3251'
This commit is contained in:
@@ -73,6 +73,7 @@
|
||||
* - @subpage libdhcpRelay
|
||||
* - @subpage libdhcpIfaceMgr
|
||||
* - @subpage libdhcpPktFilter
|
||||
* - @subpage libdhcpPktFilter6
|
||||
* - @subpage libdhcpsrv
|
||||
* - @subpage leasemgr
|
||||
* - @subpage cfgmgr
|
||||
|
@@ -42,7 +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 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
|
||||
|
@@ -23,6 +23,7 @@
|
||||
#include <dhcp/dhcp6.h>
|
||||
#include <dhcp/iface_mgr.h>
|
||||
#include <dhcp/pkt_filter_inet.h>
|
||||
#include <dhcp/pkt_filter_inet6.h>
|
||||
#include <exceptions/exceptions.h>
|
||||
#include <util/io/pktinfo_utilities.h>
|
||||
|
||||
@@ -169,7 +170,8 @@ IfaceMgr::IfaceMgr()
|
||||
:control_buf_len_(CMSG_SPACE(sizeof(struct in6_pktinfo))),
|
||||
control_buf_(new char[control_buf_len_]),
|
||||
session_socket_(INVALID_SOCKET), session_callback_(NULL),
|
||||
packet_filter_(new PktFilterInet())
|
||||
packet_filter_(new PktFilterInet()),
|
||||
packet_filter6_(new PktFilterInet6())
|
||||
{
|
||||
|
||||
try {
|
||||
@@ -225,10 +227,11 @@ IfaceMgr::isDirectResponseSupported() const {
|
||||
}
|
||||
|
||||
void
|
||||
IfaceMgr::setPacketFilter(const boost::shared_ptr<PktFilter>& packet_filter) {
|
||||
IfaceMgr::setPacketFilter(const PktFilterPtr& packet_filter) {
|
||||
// Do not allow NULL pointer.
|
||||
if (!packet_filter) {
|
||||
isc_throw(InvalidPacketFilter, "NULL packet filter object specified");
|
||||
isc_throw(InvalidPacketFilter, "NULL packet filter object specified for"
|
||||
" DHCPv4");
|
||||
}
|
||||
// Different packet filters use different socket types. It does not make
|
||||
// sense to allow the change of packet filter when there are IPv4 sockets
|
||||
@@ -236,24 +239,53 @@ IfaceMgr::setPacketFilter(const boost::shared_ptr<PktFilter>& packet_filter) {
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
if (hasOpenSocket(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::setPacketFilter(const PktFilter6Ptr& packet_filter) {
|
||||
if (!packet_filter) {
|
||||
isc_throw(InvalidPacketFilter, "NULL packet filter object specified for"
|
||||
" DHCPv6");
|
||||
}
|
||||
|
||||
if (hasOpenSocket(AF_INET6)) {
|
||||
// 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 IPv6 sockets - need"
|
||||
<< " to close them first");
|
||||
}
|
||||
|
||||
packet_filter6_ = packet_filter;
|
||||
}
|
||||
|
||||
bool
|
||||
IfaceMgr::hasOpenSocket(const uint16_t family) const {
|
||||
// Iterate over all interfaces and search for open sockets.
|
||||
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) {
|
||||
// Check if the socket matches specified family.
|
||||
if (sock->family_ == family) {
|
||||
// There is at least one socket open, so return.
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// There are no open sockets found for the specified family.
|
||||
return (false);
|
||||
}
|
||||
|
||||
void IfaceMgr::stubDetectIfaces() {
|
||||
string ifaceName;
|
||||
@@ -445,41 +477,41 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sock = openSocket(iface->getName(), *addr, port);
|
||||
// Open socket and join multicast group only if the interface
|
||||
// is multicast-capable.
|
||||
// @todo The DHCPv6 requires multicast so we may want to think
|
||||
// whether we want to open the socket on a multicast-incapable
|
||||
// interface or not. For now, we prefer to be liberal and allow
|
||||
// it for some odd use cases which may utilize non-multicast
|
||||
// interfaces. Perhaps a warning should be emitted if the
|
||||
// interface is not a multicast one.
|
||||
sock = openSocket(iface->getName(), *addr, port,
|
||||
iface->flag_multicast_);
|
||||
if (sock < 0) {
|
||||
const char* errstr = strerror(errno);
|
||||
isc_throw(SocketConfigError, "failed to open link-local socket on "
|
||||
<< addr->toText() << " on interface "
|
||||
isc_throw(SocketConfigError, "failed to open link-local"
|
||||
" socket on " << addr->toText() << " on interface "
|
||||
<< iface->getName() << ", reason: " << errstr);
|
||||
}
|
||||
|
||||
// Binding socket to unicast address and then joining multicast group
|
||||
// works well on Mac OS (and possibly other BSDs), but does not work
|
||||
// on Linux.
|
||||
if ( !joinMulticast(sock, iface->getName(),
|
||||
string(ALL_DHCP_RELAY_AGENTS_AND_SERVERS))) {
|
||||
close(sock);
|
||||
isc_throw(SocketConfigError, "Failed to join "
|
||||
<< ALL_DHCP_RELAY_AGENTS_AND_SERVERS
|
||||
<< " multicast group.");
|
||||
}
|
||||
|
||||
count++;
|
||||
|
||||
/// @todo: Remove this ifdef once we start supporting BSD systems.
|
||||
#if defined(OS_LINUX)
|
||||
// To receive multicast traffic, Linux requires binding socket to
|
||||
// a multicast group. That in turn doesn't work on NetBSD.
|
||||
|
||||
int sock2 = openSocket(iface->getName(),
|
||||
IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
|
||||
port);
|
||||
if (sock2 < 0) {
|
||||
const char* errstr = strerror(errno);
|
||||
isc_throw(SocketConfigError, "Failed to open multicast socket on "
|
||||
<< " interface " << iface->getFullName() << ", reason:"
|
||||
<< errstr);
|
||||
iface->delSocket(sock); // delete previously opened socket
|
||||
if (iface->flag_multicast_) {
|
||||
int sock2 =
|
||||
openSocket(iface->getName(),
|
||||
IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
|
||||
port);
|
||||
if (sock2 < 0) {
|
||||
const char* errstr = strerror(errno);
|
||||
isc_throw(SocketConfigError, "Failed to open multicast"
|
||||
" socket on interface " << iface->getFullName()
|
||||
<< ", reason:" << errstr);
|
||||
iface->delSocket(sock); // delete previously opened socket
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -501,7 +533,6 @@ IfaceMgr::handleSocketConfigError(const std::string& errmsg,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
|
||||
for (IfaceCollection::const_iterator iface=ifaces_.begin();
|
||||
@@ -565,7 +596,7 @@ int IfaceMgr::openSocket(const std::string& ifname, const IOAddress& addr,
|
||||
return openSocket4(*iface, addr, port, receive_bcast, send_bcast);
|
||||
|
||||
} else if (addr.isV6()) {
|
||||
return openSocket6(*iface, addr, port);
|
||||
return openSocket6(*iface, addr, port, receive_bcast);
|
||||
|
||||
} else {
|
||||
isc_throw(BadValue, "Failed to detect family of address: "
|
||||
@@ -594,7 +625,7 @@ int IfaceMgr::openSocketFromIface(const std::string& ifname,
|
||||
if (addr_it->getFamily() == family) {
|
||||
// We have interface and address so let's open socket.
|
||||
// This may cause isc::Unexpected exception.
|
||||
return (openSocket(iface->getName(), *addr_it, port));
|
||||
return (openSocket(iface->getName(), *addr_it, port, false));
|
||||
}
|
||||
++addr_it;
|
||||
}
|
||||
@@ -638,7 +669,7 @@ int IfaceMgr::openSocketFromAddress(const IOAddress& addr,
|
||||
if (*addr_it == addr) {
|
||||
// Open socket using local interface, address and port.
|
||||
// This may cause isc::Unexpected exception.
|
||||
return (openSocket(iface->getName(), *addr_it, port));
|
||||
return (openSocket(iface->getName(), *addr_it, port, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -717,86 +748,23 @@ IfaceMgr::getLocalAddress(const IOAddress& remote_addr, const uint16_t port) {
|
||||
}
|
||||
|
||||
|
||||
int IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, 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(),
|
||||
string(ALL_DHCP_RELAY_AGENTS_AND_SERVERS) ) ) {
|
||||
close(sock);
|
||||
isc_throw(SocketConfigError, "Failed to join " << ALL_DHCP_RELAY_AGENTS_AND_SERVERS
|
||||
<< " multicast group.");
|
||||
}
|
||||
}
|
||||
|
||||
SocketInfo info(addr, port, sock);
|
||||
int
|
||||
IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port,
|
||||
const bool join_multicast) {
|
||||
// Assuming that packet filter is not NULL, because its modifier checks it.
|
||||
SocketInfo info = packet_filter6_->openSocket(iface, addr, port,
|
||||
join_multicast);
|
||||
iface.addSocket(info);
|
||||
|
||||
return (sock);
|
||||
return (info.sockfd_);
|
||||
}
|
||||
|
||||
int IfaceMgr::openSocket4(Iface& iface, const IOAddress& addr,
|
||||
int
|
||||
IfaceMgr::openSocket4(Iface& iface, const IOAddress& addr,
|
||||
const uint16_t port, const bool receive_bcast,
|
||||
const bool send_bcast) {
|
||||
|
||||
// Skip checking if the packet_filter_ is non-NULL because this check
|
||||
// has been already done when packet filter object was set.
|
||||
|
||||
// Assuming that packet filter is not NULL, because its modifier checks it.
|
||||
SocketInfo info = packet_filter_->openSocket(iface, addr, port,
|
||||
receive_bcast, send_bcast);
|
||||
iface.addSocket(info);
|
||||
@@ -804,114 +772,16 @@ int IfaceMgr::openSocket4(Iface& iface, const IOAddress& addr,
|
||||
return (info.sockfd_);
|
||||
}
|
||||
|
||||
bool
|
||||
IfaceMgr::joinMulticast(int sock, const std::string& ifname,
|
||||
const std::string & mcast) {
|
||||
|
||||
struct ipv6_mreq mreq;
|
||||
|
||||
if (inet_pton(AF_INET6, mcast.c_str(),
|
||||
&mreq.ipv6mr_multiaddr) <= 0) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
mreq.ipv6mr_interface = if_nametoindex(ifname.c_str());
|
||||
if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP,
|
||||
&mreq, sizeof(mreq)) < 0) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (true);
|
||||
}
|
||||
|
||||
bool
|
||||
IfaceMgr::send(const Pkt6Ptr& pkt) {
|
||||
int result;
|
||||
|
||||
Iface* iface = getIface(pkt->getIface());
|
||||
if (!iface) {
|
||||
isc_throw(BadValue, "Unable to send Pkt6. Invalid interface ("
|
||||
isc_throw(BadValue, "Unable to send DHCPv6 message. Invalid interface ("
|
||||
<< pkt->getIface() << ") specified.");
|
||||
}
|
||||
|
||||
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 = 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();
|
||||
|
||||
result = sendmsg(getSocket(*pkt), &m, 0);
|
||||
if (result < 0) {
|
||||
isc_throw(SocketWriteError, "pkt6 send failed: sendmsg() returned"
|
||||
" with an error: " << strerror(errno));
|
||||
}
|
||||
|
||||
return (result);
|
||||
// Assuming that packet filter is not NULL, because its modifier checks it.
|
||||
return (packet_filter6_->send(*iface, getSocket(*pkt), pkt));
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -919,12 +789,11 @@ IfaceMgr::send(const Pkt4Ptr& pkt) {
|
||||
|
||||
Iface* iface = getIface(pkt->getIface());
|
||||
if (!iface) {
|
||||
isc_throw(BadValue, "Unable to send Pkt4. Invalid interface ("
|
||||
isc_throw(BadValue, "Unable to send DHCPv4 message. Invalid interface ("
|
||||
<< pkt->getIface() << ") specified.");
|
||||
}
|
||||
|
||||
// Skip checking if packet filter is non-NULL because it has been
|
||||
// already checked when packet filter was set.
|
||||
// Assuming that packet filter is not NULL, because its modifier checks it.
|
||||
return (packet_filter_->send(*iface, getSocket(*pkt), pkt));
|
||||
}
|
||||
|
||||
@@ -1023,8 +892,7 @@ IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
|
||||
}
|
||||
|
||||
// Now we have a socket, let's get some data from it!
|
||||
// Skip checking if packet filter is non-NULL because it has been
|
||||
// already checked when packet filter was set.
|
||||
// Assuming that packet filter is not NULL, because its modifier checks it.
|
||||
return (packet_filter_->receive(*iface, *candidate));
|
||||
}
|
||||
|
||||
@@ -1119,102 +987,8 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
|
||||
if (!candidate) {
|
||||
isc_throw(SocketReadError, "received data over unknown socket");
|
||||
}
|
||||
|
||||
// Now we have a socket, let's get some data from it!
|
||||
uint8_t buf[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 = 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_;
|
||||
|
||||
result = recvmsg(candidate->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 = 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 = getIface(pkt->getIndex());
|
||||
if (received) {
|
||||
pkt->setIface(received->getName());
|
||||
} else {
|
||||
isc_throw(SocketReadError, "received packet over unknown interface"
|
||||
<< "(ifindex=" << pkt->getIndex() << ")");
|
||||
}
|
||||
|
||||
return (pkt);
|
||||
// Assuming that packet filter is not NULL, because its modifier checks it.
|
||||
return (packet_filter6_->receive(*candidate));
|
||||
}
|
||||
|
||||
uint16_t IfaceMgr::getSocket(const isc::dhcp::Pkt6& pkt) {
|
||||
|
@@ -21,6 +21,7 @@
|
||||
#include <dhcp/pkt4.h>
|
||||
#include <dhcp/pkt6.h>
|
||||
#include <dhcp/pkt_filter.h>
|
||||
#include <dhcp/pkt_filter6.h>
|
||||
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
@@ -554,8 +555,8 @@ public:
|
||||
/// @param ifname name of the interface
|
||||
/// @param addr address to be bound.
|
||||
/// @param port UDP port.
|
||||
/// @param receive_bcast configure IPv4 socket to receive broadcast messages.
|
||||
/// This parameter is ignored for IPv6 sockets.
|
||||
/// @param receive_bcast configure IPv4 socket to receive broadcast
|
||||
/// messages or IPv6 socket to join multicast group.
|
||||
/// @param send_bcast configure IPv4 socket to send broadcast messages.
|
||||
/// This parameter is ignored for IPv6 sockets.
|
||||
///
|
||||
@@ -577,11 +578,13 @@ public:
|
||||
/// Instead, the method searches through the addresses on the specified
|
||||
/// interface and selects one that matches the address family.
|
||||
///
|
||||
/// @note This method does not join the socket to the multicast group.
|
||||
///
|
||||
/// @param ifname name of the interface
|
||||
/// @param port UDP port
|
||||
/// @param family address family (AF_INET or AF_INET6)
|
||||
/// @return socket descriptor, if socket creation, binding and multicast
|
||||
/// group join were all successful.
|
||||
/// @return socket descriptor, if socket creation and binding was
|
||||
/// successful.
|
||||
/// @throw isc::Unexpected if failed to create and bind socket.
|
||||
/// @throw isc::BadValue if there is no address on specified interface
|
||||
/// that belongs to given family.
|
||||
@@ -594,10 +597,12 @@ public:
|
||||
/// This methods differs from \ref openSocket in that it does not require
|
||||
/// the specification of the interface to which the socket will be bound.
|
||||
///
|
||||
/// @note This method does not join the socket to the multicast group.
|
||||
///
|
||||
/// @param addr address to be bound
|
||||
/// @param port UDP port
|
||||
/// @return socket descriptor, if socket creation, binding and multicast
|
||||
/// group join were all successful.
|
||||
/// @return socket descriptor, if socket creation and binding was
|
||||
/// successful.
|
||||
/// @throw isc::Unexpected if failed to create and bind socket
|
||||
/// @throw isc::BadValue if specified address is not available on
|
||||
/// any interface
|
||||
@@ -611,10 +616,12 @@ public:
|
||||
/// identified, \ref openSocket is called to open a socket and bind it to
|
||||
/// the interface, address and port.
|
||||
///
|
||||
/// @note This method does not join the socket to a multicast group.
|
||||
///
|
||||
/// @param remote_addr remote address to connect to
|
||||
/// @param port UDP port
|
||||
/// @return socket descriptor, if socket creation, binding and multicast
|
||||
/// group join were all successful.
|
||||
/// @return socket descriptor, if socket creation and binding was
|
||||
/// successful.
|
||||
/// @throw isc::Unexpected if failed to create and bind socket
|
||||
int openSocketFromRemoteAddress(const isc::asiolink::IOAddress& remote_addr,
|
||||
const uint16_t port);
|
||||
@@ -749,22 +756,45 @@ public:
|
||||
session_callback_ = callback;
|
||||
}
|
||||
|
||||
/// @brief Set Packet Filter object to handle send/receive packets.
|
||||
/// @brief Set packet filter object to handle sending and receiving DHCPv4
|
||||
/// messages.
|
||||
///
|
||||
/// 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. 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.
|
||||
/// Packet filter objects provide means for the @c IfaceMgr to open sockets
|
||||
/// for IPv4 packets reception and sending. This function sets custom packet
|
||||
/// filter (represented by a class derived from PktFilter) to be used by
|
||||
/// @c 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.
|
||||
/// @param packet_filter A pointer to the new packet filter object to be
|
||||
/// used by @c IfaceMgr.
|
||||
///
|
||||
/// @throw InvalidPacketFilter if provided packet filter object is NULL.
|
||||
/// @throw PacketFilterChangeDenied if there are open IPv4 sockets
|
||||
/// @throw PacketFilterChangeDenied if there are open IPv4 sockets.
|
||||
void setPacketFilter(const PktFilterPtr& packet_filter);
|
||||
|
||||
/// @brief Set packet filter object to handle sending and receving DHCPv6
|
||||
/// messages.
|
||||
///
|
||||
/// Packet filter objects provide means for the @c IfaceMgr to open sockets
|
||||
/// for IPv6 packets reception and sending. This function sets the new
|
||||
/// instance of the packet filter which will be used by @c IfaceMgr to send
|
||||
/// and receive DHCPv6 messages, until replaced by another packet filter.
|
||||
///
|
||||
/// It is required that DHCPv6 messages are send and received using methods
|
||||
/// of the same object that was used to open socket. Therefore, it is
|
||||
/// required that all IPv6 sockets are closed prior to calling this
|
||||
/// function. Call closeSockets(AF_INET6) to close all hanging IPv6 sockets
|
||||
/// opened by the current packet filter object.
|
||||
///
|
||||
/// @param packet_filter A pointer to the new packet filter object to be
|
||||
/// used by @c IfaceMgr.
|
||||
///
|
||||
/// @throw isc::dhcp::InvalidPacketFilter if specified object is NULL.
|
||||
/// @throw isc::dhcp::PacketFilterChangeDenied if there are open IPv6
|
||||
/// sockets.
|
||||
void setPacketFilter(const PktFilter6Ptr& packet_filter);
|
||||
|
||||
/// @brief Set Packet Filter object to handle send/receive packets.
|
||||
///
|
||||
/// This function sets Packet Filter object to be used by IfaceMgr,
|
||||
@@ -832,9 +862,13 @@ protected:
|
||||
/// @param iface reference to interface structure.
|
||||
/// @param addr an address the created socket should be bound to
|
||||
/// @param port a port that created socket should be bound to
|
||||
/// @param join_multicast A boolean parameter which indicates whether
|
||||
/// socket should join All_DHCP_Relay_Agents_and_servers multicast
|
||||
/// group.
|
||||
///
|
||||
/// @return socket descriptor
|
||||
int openSocket6(Iface& iface, const isc::asiolink::IOAddress& addr, uint16_t port);
|
||||
int openSocket6(Iface& iface, const isc::asiolink::IOAddress& addr,
|
||||
uint16_t port, const bool join_multicast);
|
||||
|
||||
/// @brief Detects network interfaces.
|
||||
///
|
||||
@@ -899,23 +933,6 @@ protected:
|
||||
SessionCallback session_callback_;
|
||||
private:
|
||||
|
||||
/// @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 socket fd (socket must be bound)
|
||||
/// @param ifname interface name (for link-scoped multicast groups)
|
||||
/// @param mcast multicast address to join (e.g. "ff02::1:2")
|
||||
///
|
||||
/// @return true if multicast join was successful
|
||||
///
|
||||
bool
|
||||
joinMulticast(int sock, const std::string& ifname,
|
||||
const std::string& mcast);
|
||||
|
||||
/// @brief Identifies local network address to be used to
|
||||
/// connect to remote address.
|
||||
///
|
||||
@@ -951,15 +968,29 @@ private:
|
||||
void handleSocketConfigError(const std::string& errmsg,
|
||||
IfaceMgrErrorMsgCallback handler);
|
||||
|
||||
/// @brief Checks if there is at least one socket of the specified family
|
||||
/// open.
|
||||
///
|
||||
/// @param family A socket family.
|
||||
///
|
||||
/// @return true if there is at least one socket open, false otherwise.
|
||||
bool hasOpenSocket(const uint16_t family) const;
|
||||
|
||||
/// Holds instance of a class derived from PktFilter, used by the
|
||||
/// IfaceMgr to open sockets and send/receive packets through these
|
||||
/// sockets. It is possible to supply custom object using
|
||||
/// setPacketFilter class. Various Packet Filters differ mainly by using
|
||||
/// setPacketFilter method. Various Packet Filters differ mainly by using
|
||||
/// different types of sockets, e.g. SOCK_DGRAM, SOCK_RAW and different
|
||||
/// 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).
|
||||
PktFilterPtr packet_filter_;
|
||||
|
||||
/// Holds instance of a class derived from PktFilter6, used by the
|
||||
/// IfaceMgr to manage sockets used to send and receive DHCPv6
|
||||
/// messages. It is possible to supply a custom object using
|
||||
/// setPacketFilter method.
|
||||
PktFilter6Ptr packet_filter6_;
|
||||
};
|
||||
|
||||
}; // namespace isc::dhcp
|
||||
|
@@ -185,5 +185,50 @@ the regular IP/UDP socket. The isc::dhcp::PktFilterInet should be used in all
|
||||
cases when an application using the libdhcp++ doesn't require sending
|
||||
DHCP messages to a device which doesn't have an address yet.
|
||||
|
||||
@section libdhcpPktFilter6 Switchable Packet Filters for DHCPv6
|
||||
|
||||
The DHCPv6 implementation doesn't suffer from the problems described in \ref
|
||||
libdhcpPktFilter. Therefore, the socket creation and methods used to send
|
||||
and receive DHCPv6 messages are common for all OSes. However, there is
|
||||
still a need to customize the operations on the sockets to reliably unit test
|
||||
the \ref isc::dhcp::IfaceMgr logic.
|
||||
|
||||
The \ref isc::dhcp::IfaceMgr::openSockets6 function examines configuration
|
||||
of detected interfaces for their availability to listen DHCPv6 traffic. For
|
||||
all running interfaces (except local loopback) it will try to open a socket
|
||||
and bind it to the link local or global unicast address. The socket will
|
||||
not be opened on the interface which is down or for which it was explicitly
|
||||
specified that it should not be used to listen to DHCPv6 messages. There is
|
||||
a substantial amount of logic in this function that has to be unit tested for
|
||||
various interface configurations, e.g.:
|
||||
- multiple interfaces with link-local addresses only
|
||||
- multiple interfaces, some of them having global unicast addresses,
|
||||
- multiple interfaces, some of them disabled
|
||||
- no interfaces
|
||||
|
||||
The \ref isc::dhcp::IfaceMgr::openSockets6 function attempts to open
|
||||
sockets on detected interfaces. At the same time, the number of interfaces,
|
||||
and their configuration is specific to OS where the tests are being run.
|
||||
So the test doesn't have any means to configure interfaces for the test case
|
||||
being run. Moreover, a unit test should not change the configuration of the
|
||||
system. For example, a change to the configuration of the interface which
|
||||
is used to access the machine running a test, may effectively break the
|
||||
access to this machine.
|
||||
|
||||
In order to overcome the problem described above, the unit tests use
|
||||
fake interfaces which can be freely added, configured and removed from the
|
||||
\ref isc::dhcp::IfaceMgr. Obviously, it is not possible to open a socket
|
||||
on a fake interface, nor use it to send or receive IP packets. To mimic
|
||||
socket operations on fake interfaces it is required that the functions
|
||||
which open sockets, send messages and receive messages have to be
|
||||
customizable. This is achieved by implementation of replaceable packet
|
||||
filter objects which derive from the \ref isc::dhcp::PktFilter6 class.
|
||||
The default implementation of this class is \ref isc::dhcp::PktFilterInet6
|
||||
which creates a regular datagram IPv6/UDPv6 socket. The unit tests use a
|
||||
stub implementation isc::dhcp::test::PktFilter6Stub which contains no-op
|
||||
functions.
|
||||
|
||||
Use \ref isc::dhcp::IfaceMgr::setPacketFilter function to set the custom packet
|
||||
filter object to be used by Interface Manager.
|
||||
|
||||
*/
|
||||
|
44
src/lib/dhcp/pkt_filter6.cc
Normal file
44
src/lib/dhcp/pkt_filter6.cc
Normal file
@@ -0,0 +1,44 @@
|
||||
// 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;
|
||||
memset(&mreq, 0, sizeof(ipv6_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
|
150
src/lib/dhcp/pkt_filter6.h
Normal file
150
src/lib/dhcp/pkt_filter6.h
Normal file
@@ -0,0 +1,150 @@
|
||||
// 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_H
|
||||
#define PKT_FILTER6_H
|
||||
|
||||
#include <asiolink/io_address.h>
|
||||
#include <dhcp/pkt6.h>
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
/// Forward declaration to the structure describing a socket.
|
||||
struct SocketInfo;
|
||||
|
||||
/// Forward declaration to the class representing interface
|
||||
class Iface;
|
||||
|
||||
/// @brief Abstract packet handling class for DHCPv6.
|
||||
///
|
||||
/// This class defines methods for performing low level operations on IPv6
|
||||
/// socket:
|
||||
/// - open socket,
|
||||
/// - send DHCPv6 message through the socket,
|
||||
/// - receive DHCPv6 through the socket.
|
||||
///
|
||||
/// Methods exposed by this class are called through the @c IfaceMgr only. They
|
||||
/// are not meant to be called directly, except unit testing.
|
||||
///
|
||||
/// The @c IfaceMgr is responsible for managing the pool of sockets. In
|
||||
/// particular, @c IfaceMgr detects interfaces suitable to send/receive DHCPv6
|
||||
/// messages. When it intends to open a socket on a particular interface, it
|
||||
/// will call the PktFilter6::openSocket. If this call is successful, the
|
||||
/// structure describing a new socket is returned.
|
||||
///
|
||||
/// In order to send or receive a DHCPv6 message through this socket,
|
||||
/// the @c IfaceMgr must use PktFilter6::send or PktFilter6::receive
|
||||
/// functions of the same class that has been used to open a socket,
|
||||
/// i.e. all send/receive operations should be performed using this
|
||||
/// particular class.
|
||||
///
|
||||
/// The major motivation behind creating a separate class, to manage low level
|
||||
/// operations using sockets, is to make @c IfaceMgr unit testable. By providing
|
||||
/// a stub implementation of this class which mimics the behavior of the real
|
||||
/// socket handling class, it is possible to simulate and test various
|
||||
/// conditions. For example, the @c IfaceMgr::openSockets function will try to
|
||||
/// open sockets on all available interfaces. The test doesn't have any means
|
||||
/// to know which interfaces are present. In addition, even if the network
|
||||
/// interface detection was implemented on the test side, there is no guarantee
|
||||
/// that the particular system has sufficient number of suitable IPv6-enabled
|
||||
/// interfaces available for a particular test. Moreover, the test may need
|
||||
/// to tweak some of the interface configuration to cover certain test
|
||||
/// scenarios. The proposed solution is to not use the actual interfaces
|
||||
/// but simply create a pool of fake interfaces which configuration
|
||||
/// can be freely modified by a test. This however implies that operations
|
||||
/// on sockets must be simulated.
|
||||
///
|
||||
/// @note This class is named after @c PktFilter abstract class which exposes
|
||||
/// similar interface for DHVPv4. However, the PktFilter class is devoted to
|
||||
/// solve the problem of sending DHCPv4 messages to the hosts which don't have
|
||||
/// an IP address yet (a.k.a. direct DHCPv4 traffic). Where required, the
|
||||
/// custom implementations of @c PktFilter are provided to send and receive
|
||||
/// messages through raw sockets. In order to filter out the desired traffic
|
||||
/// Linux Packet Filtering or Berkeley Packet Filtering is used, hence the
|
||||
/// name of the class. In case of DHCPv6 regular IPv6/UDPv6 sockets are used
|
||||
/// and derived classes do not use Linux or Berkeley Packet Filtering.
|
||||
class PktFilter6 {
|
||||
public:
|
||||
|
||||
/// @brief Virtual Destructor.
|
||||
virtual ~PktFilter6() { }
|
||||
|
||||
/// @brief Opens a socket.
|
||||
///
|
||||
/// This function open an IPv6 socket on an interface and binds it to a
|
||||
/// specified UDP port and IPv6 address.
|
||||
///
|
||||
/// @param iface Interface descriptor.
|
||||
/// @param addr Address on the interface to be used to send packets.
|
||||
/// @param port Port number.
|
||||
/// @param join_multicast A boolean parameter which indicates whether
|
||||
/// socket should join All_DHCP_Relay_Agents_and_servers multicast
|
||||
/// group.
|
||||
///
|
||||
/// @return A structure describing a primary and fallback socket.
|
||||
virtual SocketInfo openSocket(const Iface& iface,
|
||||
const isc::asiolink::IOAddress& addr,
|
||||
const uint16_t port,
|
||||
const bool join_multicast) = 0;
|
||||
|
||||
/// @brief Receives 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.
|
||||
virtual Pkt6Ptr receive(const SocketInfo& socket_info) = 0;
|
||||
|
||||
/// @brief Sends DHCPv6 message through a specified interface and socket.
|
||||
///
|
||||
/// This 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.
|
||||
virtual int send(const Iface& iface, uint16_t sockfd,
|
||||
const Pkt6Ptr& pkt) = 0;
|
||||
|
||||
/// @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);
|
||||
|
||||
};
|
||||
|
||||
|
||||
/// Pointer to a PktFilter object.
|
||||
typedef boost::shared_ptr<PktFilter6> PktFilter6Ptr;
|
||||
|
||||
} // namespace isc::dhcp
|
||||
} // namespace isc
|
||||
|
||||
#endif // PKT_FILTER6_H
|
286
src/lib/dhcp/pkt_filter_inet6.cc
Normal file
286
src/lib/dhcp/pkt_filter_inet6.cc
Normal file
@@ -0,0 +1,286 @@
|
||||
// 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,
|
||||
const bool join_multicast) {
|
||||
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 SO_REUSEADDR option.
|
||||
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
|
||||
|
||||
// Join All_DHCP_Relay_Agents_and_Servers multicast group if
|
||||
// requested.
|
||||
if (join_multicast &&
|
||||
!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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
98
src/lib/dhcp/pkt_filter_inet6.h
Normal file
98
src/lib/dhcp/pkt_filter_inet6.h
Normal file
@@ -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_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 Opens 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.
|
||||
/// @param join_multicast A boolean parameter which indicates whether
|
||||
/// socket should join All_DHCP_Relay_Agents_and_servers multicast
|
||||
/// group.
|
||||
///
|
||||
/// @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,
|
||||
const bool join_multicast);
|
||||
|
||||
/// @brief Receives DHCPv6 message on the interface.
|
||||
///
|
||||
/// This function receives a single DHCPv6 message through a socket
|
||||
/// open on a specified interface. This function will block if there is
|
||||
/// no message waiting on the specified socket. Therefore the @c IfaceMgr
|
||||
/// must first check that there is any message on the socket (using
|
||||
/// select function) prior to calling this function.
|
||||
///
|
||||
/// @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 Sends 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
|
@@ -24,11 +24,10 @@ namespace dhcp {
|
||||
|
||||
/// @brief Packet handling class using Linux Packet Filtering
|
||||
///
|
||||
/// This class provides methods to send and recive packet using raw sockets
|
||||
/// and Linux Packet Filtering.
|
||||
///
|
||||
/// @warning This class is not implemented yet. Therefore all functions
|
||||
/// currently throw isc::NotImplemented exception.
|
||||
/// This class provides methods to send and recive DHCPv4 messages using raw
|
||||
/// sockets and Linux Packet Filtering. It is used by @c isc::dhcp::IfaceMgr
|
||||
/// to send DHCPv4 messages to the hosts which don't have an IPv4 address
|
||||
/// assigned yet.
|
||||
class PktFilterLPF : public PktFilter {
|
||||
public:
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -19,6 +19,7 @@
|
||||
#include <dhcp/iface_mgr.h>
|
||||
#include <dhcp/pkt6.h>
|
||||
#include <dhcp/pkt_filter.h>
|
||||
#include <dhcp/tests/pkt_filter6_test_utils.h>
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
@@ -35,6 +36,7 @@ using namespace std;
|
||||
using namespace isc;
|
||||
using namespace isc::asiolink;
|
||||
using namespace isc::dhcp;
|
||||
using namespace isc::dhcp::test;
|
||||
using boost::scoped_ptr;
|
||||
|
||||
namespace {
|
||||
@@ -156,7 +158,7 @@ public:
|
||||
/// @brief Returns the collection of existing interfaces.
|
||||
IfaceCollection& getIfacesLst() { return (ifaces_); }
|
||||
|
||||
/// @brief This function creates fictious interfaces with fictious
|
||||
/// @brief This function creates fictitious interfaces with fictious
|
||||
/// addresses.
|
||||
///
|
||||
/// These interfaces can be used in tests that don't actually try
|
||||
@@ -168,11 +170,21 @@ public:
|
||||
ifaces_.clear();
|
||||
|
||||
// local loopback
|
||||
ifaces_.push_back(createIface("lo", 0, "127.0.0.1"));
|
||||
Iface lo = createIface("lo", 0);
|
||||
lo.addAddress(IOAddress("127.0.0.1"));
|
||||
lo.addAddress(IOAddress("::1"));
|
||||
ifaces_.push_back(lo);
|
||||
// eth0
|
||||
ifaces_.push_back(createIface("eth0", 1, "10.0.0.1"));
|
||||
Iface eth0 = createIface("eth0", 1);
|
||||
eth0.addAddress(IOAddress("10.0.0.1"));
|
||||
eth0.addAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"));
|
||||
eth0.addAddress(IOAddress("2001:db8:1::1"));
|
||||
ifaces_.push_back(eth0);
|
||||
// eth1
|
||||
ifaces_.push_back(createIface("eth1", 2, "192.0.2.3"));
|
||||
Iface eth1 = createIface("eth1", 2);
|
||||
eth1.addAddress(IOAddress("192.0.2.3"));
|
||||
eth1.addAddress(IOAddress("fe80::3a60:77ff:fed5:abcd"));
|
||||
ifaces_.push_back(eth1);
|
||||
}
|
||||
|
||||
/// @brief Create an object representing interface.
|
||||
@@ -183,29 +195,59 @@ public:
|
||||
/// - up always true
|
||||
/// - running always true
|
||||
/// - inactive always to false
|
||||
/// - multicast always to true
|
||||
/// - broadcast always to false
|
||||
///
|
||||
/// If one needs to modify the default flag settings, the setIfaceFlags
|
||||
/// function should be used.
|
||||
///
|
||||
/// @param name A name of the interface to be created.
|
||||
/// @param ifindex An index of the interface to be created.
|
||||
/// @param addr An IP address to be assigned to the interface.
|
||||
///
|
||||
/// @return An object representing interface.
|
||||
static Iface createIface(const std::string& name, const int ifindex,
|
||||
const std::string& addr) {
|
||||
static Iface createIface(const std::string& name, const int ifindex) {
|
||||
Iface iface(name, ifindex);
|
||||
iface.addAddress(IOAddress(addr));
|
||||
if (name == "lo") {
|
||||
iface.flag_loopback_ = true;
|
||||
}
|
||||
iface.flag_multicast_ = true;
|
||||
// On BSD systems, the SO_BINDTODEVICE option is not supported.
|
||||
// Therefore the IfaceMgr will throw an exception on attempt to
|
||||
// open sockets on more than one broadcast-capable interface at
|
||||
// the same time. In order to prevent this error, we mark all
|
||||
// interfaces broadcast-incapable for unit testing.
|
||||
iface.flag_broadcast_ = false;
|
||||
iface.flag_up_ = true;
|
||||
iface.flag_running_ = true;
|
||||
iface.inactive4_ = false;
|
||||
iface.inactive6_ = false;
|
||||
return (iface);
|
||||
}
|
||||
|
||||
/// @brief Modified flags on the interface.
|
||||
/// @brief Checks if the specified interface has a socket bound to a
|
||||
/// specified adddress.
|
||||
///
|
||||
/// @param iface_name A name of the interface.
|
||||
/// @param addr An address to be checked for binding.
|
||||
///
|
||||
/// @return true if there is a socket bound to the specified address.
|
||||
bool isBound(const std::string& iface_name, const std::string& addr) {
|
||||
Iface* iface = getIface(iface_name);
|
||||
if (iface == NULL) {
|
||||
ADD_FAILURE() << "the interface " << iface_name << " doesn't exist";
|
||||
return (false);
|
||||
}
|
||||
const Iface::SocketCollection& sockets = iface->getSockets();
|
||||
for (Iface::SocketCollection::const_iterator sock = sockets.begin();
|
||||
sock != sockets.end(); ++sock) {
|
||||
if (sock->addr_ == IOAddress(addr)) {
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
return (false);
|
||||
}
|
||||
|
||||
/// @brief Modify flags on the interface.
|
||||
///
|
||||
/// @param name A name of the interface.
|
||||
/// @param loopback A new value of the loopback flag.
|
||||
@@ -214,14 +256,16 @@ public:
|
||||
/// @param inactive A new value of the inactive flag.
|
||||
void setIfaceFlags(const std::string& name, const bool loopback,
|
||||
const bool up, const bool running,
|
||||
const bool inactive) {
|
||||
const bool inactive4,
|
||||
const bool inactive6) {
|
||||
for (IfaceMgr::IfaceCollection::iterator iface = ifaces_.begin();
|
||||
iface != ifaces_.end(); ++iface) {
|
||||
if (iface->getName() == name) {
|
||||
iface->flag_loopback_ = loopback;
|
||||
iface->flag_up_ = up;
|
||||
iface->flag_running_ = running;
|
||||
iface->inactive4_ = inactive;
|
||||
iface->inactive4_ = inactive4;
|
||||
iface->inactive6_ = inactive6;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -244,6 +288,43 @@ public:
|
||||
~IfaceMgrTest() {
|
||||
}
|
||||
|
||||
/// @brief Tests the number of IPv6 sockets on interface
|
||||
///
|
||||
/// This function checks the expected number of open IPv6 sockets on the
|
||||
/// specified interface. On non-Linux systems, sockets are bound to a
|
||||
/// link-local address and the number of unicast addresses specified.
|
||||
/// On Linux systems, there is one more socket bound to a ff02::1:2
|
||||
/// multicast address.
|
||||
///
|
||||
/// @param iface An interface on which sockets are open.
|
||||
/// @param unicast_num A number of unicast addresses bound.
|
||||
/// @param link_local_num A number of link local addresses bound.
|
||||
void checkSocketsCount6(const Iface& iface, const int unicast_num,
|
||||
const int link_local_num = 1) {
|
||||
// On local-loopback interface, there should be no sockets.
|
||||
if (iface.flag_loopback_) {
|
||||
ASSERT_TRUE(iface.getSockets().empty())
|
||||
<< "expected empty socket set on loopback interface "
|
||||
<< iface.getName();
|
||||
return;
|
||||
}
|
||||
#if defined OS_LINUX
|
||||
// On Linux, for each link-local address there may be an
|
||||
// additional socket opened and bound to ff02::1:2. This socket
|
||||
// is only opened if the interface is multicast-capable.
|
||||
ASSERT_EQ(unicast_num + (iface.flag_multicast_ ? link_local_num : 0)
|
||||
+ link_local_num, iface.getSockets().size())
|
||||
<< "invalid number of sockets on interface "
|
||||
<< iface.getName();
|
||||
#else
|
||||
// On non-Linux, there is no additional socket.
|
||||
ASSERT_EQ(unicast_num + link_local_num, iface.getSockets().size())
|
||||
<< "invalid number of sockets on interface "
|
||||
<< iface.getName();
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
// Get ther number of IPv4 or IPv6 sockets on the loopback interface
|
||||
int getOpenSocketsCount(const Iface& iface, uint16_t family) const {
|
||||
// Get all sockets.
|
||||
@@ -1073,6 +1154,49 @@ TEST_F(IfaceMgrTest, setPacketFilter) {
|
||||
EXPECT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter));
|
||||
}
|
||||
|
||||
// This test checks that the default packet filter for DHCPv6 can be replaced
|
||||
// with the custom one.
|
||||
TEST_F(IfaceMgrTest, setPacketFilter6) {
|
||||
// Create an instance of IfaceMgr.
|
||||
boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr());
|
||||
ASSERT_TRUE(iface_mgr);
|
||||
|
||||
// Try to set NULL packet filter object and make sure it is rejected.
|
||||
boost::shared_ptr<PktFilter6Stub> custom_packet_filter;
|
||||
EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter),
|
||||
isc::dhcp::InvalidPacketFilter);
|
||||
|
||||
// Create valid object and check if it can be set.
|
||||
custom_packet_filter.reset(new PktFilter6Stub());
|
||||
ASSERT_TRUE(custom_packet_filter);
|
||||
ASSERT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter));
|
||||
|
||||
// Try to open socket using IfaceMgr. It should call the openSocket()
|
||||
// function on the packet filter object we have set.
|
||||
IOAddress loAddr("::1");
|
||||
int socket1 = 0;
|
||||
EXPECT_NO_THROW(
|
||||
socket1 = iface_mgr->openSocket(LOOPBACK, loAddr,
|
||||
DHCP6_SERVER_PORT + 10000);
|
||||
);
|
||||
// Check that openSocket function has been actually called on the packet
|
||||
// filter object.
|
||||
EXPECT_EQ(1, custom_packet_filter->open_socket_count_);
|
||||
// Also check that the returned socket descriptor has an expected value.
|
||||
EXPECT_EQ(0, socket1);
|
||||
|
||||
// Replacing current packet filter object, while there are sockets open,
|
||||
// is not allowed!
|
||||
EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter),
|
||||
PacketFilterChangeDenied);
|
||||
|
||||
// So, let's close the IPv6 sockets and retry. Now it should succeed.
|
||||
iface_mgr->closeSockets(AF_INET6);
|
||||
EXPECT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter));
|
||||
|
||||
}
|
||||
|
||||
|
||||
#if defined OS_LINUX
|
||||
|
||||
// This Linux specific test checks whether it is possible to use
|
||||
@@ -1251,7 +1375,7 @@ TEST_F(IfaceMgrTest, openSockets4IfaceDown) {
|
||||
// - is "down" (not up)
|
||||
// - is not running
|
||||
// - is active (is not inactive)
|
||||
ifacemgr.setIfaceFlags("eth0", false, false, true, false);
|
||||
ifacemgr.setIfaceFlags("eth0", false, false, true, false, false);
|
||||
ASSERT_FALSE(ifacemgr.getIface("eth0")->flag_up_);
|
||||
ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL));
|
||||
|
||||
@@ -1282,7 +1406,7 @@ TEST_F(IfaceMgrTest, openSockets4IfaceInactive) {
|
||||
// - is up
|
||||
// - is running
|
||||
// - is inactive
|
||||
ifacemgr.setIfaceFlags("eth1", false, true, true, true);
|
||||
ifacemgr.setIfaceFlags("eth1", false, true, true, true, false);
|
||||
ASSERT_TRUE(ifacemgr.getIface("eth1")->inactive4_);
|
||||
ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL));
|
||||
|
||||
@@ -1359,6 +1483,332 @@ TEST_F(IfaceMgrTest, openSocket4ErrorHandler) {
|
||||
|
||||
}
|
||||
|
||||
// This test checks that the sockets are open and bound to link local addresses
|
||||
// only, if unicast addresses are not specified.
|
||||
TEST_F(IfaceMgrTest, openSockets6LinkLocal) {
|
||||
NakedIfaceMgr ifacemgr;
|
||||
|
||||
// Remove all real interfaces and create a set of dummy interfaces.
|
||||
ifacemgr.createIfaces();
|
||||
|
||||
boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
|
||||
ASSERT_TRUE(filter);
|
||||
ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
|
||||
|
||||
// Simulate opening sockets using the dummy packet filter.
|
||||
bool success = false;
|
||||
ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT));
|
||||
EXPECT_TRUE(success);
|
||||
|
||||
// Check that the number of sockets is correct on each interface.
|
||||
checkSocketsCount6(*ifacemgr.getIface("lo"), 0);
|
||||
checkSocketsCount6(*ifacemgr.getIface("eth0"), 0);
|
||||
checkSocketsCount6(*ifacemgr.getIface("eth1"), 0);
|
||||
|
||||
// Sockets on eth0 should be bound to link-local and should not be bound
|
||||
// to global unicast address, even though this address is configured on
|
||||
// the eth0.
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef"));
|
||||
EXPECT_FALSE(ifacemgr.isBound("eth0", "2001:db8:1::1"));
|
||||
// Socket on eth1 should be bound to link local only.
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd"));
|
||||
|
||||
// If we are on Linux, there is one more socket bound to ff02::1:2
|
||||
#if defined OS_LINUX
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
|
||||
#endif
|
||||
}
|
||||
|
||||
// This test checks that socket is not open on the interface which doesn't
|
||||
// have a link-local address.
|
||||
TEST_F(IfaceMgrTest, openSockets6NoLinkLocal) {
|
||||
NakedIfaceMgr ifacemgr;
|
||||
|
||||
// Remove all real interfaces and create a set of dummy interfaces.
|
||||
ifacemgr.createIfaces();
|
||||
|
||||
boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
|
||||
ASSERT_TRUE(filter);
|
||||
ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
|
||||
|
||||
// Remove a link local address from eth0. If there is no link-local
|
||||
// address, the socket should not open.
|
||||
ASSERT_TRUE(ifacemgr.getIface("eth0")->
|
||||
delAddress(IOAddress("fe80::3a60:77ff:fed5:cdef")));
|
||||
|
||||
// Simulate opening sockets using the dummy packet filter.
|
||||
bool success = false;
|
||||
ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT));
|
||||
EXPECT_TRUE(success);
|
||||
|
||||
// Check that the number of sockets is correct on each interface.
|
||||
checkSocketsCount6(*ifacemgr.getIface("lo"), 0);
|
||||
// The thrid parameter specifies that the number of link-local
|
||||
// addresses for eth0 is equal to 0.
|
||||
checkSocketsCount6(*ifacemgr.getIface("eth0"), 0, 0);
|
||||
checkSocketsCount6(*ifacemgr.getIface("eth1"), 0, 1);
|
||||
|
||||
// There should be no sockets open on eth0 because it neither has
|
||||
// link-local nor global unicast addresses.
|
||||
EXPECT_FALSE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef"));
|
||||
EXPECT_FALSE(ifacemgr.isBound("eth0", "2001:db8:1::1"));
|
||||
// Socket on eth1 should be bound to link local only.
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd"));
|
||||
|
||||
// If we are on Linux, there is one more socket bound to ff02::1:2
|
||||
#if defined OS_LINUX
|
||||
EXPECT_FALSE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
// This test checks that socket is open on the non-muticast-capable
|
||||
// interface. This socket simply doesn't join multicast group.
|
||||
TEST_F(IfaceMgrTest, openSockets6NotMulticast) {
|
||||
NakedIfaceMgr ifacemgr;
|
||||
|
||||
// Remove all real interfaces and create a set of dummy interfaces.
|
||||
ifacemgr.createIfaces();
|
||||
|
||||
boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
|
||||
ASSERT_TRUE(filter);
|
||||
ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
|
||||
|
||||
// Make eth0 multicast-incapable.
|
||||
ifacemgr.getIface("eth0")->flag_multicast_ = false;
|
||||
|
||||
// Simulate opening sockets using the dummy packet filter.
|
||||
bool success = false;
|
||||
ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT));
|
||||
EXPECT_TRUE(success);
|
||||
|
||||
// Check that the number of sockets is correct on each interface.
|
||||
checkSocketsCount6(*ifacemgr.getIface("lo"), 0);
|
||||
checkSocketsCount6(*ifacemgr.getIface("eth0"), 0);
|
||||
checkSocketsCount6(*ifacemgr.getIface("eth1"), 0);
|
||||
|
||||
// Sockets on eth0 should be bound to link-local and should not be bound
|
||||
// to global unicast address, even though this address is configured on
|
||||
// the eth0.
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef"));
|
||||
EXPECT_FALSE(ifacemgr.isBound("eth0", "2001:db8:1::1"));
|
||||
// The eth0 is not a multicast-capable interface, so the socket should
|
||||
// not be bound to the multicast address.
|
||||
EXPECT_FALSE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
|
||||
// Socket on eth1 should be bound to link local only.
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd"));
|
||||
|
||||
// If we are on Linux, there is one more socket bound to ff02::1:2
|
||||
// on eth1.
|
||||
#if defined OS_LINUX
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
|
||||
#endif
|
||||
}
|
||||
|
||||
// This test checks that the sockets are opened and bound to link local
|
||||
// and unicast addresses which have been explicitly specified.
|
||||
TEST_F(IfaceMgrTest, openSockets6Unicast) {
|
||||
NakedIfaceMgr ifacemgr;
|
||||
|
||||
// Remove all real interfaces and create a set of dummy interfaces.
|
||||
ifacemgr.createIfaces();
|
||||
|
||||
boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
|
||||
ASSERT_TRUE(filter);
|
||||
ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
|
||||
|
||||
// Configure the eth0 to open socket on the unicast address, apart
|
||||
// from link-local address.
|
||||
ifacemgr.getIface("eth0")->addUnicast(IOAddress("2001:db8:1::1"));
|
||||
|
||||
// Simulate opening sockets using the dummy packet filter.
|
||||
bool success = false;
|
||||
ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT));
|
||||
EXPECT_TRUE(success);
|
||||
|
||||
// Check that we have correct number of sockets on each interface.
|
||||
checkSocketsCount6(*ifacemgr.getIface("lo"), 0);
|
||||
checkSocketsCount6(*ifacemgr.getIface("eth0"), 1); // one unicast address.
|
||||
checkSocketsCount6(*ifacemgr.getIface("eth1"), 0);
|
||||
|
||||
// eth0 should have two sockets, one bound to link-local, another one
|
||||
// bound to unicast address.
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef"));
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth0", "2001:db8:1::1"));
|
||||
// eth1 should have one socket, bound to link-local address.
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd"));
|
||||
|
||||
// If we are on Linux, there is one more socket bound to ff02::1:2
|
||||
#if defined OS_LINUX
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
// This test checks that the socket is open and bound to a global unicast
|
||||
// address if the link-local address does not exist for the particular
|
||||
// interface.
|
||||
TEST_F(IfaceMgrTest, openSockets6UnicastOnly) {
|
||||
NakedIfaceMgr ifacemgr;
|
||||
|
||||
// Remove all real interfaces and create a set of dummy interfaces.
|
||||
ifacemgr.createIfaces();
|
||||
|
||||
boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
|
||||
ASSERT_TRUE(filter);
|
||||
ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
|
||||
|
||||
// Configure the eth0 to open socket on the unicast address, apart
|
||||
// from link-local address.
|
||||
ifacemgr.getIface("eth0")->addUnicast(IOAddress("2001:db8:1::1"));
|
||||
// Explicitly remove the link-local address from eth0.
|
||||
ASSERT_TRUE(ifacemgr.getIface("eth0")->
|
||||
delAddress(IOAddress("fe80::3a60:77ff:fed5:cdef")));
|
||||
|
||||
// Simulate opening sockets using the dummy packet filter.
|
||||
bool success = false;
|
||||
ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT));
|
||||
EXPECT_TRUE(success);
|
||||
|
||||
// Check that we have correct number of sockets on each interface.
|
||||
checkSocketsCount6(*ifacemgr.getIface("lo"), 0);
|
||||
checkSocketsCount6(*ifacemgr.getIface("eth0"), 1, 0);
|
||||
checkSocketsCount6(*ifacemgr.getIface("eth1"), 0);
|
||||
|
||||
// The link-local address is not present on eth0. Therefore, no socket
|
||||
// must be bound to this address, nor to multicast address.
|
||||
EXPECT_FALSE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef"));
|
||||
EXPECT_FALSE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
|
||||
// There should be one socket bound to unicast address.
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth0", "2001:db8:1::1"));
|
||||
// eth1 should have one socket, bound to link-local address.
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd"));
|
||||
|
||||
// If we are on Linux, there is one more socket bound to ff02::1:2
|
||||
#if defined OS_LINUX
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
// This test checks that no sockets are open for the interface which is down.
|
||||
TEST_F(IfaceMgrTest, openSockets6IfaceDown) {
|
||||
NakedIfaceMgr ifacemgr;
|
||||
|
||||
// Remove all real interfaces and create a set of dummy interfaces.
|
||||
ifacemgr.createIfaces();
|
||||
|
||||
boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
|
||||
ASSERT_TRUE(filter);
|
||||
ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
|
||||
|
||||
// Configure the eth0 to open socket on the unicast address, apart
|
||||
// from link-local address.
|
||||
ifacemgr.getIface("eth0")->addUnicast(IOAddress("2001:db8:1::1"));
|
||||
|
||||
// Boolean parameters specify that eth0 is:
|
||||
// - not a loopback
|
||||
// - is "down" (not up)
|
||||
// - is not running
|
||||
// - is active for both v4 and v6
|
||||
ifacemgr.setIfaceFlags("eth0", false, false, false, false, false);
|
||||
|
||||
// Simulate opening sockets using the dummy packet filter.
|
||||
bool success = false;
|
||||
ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT));
|
||||
EXPECT_TRUE(success);
|
||||
|
||||
// Check that we have correct number of sockets on each interface.
|
||||
checkSocketsCount6(*ifacemgr.getIface("lo"), 0);
|
||||
// There should be no sockets on eth0 because interface is down.
|
||||
ASSERT_TRUE(ifacemgr.getIface("eth0")->getSockets().empty());
|
||||
checkSocketsCount6(*ifacemgr.getIface("eth1"), 0);
|
||||
|
||||
// eth0 should have no sockets because the interface is down.
|
||||
EXPECT_FALSE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef"));
|
||||
EXPECT_FALSE(ifacemgr.isBound("eth0", "2001:db8:1::1"));
|
||||
EXPECT_FALSE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
|
||||
// eth1 should have one socket, bound to link-local address.
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd"));
|
||||
|
||||
// If we are on Linux, there is one more socket bound to ff02::1:2
|
||||
#if defined OS_LINUX
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
// This test checks that no sockets are open for the interface which is
|
||||
// inactive.
|
||||
TEST_F(IfaceMgrTest, openSockets6IfaceInactive) {
|
||||
NakedIfaceMgr ifacemgr;
|
||||
|
||||
// Remove all real interfaces and create a set of dummy interfaces.
|
||||
ifacemgr.createIfaces();
|
||||
|
||||
boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
|
||||
ASSERT_TRUE(filter);
|
||||
ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
|
||||
|
||||
// Configure the eth0 to open socket on the unicast address, apart
|
||||
// from link-local address.
|
||||
ifacemgr.getIface("eth0")->addUnicast(IOAddress("2001:db8:1::1"));
|
||||
|
||||
// Boolean parameters specify that eth1 is:
|
||||
// - not a loopback
|
||||
// - is up
|
||||
// - is running
|
||||
// - is active for v4
|
||||
// - is inactive for v6
|
||||
ifacemgr.setIfaceFlags("eth1", false, true, true, false, true);
|
||||
|
||||
// Simulate opening sockets using the dummy packet filter.
|
||||
bool success = false;
|
||||
ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT));
|
||||
EXPECT_TRUE(success);
|
||||
|
||||
// Check that we have correct number of sockets on each interface.
|
||||
checkSocketsCount6(*ifacemgr.getIface("lo"), 0);
|
||||
checkSocketsCount6(*ifacemgr.getIface("eth0"), 1); // one unicast address
|
||||
// There should be no sockets on eth1 because interface is inactive
|
||||
ASSERT_TRUE(ifacemgr.getIface("eth1")->getSockets().empty());
|
||||
|
||||
// eth0 should have one socket bound to a link-local address, another one
|
||||
// bound to unicast address.
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef"));
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth0", "2001:db8:1::1"));
|
||||
|
||||
// eth1 shouldn't have a socket bound to link local address because
|
||||
// interface is inactive.
|
||||
EXPECT_FALSE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd"));
|
||||
EXPECT_FALSE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
|
||||
|
||||
// If we are on Linux, there is one more socket bound to ff02::1:2
|
||||
#if defined OS_LINUX
|
||||
EXPECT_TRUE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
// Test that the openSockets6 function does not throw if there are no interfaces
|
||||
// detected. This function is expected to return false.
|
||||
TEST_F(IfaceMgrTest, openSockets6NoIfaces) {
|
||||
NakedIfaceMgr ifacemgr;
|
||||
// Remove existing interfaces.
|
||||
ifacemgr.getIfacesLst().clear();
|
||||
|
||||
boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
|
||||
ASSERT_TRUE(filter);
|
||||
ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
|
||||
|
||||
// This value indicates if at least one socket opens. There are no
|
||||
// interfaces, so it should be set to false.
|
||||
bool socket_open = false;
|
||||
ASSERT_NO_THROW(socket_open = ifacemgr.openSockets6(DHCP6_SERVER_PORT));
|
||||
EXPECT_FALSE(socket_open);
|
||||
}
|
||||
|
||||
// Test the Iface structure itself
|
||||
TEST_F(IfaceMgrTest, iface) {
|
||||
|
203
src/lib/dhcp/tests/pkt_filter6_test_utils.cc
Normal file
203
src/lib/dhcp/tests/pkt_filter6_test_utils.cc
Normal file
@@ -0,0 +1,203 @@
|
||||
// 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());
|
||||
}
|
||||
|
||||
PktFilter6Stub::PktFilter6Stub()
|
||||
: open_socket_count_ (0) {
|
||||
}
|
||||
|
||||
SocketInfo
|
||||
PktFilter6Stub::openSocket(const Iface&, const isc::asiolink::IOAddress& addr,
|
||||
const uint16_t port, const bool) {
|
||||
++open_socket_count_;
|
||||
return (SocketInfo(addr, port, 0));
|
||||
}
|
||||
|
||||
Pkt6Ptr
|
||||
PktFilter6Stub::receive(const SocketInfo&) {
|
||||
return Pkt6Ptr();
|
||||
}
|
||||
|
||||
int
|
||||
PktFilter6Stub::send(const Iface&, uint16_t, const Pkt6Ptr&) {
|
||||
return (0);
|
||||
}
|
||||
|
||||
|
||||
} // end of isc::dhcp::test namespace
|
||||
} // end of isc::dhcp namespace
|
||||
} // end of isc namespace
|
155
src/lib/dhcp/tests/pkt_filter6_test_utils.h
Normal file
155
src/lib/dhcp/tests/pkt_filter6_test_utils.h
Normal file
@@ -0,0 +1,155 @@
|
||||
// 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.
|
||||
|
||||
};
|
||||
|
||||
/// @brief A stub implementation of the PktFilter6 class.
|
||||
///
|
||||
/// This class implements abstract methods of the @c isc::dhcp::PktFilter class.
|
||||
/// The methods of this class mimic operations on sockets, but they neither
|
||||
/// open actual sockets, nor perform any send nor receive operations on them.
|
||||
class PktFilter6Stub : public PktFilter6 {
|
||||
public:
|
||||
|
||||
/// @brief Constructor
|
||||
PktFilter6Stub();
|
||||
|
||||
/// @brief Simulate opening of a socket.
|
||||
///
|
||||
/// This function simulates opening a socket. In reality, it doesn't open a
|
||||
/// socket but the socket descriptor returned in the SocketInfo structure is
|
||||
/// always set to 0. On each call to this function, the counter of
|
||||
/// invocations is increased by one. This is useful to check if packet
|
||||
/// filter object has been correctly installed and is used by @c IfaceMgr.
|
||||
///
|
||||
/// @param iface Interface descriptor.
|
||||
/// @param addr Address on the interface to be used to send packets.
|
||||
/// @param port Port number.
|
||||
/// @param join_multicast A boolean parameter which indicates whether
|
||||
/// socket should join All_DHCP_Relay_Agents_and_servers multicast
|
||||
/// group.
|
||||
///
|
||||
/// @return A structure describing a primary and fallback socket.
|
||||
virtual SocketInfo openSocket(const Iface& iface,
|
||||
const isc::asiolink::IOAddress& addr,
|
||||
const uint16_t port,
|
||||
const bool join_multicast);
|
||||
|
||||
/// @brief Simulate reception of the DHCPv6 message.
|
||||
///
|
||||
/// @param socket_info A structure holding socket information.
|
||||
///
|
||||
/// @return Always a NULL object.
|
||||
virtual Pkt6Ptr receive(const SocketInfo& socket_info);
|
||||
|
||||
/// @brief Simulate sending a DHCPv6 message.
|
||||
///
|
||||
/// This function does nothing.
|
||||
///
|
||||
/// @param iface Interface to be used to send packet.
|
||||
/// @param sockfd A socket descriptor
|
||||
/// @param pkt A packet to be sent.
|
||||
///
|
||||
/// @note All parameters are ignored.
|
||||
///
|
||||
/// @return 0.
|
||||
virtual int send(const Iface& iface, uint16_t sockfd, const Pkt6Ptr& pkt);
|
||||
|
||||
/// Holds the number of invocations to PktFilter6Stub::openSocket.
|
||||
int open_socket_count_;
|
||||
|
||||
};
|
||||
|
||||
}; // end of isc::dhcp::test namespace
|
||||
}; // end of isc::dhcp namespace
|
||||
}; // end of isc namespace
|
||||
|
||||
#endif // PKT_FILTER6_TEST_UTILS_H
|
140
src/lib/dhcp/tests/pkt_filter_inet6_unittest.cc
Normal file
140
src/lib/dhcp/tests/pkt_filter_inet6_unittest.cc
Normal file
@@ -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, true);
|
||||
// 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, true);
|
||||
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, true);
|
||||
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
|
@@ -93,7 +93,7 @@ public:
|
||||
|
||||
/// @brief A stub implementation of the PktFilter class.
|
||||
///
|
||||
/// This class implements abstract methods of the @c isc::dhcp::test::PktFilter
|
||||
/// This class implements abstract methods of the @c isc::dhcp::PktFilter
|
||||
/// class. It is used by unit tests, which test protected methods of the
|
||||
/// @c isc::dhcp::test::PktFilter class. The implemented abstract methods are
|
||||
/// no-op.
|
||||
|
Reference in New Issue
Block a user