mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-09-04 16:05:17 +00:00
[master] Merge branch 'trac3251'
This commit is contained in:
@@ -73,6 +73,7 @@
|
|||||||
* - @subpage libdhcpRelay
|
* - @subpage libdhcpRelay
|
||||||
* - @subpage libdhcpIfaceMgr
|
* - @subpage libdhcpIfaceMgr
|
||||||
* - @subpage libdhcpPktFilter
|
* - @subpage libdhcpPktFilter
|
||||||
|
* - @subpage libdhcpPktFilter6
|
||||||
* - @subpage libdhcpsrv
|
* - @subpage libdhcpsrv
|
||||||
* - @subpage leasemgr
|
* - @subpage leasemgr
|
||||||
* - @subpage cfgmgr
|
* - @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 += pkt6.cc pkt6.h
|
||||||
libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
|
libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
|
||||||
libb10_dhcp___la_SOURCES += pkt_filter.h pkt_filter.cc
|
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_inet.cc pkt_filter_inet.h
|
||||||
|
libb10_dhcp___la_SOURCES += pkt_filter_inet6.cc pkt_filter_inet6.h
|
||||||
|
|
||||||
if OS_LINUX
|
if OS_LINUX
|
||||||
libb10_dhcp___la_SOURCES += pkt_filter_lpf.cc pkt_filter_lpf.h
|
libb10_dhcp___la_SOURCES += pkt_filter_lpf.cc pkt_filter_lpf.h
|
||||||
|
@@ -23,6 +23,7 @@
|
|||||||
#include <dhcp/dhcp6.h>
|
#include <dhcp/dhcp6.h>
|
||||||
#include <dhcp/iface_mgr.h>
|
#include <dhcp/iface_mgr.h>
|
||||||
#include <dhcp/pkt_filter_inet.h>
|
#include <dhcp/pkt_filter_inet.h>
|
||||||
|
#include <dhcp/pkt_filter_inet6.h>
|
||||||
#include <exceptions/exceptions.h>
|
#include <exceptions/exceptions.h>
|
||||||
#include <util/io/pktinfo_utilities.h>
|
#include <util/io/pktinfo_utilities.h>
|
||||||
|
|
||||||
@@ -169,7 +170,8 @@ IfaceMgr::IfaceMgr()
|
|||||||
:control_buf_len_(CMSG_SPACE(sizeof(struct in6_pktinfo))),
|
:control_buf_len_(CMSG_SPACE(sizeof(struct in6_pktinfo))),
|
||||||
control_buf_(new char[control_buf_len_]),
|
control_buf_(new char[control_buf_len_]),
|
||||||
session_socket_(INVALID_SOCKET), session_callback_(NULL),
|
session_socket_(INVALID_SOCKET), session_callback_(NULL),
|
||||||
packet_filter_(new PktFilterInet())
|
packet_filter_(new PktFilterInet()),
|
||||||
|
packet_filter6_(new PktFilterInet6())
|
||||||
{
|
{
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -225,10 +227,11 @@ IfaceMgr::isDirectResponseSupported() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
IfaceMgr::setPacketFilter(const boost::shared_ptr<PktFilter>& packet_filter) {
|
IfaceMgr::setPacketFilter(const PktFilterPtr& packet_filter) {
|
||||||
// Do not allow NULL pointer.
|
// Do not allow NULL pointer.
|
||||||
if (!packet_filter) {
|
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
|
// Different packet filters use different socket types. It does not make
|
||||||
// sense to allow the change of packet filter when there are IPv4 sockets
|
// 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.
|
// 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
|
// 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.
|
// chance to replace the packet filter if he closes sockets explicitly.
|
||||||
for (IfaceCollection::const_iterator iface = ifaces_.begin();
|
if (hasOpenSocket(AF_INET)) {
|
||||||
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.
|
// There is at least one socket open, so we have to fail.
|
||||||
isc_throw(PacketFilterChangeDenied,
|
isc_throw(PacketFilterChangeDenied,
|
||||||
"it is not allowed to set new packet"
|
"it is not allowed to set new packet"
|
||||||
<< " filter when there are open IPv4 sockets - need"
|
<< " filter when there are open IPv4 sockets - need"
|
||||||
<< " to close them first");
|
<< " to close them first");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
// Everything is fine, so replace packet filter.
|
// Everything is fine, so replace packet filter.
|
||||||
packet_filter_ = 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() {
|
void IfaceMgr::stubDetectIfaces() {
|
||||||
string ifaceName;
|
string ifaceName;
|
||||||
@@ -445,42 +477,42 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
|
|||||||
continue;
|
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) {
|
if (sock < 0) {
|
||||||
const char* errstr = strerror(errno);
|
const char* errstr = strerror(errno);
|
||||||
isc_throw(SocketConfigError, "failed to open link-local socket on "
|
isc_throw(SocketConfigError, "failed to open link-local"
|
||||||
<< addr->toText() << " on interface "
|
" socket on " << addr->toText() << " on interface "
|
||||||
<< iface->getName() << ", reason: " << errstr);
|
<< 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++;
|
count++;
|
||||||
|
|
||||||
/// @todo: Remove this ifdef once we start supporting BSD systems.
|
/// @todo: Remove this ifdef once we start supporting BSD systems.
|
||||||
#if defined(OS_LINUX)
|
#if defined(OS_LINUX)
|
||||||
// To receive multicast traffic, Linux requires binding socket to
|
// To receive multicast traffic, Linux requires binding socket to
|
||||||
// a multicast group. That in turn doesn't work on NetBSD.
|
// a multicast group. That in turn doesn't work on NetBSD.
|
||||||
|
if (iface->flag_multicast_) {
|
||||||
int sock2 = openSocket(iface->getName(),
|
int sock2 =
|
||||||
|
openSocket(iface->getName(),
|
||||||
IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
|
IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
|
||||||
port);
|
port);
|
||||||
if (sock2 < 0) {
|
if (sock2 < 0) {
|
||||||
const char* errstr = strerror(errno);
|
const char* errstr = strerror(errno);
|
||||||
isc_throw(SocketConfigError, "Failed to open multicast socket on "
|
isc_throw(SocketConfigError, "Failed to open multicast"
|
||||||
<< " interface " << iface->getFullName() << ", reason:"
|
" socket on interface " << iface->getFullName()
|
||||||
<< errstr);
|
<< ", reason:" << errstr);
|
||||||
iface->delSocket(sock); // delete previously opened socket
|
iface->delSocket(sock); // delete previously opened socket
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -501,7 +533,6 @@ IfaceMgr::handleSocketConfigError(const std::string& errmsg,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
|
IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
|
||||||
for (IfaceCollection::const_iterator iface=ifaces_.begin();
|
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);
|
return openSocket4(*iface, addr, port, receive_bcast, send_bcast);
|
||||||
|
|
||||||
} else if (addr.isV6()) {
|
} else if (addr.isV6()) {
|
||||||
return openSocket6(*iface, addr, port);
|
return openSocket6(*iface, addr, port, receive_bcast);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
isc_throw(BadValue, "Failed to detect family of address: "
|
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) {
|
if (addr_it->getFamily() == family) {
|
||||||
// We have interface and address so let's open socket.
|
// We have interface and address so let's open socket.
|
||||||
// This may cause isc::Unexpected exception.
|
// This may cause isc::Unexpected exception.
|
||||||
return (openSocket(iface->getName(), *addr_it, port));
|
return (openSocket(iface->getName(), *addr_it, port, false));
|
||||||
}
|
}
|
||||||
++addr_it;
|
++addr_it;
|
||||||
}
|
}
|
||||||
@@ -638,7 +669,7 @@ int IfaceMgr::openSocketFromAddress(const IOAddress& addr,
|
|||||||
if (*addr_it == addr) {
|
if (*addr_it == addr) {
|
||||||
// Open socket using local interface, address and port.
|
// Open socket using local interface, address and port.
|
||||||
// This may cause isc::Unexpected exception.
|
// 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) {
|
int
|
||||||
|
IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port,
|
||||||
struct sockaddr_in6 addr6;
|
const bool join_multicast) {
|
||||||
memset(&addr6, 0, sizeof(addr6));
|
// Assuming that packet filter is not NULL, because its modifier checks it.
|
||||||
addr6.sin6_family = AF_INET6;
|
SocketInfo info = packet_filter6_->openSocket(iface, addr, port,
|
||||||
addr6.sin6_port = htons(port);
|
join_multicast);
|
||||||
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);
|
|
||||||
iface.addSocket(info);
|
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 uint16_t port, const bool receive_bcast,
|
||||||
const bool send_bcast) {
|
const bool send_bcast) {
|
||||||
|
|
||||||
// Skip checking if the packet_filter_ is non-NULL because this check
|
// Assuming that packet filter is not NULL, because its modifier checks it.
|
||||||
// has been already done when packet filter object was set.
|
|
||||||
|
|
||||||
SocketInfo info = packet_filter_->openSocket(iface, addr, port,
|
SocketInfo info = packet_filter_->openSocket(iface, addr, port,
|
||||||
receive_bcast, send_bcast);
|
receive_bcast, send_bcast);
|
||||||
iface.addSocket(info);
|
iface.addSocket(info);
|
||||||
@@ -804,114 +772,16 @@ int IfaceMgr::openSocket4(Iface& iface, const IOAddress& addr,
|
|||||||
return (info.sockfd_);
|
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
|
bool
|
||||||
IfaceMgr::send(const Pkt6Ptr& pkt) {
|
IfaceMgr::send(const Pkt6Ptr& pkt) {
|
||||||
int result;
|
|
||||||
|
|
||||||
Iface* iface = getIface(pkt->getIface());
|
Iface* iface = getIface(pkt->getIface());
|
||||||
if (!iface) {
|
if (!iface) {
|
||||||
isc_throw(BadValue, "Unable to send Pkt6. Invalid interface ("
|
isc_throw(BadValue, "Unable to send DHCPv6 message. Invalid interface ("
|
||||||
<< pkt->getIface() << ") specified.");
|
<< pkt->getIface() << ") specified.");
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(&control_buf_[0], 0, control_buf_len_);
|
// Assuming that packet filter is not NULL, because its modifier checks it.
|
||||||
|
return (packet_filter6_->send(*iface, getSocket(*pkt), pkt));
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@@ -919,12 +789,11 @@ IfaceMgr::send(const Pkt4Ptr& pkt) {
|
|||||||
|
|
||||||
Iface* iface = getIface(pkt->getIface());
|
Iface* iface = getIface(pkt->getIface());
|
||||||
if (!iface) {
|
if (!iface) {
|
||||||
isc_throw(BadValue, "Unable to send Pkt4. Invalid interface ("
|
isc_throw(BadValue, "Unable to send DHCPv4 message. Invalid interface ("
|
||||||
<< pkt->getIface() << ") specified.");
|
<< pkt->getIface() << ") specified.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip checking if packet filter is non-NULL because it has been
|
// Assuming that packet filter is not NULL, because its modifier checks it.
|
||||||
// already checked when packet filter was set.
|
|
||||||
return (packet_filter_->send(*iface, getSocket(*pkt), pkt));
|
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!
|
// Now we have a socket, let's get some data from it!
|
||||||
// Skip checking if packet filter is non-NULL because it has been
|
// Assuming that packet filter is not NULL, because its modifier checks it.
|
||||||
// already checked when packet filter was set.
|
|
||||||
return (packet_filter_->receive(*iface, *candidate));
|
return (packet_filter_->receive(*iface, *candidate));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1119,102 +987,8 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
|
|||||||
if (!candidate) {
|
if (!candidate) {
|
||||||
isc_throw(SocketReadError, "received data over unknown socket");
|
isc_throw(SocketReadError, "received data over unknown socket");
|
||||||
}
|
}
|
||||||
|
// Assuming that packet filter is not NULL, because its modifier checks it.
|
||||||
// Now we have a socket, let's get some data from it!
|
return (packet_filter6_->receive(*candidate));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t IfaceMgr::getSocket(const isc::dhcp::Pkt6& pkt) {
|
uint16_t IfaceMgr::getSocket(const isc::dhcp::Pkt6& pkt) {
|
||||||
|
@@ -21,6 +21,7 @@
|
|||||||
#include <dhcp/pkt4.h>
|
#include <dhcp/pkt4.h>
|
||||||
#include <dhcp/pkt6.h>
|
#include <dhcp/pkt6.h>
|
||||||
#include <dhcp/pkt_filter.h>
|
#include <dhcp/pkt_filter.h>
|
||||||
|
#include <dhcp/pkt_filter6.h>
|
||||||
|
|
||||||
#include <boost/function.hpp>
|
#include <boost/function.hpp>
|
||||||
#include <boost/noncopyable.hpp>
|
#include <boost/noncopyable.hpp>
|
||||||
@@ -554,8 +555,8 @@ public:
|
|||||||
/// @param ifname name of the interface
|
/// @param ifname name of the interface
|
||||||
/// @param addr address to be bound.
|
/// @param addr address to be bound.
|
||||||
/// @param port UDP port.
|
/// @param port UDP port.
|
||||||
/// @param receive_bcast configure IPv4 socket to receive broadcast messages.
|
/// @param receive_bcast configure IPv4 socket to receive broadcast
|
||||||
/// This parameter is ignored for IPv6 sockets.
|
/// messages or IPv6 socket to join multicast group.
|
||||||
/// @param send_bcast configure IPv4 socket to send broadcast messages.
|
/// @param send_bcast configure IPv4 socket to send broadcast messages.
|
||||||
/// This parameter is ignored for IPv6 sockets.
|
/// This parameter is ignored for IPv6 sockets.
|
||||||
///
|
///
|
||||||
@@ -577,11 +578,13 @@ public:
|
|||||||
/// Instead, the method searches through the addresses on the specified
|
/// Instead, the method searches through the addresses on the specified
|
||||||
/// interface and selects one that matches the address family.
|
/// 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 ifname name of the interface
|
||||||
/// @param port UDP port
|
/// @param port UDP port
|
||||||
/// @param family address family (AF_INET or AF_INET6)
|
/// @param family address family (AF_INET or AF_INET6)
|
||||||
/// @return socket descriptor, if socket creation, binding and multicast
|
/// @return socket descriptor, if socket creation and binding was
|
||||||
/// group join were all successful.
|
/// successful.
|
||||||
/// @throw isc::Unexpected if failed to create and bind socket.
|
/// @throw isc::Unexpected if failed to create and bind socket.
|
||||||
/// @throw isc::BadValue if there is no address on specified interface
|
/// @throw isc::BadValue if there is no address on specified interface
|
||||||
/// that belongs to given family.
|
/// that belongs to given family.
|
||||||
@@ -594,10 +597,12 @@ public:
|
|||||||
/// This methods differs from \ref openSocket in that it does not require
|
/// This methods differs from \ref openSocket in that it does not require
|
||||||
/// the specification of the interface to which the socket will be bound.
|
/// 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 addr address to be bound
|
||||||
/// @param port UDP port
|
/// @param port UDP port
|
||||||
/// @return socket descriptor, if socket creation, binding and multicast
|
/// @return socket descriptor, if socket creation and binding was
|
||||||
/// group join were all successful.
|
/// successful.
|
||||||
/// @throw isc::Unexpected if failed to create and bind socket
|
/// @throw isc::Unexpected if failed to create and bind socket
|
||||||
/// @throw isc::BadValue if specified address is not available on
|
/// @throw isc::BadValue if specified address is not available on
|
||||||
/// any interface
|
/// any interface
|
||||||
@@ -611,10 +616,12 @@ public:
|
|||||||
/// identified, \ref openSocket is called to open a socket and bind it to
|
/// identified, \ref openSocket is called to open a socket and bind it to
|
||||||
/// the interface, address and port.
|
/// 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 remote_addr remote address to connect to
|
||||||
/// @param port UDP port
|
/// @param port UDP port
|
||||||
/// @return socket descriptor, if socket creation, binding and multicast
|
/// @return socket descriptor, if socket creation and binding was
|
||||||
/// group join were all successful.
|
/// successful.
|
||||||
/// @throw isc::Unexpected if failed to create and bind socket
|
/// @throw isc::Unexpected if failed to create and bind socket
|
||||||
int openSocketFromRemoteAddress(const isc::asiolink::IOAddress& remote_addr,
|
int openSocketFromRemoteAddress(const isc::asiolink::IOAddress& remote_addr,
|
||||||
const uint16_t port);
|
const uint16_t port);
|
||||||
@@ -749,22 +756,45 @@ public:
|
|||||||
session_callback_ = callback;
|
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
|
/// Packet filter objects provide means for the @c IfaceMgr to open sockets
|
||||||
/// and sending/receiving packets through those sockets. This function
|
/// for IPv4 packets reception and sending. This function sets custom packet
|
||||||
/// sets custom Packet Filter (represented by a class derived from PktFilter)
|
/// filter (represented by a class derived from PktFilter) to be used by
|
||||||
/// to be used by IfaceMgr. Note that there must be no IPv4 sockets open
|
/// @c IfaceMgr. Note that there must be no IPv4 sockets open when this
|
||||||
/// when this function is called. Call closeSockets(AF_INET) to close
|
/// function is called. Call closeSockets(AF_INET) to close all hanging IPv4
|
||||||
/// all hanging IPv4 sockets opened by the current packet filter object.
|
/// sockets opened by the current packet filter object.
|
||||||
///
|
///
|
||||||
/// @param packet_filter new packet filter to be used by IfaceMgr to send/receive
|
/// @param packet_filter A pointer to the new packet filter object to be
|
||||||
/// packets and open sockets.
|
/// used by @c IfaceMgr.
|
||||||
///
|
///
|
||||||
/// @throw InvalidPacketFilter if provided packet filter object is NULL.
|
/// @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);
|
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.
|
/// @brief Set Packet Filter object to handle send/receive packets.
|
||||||
///
|
///
|
||||||
/// This function sets Packet Filter object to be used by IfaceMgr,
|
/// This function sets Packet Filter object to be used by IfaceMgr,
|
||||||
@@ -832,9 +862,13 @@ protected:
|
|||||||
/// @param iface reference to interface structure.
|
/// @param iface reference to interface structure.
|
||||||
/// @param addr an address the created socket should be bound to
|
/// @param addr an address the created socket should be bound to
|
||||||
/// @param port a port that 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
|
/// @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.
|
/// @brief Detects network interfaces.
|
||||||
///
|
///
|
||||||
@@ -899,23 +933,6 @@ protected:
|
|||||||
SessionCallback session_callback_;
|
SessionCallback session_callback_;
|
||||||
private:
|
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
|
/// @brief Identifies local network address to be used to
|
||||||
/// connect to remote address.
|
/// connect to remote address.
|
||||||
///
|
///
|
||||||
@@ -951,15 +968,29 @@ private:
|
|||||||
void handleSocketConfigError(const std::string& errmsg,
|
void handleSocketConfigError(const std::string& errmsg,
|
||||||
IfaceMgrErrorMsgCallback handler);
|
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
|
/// Holds instance of a class derived from PktFilter, used by the
|
||||||
/// IfaceMgr to open sockets and send/receive packets through these
|
/// IfaceMgr to open sockets and send/receive packets through these
|
||||||
/// sockets. It is possible to supply custom object using
|
/// 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
|
/// different types of sockets, e.g. SOCK_DGRAM, SOCK_RAW and different
|
||||||
/// families, e.g. AF_INET, AF_PACKET etc. Another possible type of
|
/// families, e.g. AF_INET, AF_PACKET etc. Another possible type of
|
||||||
/// Packet Filter is the one used for unit testing, which doesn't
|
/// Packet Filter is the one used for unit testing, which doesn't
|
||||||
/// open sockets but rather mimics their behavior (mock object).
|
/// open sockets but rather mimics their behavior (mock object).
|
||||||
PktFilterPtr packet_filter_;
|
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
|
}; // 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
|
cases when an application using the libdhcp++ doesn't require sending
|
||||||
DHCP messages to a device which doesn't have an address yet.
|
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
|
/// @brief Packet handling class using Linux Packet Filtering
|
||||||
///
|
///
|
||||||
/// This class provides methods to send and recive packet using raw sockets
|
/// This class provides methods to send and recive DHCPv4 messages using raw
|
||||||
/// and Linux Packet Filtering.
|
/// 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
|
||||||
/// @warning This class is not implemented yet. Therefore all functions
|
/// assigned yet.
|
||||||
/// currently throw isc::NotImplemented exception.
|
|
||||||
class PktFilterLPF : public PktFilter {
|
class PktFilterLPF : public PktFilter {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@@ -50,7 +50,9 @@ libdhcp___unittests_SOURCES += pkt4_unittest.cc
|
|||||||
libdhcp___unittests_SOURCES += pkt6_unittest.cc
|
libdhcp___unittests_SOURCES += pkt6_unittest.cc
|
||||||
libdhcp___unittests_SOURCES += pkt_filter_unittest.cc
|
libdhcp___unittests_SOURCES += pkt_filter_unittest.cc
|
||||||
libdhcp___unittests_SOURCES += pkt_filter_inet_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_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
|
if OS_LINUX
|
||||||
libdhcp___unittests_SOURCES += pkt_filter_lpf_unittest.cc
|
libdhcp___unittests_SOURCES += pkt_filter_lpf_unittest.cc
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
#include <dhcp/iface_mgr.h>
|
#include <dhcp/iface_mgr.h>
|
||||||
#include <dhcp/pkt6.h>
|
#include <dhcp/pkt6.h>
|
||||||
#include <dhcp/pkt_filter.h>
|
#include <dhcp/pkt_filter.h>
|
||||||
|
#include <dhcp/tests/pkt_filter6_test_utils.h>
|
||||||
|
|
||||||
#include <boost/bind.hpp>
|
#include <boost/bind.hpp>
|
||||||
#include <boost/scoped_ptr.hpp>
|
#include <boost/scoped_ptr.hpp>
|
||||||
@@ -35,6 +36,7 @@ using namespace std;
|
|||||||
using namespace isc;
|
using namespace isc;
|
||||||
using namespace isc::asiolink;
|
using namespace isc::asiolink;
|
||||||
using namespace isc::dhcp;
|
using namespace isc::dhcp;
|
||||||
|
using namespace isc::dhcp::test;
|
||||||
using boost::scoped_ptr;
|
using boost::scoped_ptr;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -156,7 +158,7 @@ public:
|
|||||||
/// @brief Returns the collection of existing interfaces.
|
/// @brief Returns the collection of existing interfaces.
|
||||||
IfaceCollection& getIfacesLst() { return (ifaces_); }
|
IfaceCollection& getIfacesLst() { return (ifaces_); }
|
||||||
|
|
||||||
/// @brief This function creates fictious interfaces with fictious
|
/// @brief This function creates fictitious interfaces with fictious
|
||||||
/// addresses.
|
/// addresses.
|
||||||
///
|
///
|
||||||
/// These interfaces can be used in tests that don't actually try
|
/// These interfaces can be used in tests that don't actually try
|
||||||
@@ -168,11 +170,21 @@ public:
|
|||||||
ifaces_.clear();
|
ifaces_.clear();
|
||||||
|
|
||||||
// local loopback
|
// 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
|
// 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
|
// 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.
|
/// @brief Create an object representing interface.
|
||||||
@@ -183,29 +195,59 @@ public:
|
|||||||
/// - up always true
|
/// - up always true
|
||||||
/// - running always true
|
/// - running always true
|
||||||
/// - inactive always to false
|
/// - inactive always to false
|
||||||
|
/// - multicast always to true
|
||||||
|
/// - broadcast always to false
|
||||||
///
|
///
|
||||||
/// If one needs to modify the default flag settings, the setIfaceFlags
|
/// If one needs to modify the default flag settings, the setIfaceFlags
|
||||||
/// function should be used.
|
/// function should be used.
|
||||||
///
|
///
|
||||||
/// @param name A name of the interface to be created.
|
/// @param name A name of the interface to be created.
|
||||||
/// @param ifindex An index 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.
|
/// @return An object representing interface.
|
||||||
static Iface createIface(const std::string& name, const int ifindex,
|
static Iface createIface(const std::string& name, const int ifindex) {
|
||||||
const std::string& addr) {
|
|
||||||
Iface iface(name, ifindex);
|
Iface iface(name, ifindex);
|
||||||
iface.addAddress(IOAddress(addr));
|
|
||||||
if (name == "lo") {
|
if (name == "lo") {
|
||||||
iface.flag_loopback_ = true;
|
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_up_ = true;
|
||||||
iface.flag_running_ = true;
|
iface.flag_running_ = true;
|
||||||
iface.inactive4_ = false;
|
iface.inactive4_ = false;
|
||||||
|
iface.inactive6_ = false;
|
||||||
return (iface);
|
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 name A name of the interface.
|
||||||
/// @param loopback A new value of the loopback flag.
|
/// @param loopback A new value of the loopback flag.
|
||||||
@@ -214,14 +256,16 @@ public:
|
|||||||
/// @param inactive A new value of the inactive flag.
|
/// @param inactive A new value of the inactive flag.
|
||||||
void setIfaceFlags(const std::string& name, const bool loopback,
|
void setIfaceFlags(const std::string& name, const bool loopback,
|
||||||
const bool up, const bool running,
|
const bool up, const bool running,
|
||||||
const bool inactive) {
|
const bool inactive4,
|
||||||
|
const bool inactive6) {
|
||||||
for (IfaceMgr::IfaceCollection::iterator iface = ifaces_.begin();
|
for (IfaceMgr::IfaceCollection::iterator iface = ifaces_.begin();
|
||||||
iface != ifaces_.end(); ++iface) {
|
iface != ifaces_.end(); ++iface) {
|
||||||
if (iface->getName() == name) {
|
if (iface->getName() == name) {
|
||||||
iface->flag_loopback_ = loopback;
|
iface->flag_loopback_ = loopback;
|
||||||
iface->flag_up_ = up;
|
iface->flag_up_ = up;
|
||||||
iface->flag_running_ = running;
|
iface->flag_running_ = running;
|
||||||
iface->inactive4_ = inactive;
|
iface->inactive4_ = inactive4;
|
||||||
|
iface->inactive6_ = inactive6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,6 +288,43 @@ public:
|
|||||||
~IfaceMgrTest() {
|
~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
|
// Get ther number of IPv4 or IPv6 sockets on the loopback interface
|
||||||
int getOpenSocketsCount(const Iface& iface, uint16_t family) const {
|
int getOpenSocketsCount(const Iface& iface, uint16_t family) const {
|
||||||
// Get all sockets.
|
// Get all sockets.
|
||||||
@@ -1073,6 +1154,49 @@ TEST_F(IfaceMgrTest, setPacketFilter) {
|
|||||||
EXPECT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter));
|
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
|
#if defined OS_LINUX
|
||||||
|
|
||||||
// This Linux specific test checks whether it is possible to use
|
// This Linux specific test checks whether it is possible to use
|
||||||
@@ -1251,7 +1375,7 @@ TEST_F(IfaceMgrTest, openSockets4IfaceDown) {
|
|||||||
// - is "down" (not up)
|
// - is "down" (not up)
|
||||||
// - is not running
|
// - is not running
|
||||||
// - is active (is not inactive)
|
// - 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_FALSE(ifacemgr.getIface("eth0")->flag_up_);
|
||||||
ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL));
|
ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL));
|
||||||
|
|
||||||
@@ -1282,7 +1406,7 @@ TEST_F(IfaceMgrTest, openSockets4IfaceInactive) {
|
|||||||
// - is up
|
// - is up
|
||||||
// - is running
|
// - is running
|
||||||
// - is inactive
|
// - is inactive
|
||||||
ifacemgr.setIfaceFlags("eth1", false, true, true, true);
|
ifacemgr.setIfaceFlags("eth1", false, true, true, true, false);
|
||||||
ASSERT_TRUE(ifacemgr.getIface("eth1")->inactive4_);
|
ASSERT_TRUE(ifacemgr.getIface("eth1")->inactive4_);
|
||||||
ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL));
|
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 the Iface structure itself
|
||||||
TEST_F(IfaceMgrTest, iface) {
|
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.
|
/// @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
|
/// class. It is used by unit tests, which test protected methods of the
|
||||||
/// @c isc::dhcp::test::PktFilter class. The implemented abstract methods are
|
/// @c isc::dhcp::test::PktFilter class. The implemented abstract methods are
|
||||||
/// no-op.
|
/// no-op.
|
||||||
|
Reference in New Issue
Block a user