mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-09-05 08:25:16 +00:00
[#42,!103] Initial receiver thread and packet queuing
New files: src/lib/dhcp - packet_queue.h - defines packet queuing template classes socket_info.h - contains existing class extracted iface_mgr.h tests/packet_queue4_unittest.cc tests/packet_queue6_unittest.cc src/lib/dhcp/iface_mgr.* IfaceMgr:: - new functions - receiveDHCP<4/6>Packets() - thread worker function which monitors interface sockets, enqueues packets as they are read - receiveDHCP<4/6>Packet() - reads a single packet from a socket - startDHCPReceiver(const uint16_t family) - runs receiveDHCP<4/6?appropriate worker function in a thread - stopReceiver() - stops the receiver thread - setPacketQueue<4/6> - replaces the default packet queue instance receiveDHCP<4/6>() - modified to monitor receiver watch socekts rather than interface sockets. Dequeue packets from packet queue. src/lib/dhcp/tests/iface_mgr_unittest.cc TEST_F(IfaceMgrTest, packetQueue4) TEST_F(IfaceMgrTest, packetQueue6) src/lib/dhcpsrv/cfg_iface.cc CfgIface::openSockets() - starts DHCP receiver CfgIface::closeSockets() - stops DHCP receiver
This commit is contained in:
@@ -43,6 +43,7 @@ libkea_dhcp___la_SOURCES += option_space_container.h
|
||||
libkea_dhcp___la_SOURCES += option_string.cc option_string.h
|
||||
libkea_dhcp___la_SOURCES += option_vendor.cc option_vendor.h
|
||||
libkea_dhcp___la_SOURCES += option_vendor_class.cc option_vendor_class.h
|
||||
libkea_dhcp___la_SOURCES += packet_queue.h
|
||||
libkea_dhcp___la_SOURCES += pkt.cc pkt.h
|
||||
libkea_dhcp___la_SOURCES += pkt4.cc pkt4.h
|
||||
libkea_dhcp___la_SOURCES += pkt4o6.cc pkt4o6.h
|
||||
@@ -51,6 +52,7 @@ libkea_dhcp___la_SOURCES += pkt_filter.h pkt_filter.cc
|
||||
libkea_dhcp___la_SOURCES += pkt_filter6.h pkt_filter6.cc
|
||||
libkea_dhcp___la_SOURCES += pkt_filter_inet.cc pkt_filter_inet.h
|
||||
libkea_dhcp___la_SOURCES += pkt_filter_inet6.cc pkt_filter_inet6.h
|
||||
libkea_dhcp___la_SOURCES += socket_info.h
|
||||
|
||||
# Utilize Linux Packet Filtering on Linux.
|
||||
if OS_LINUX
|
||||
|
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
#include <cstring>
|
||||
#include <errno.h>
|
||||
@@ -28,11 +29,21 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
#ifndef FD_COPY
|
||||
#define FD_COPY(orig, copy) \
|
||||
do { \
|
||||
memmove(copy, orig, sizeof(fd_set)); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
using namespace isc::asiolink;
|
||||
using namespace isc::util;
|
||||
using namespace isc::util::thread;
|
||||
using namespace isc::util::io;
|
||||
using namespace isc::util::io::internal;
|
||||
|
||||
namespace isc {
|
||||
@@ -173,16 +184,16 @@ IfaceMgr::IfaceMgr()
|
||||
packet_filter_(new PktFilterInet()),
|
||||
packet_filter6_(new PktFilterInet6()),
|
||||
test_mode_(false),
|
||||
allow_loopback_(false)
|
||||
{
|
||||
allow_loopback_(false),
|
||||
receiver_error_("no error"),
|
||||
packet_queue4_(new PacketQueueRing4()),
|
||||
packet_queue6_(new PacketQueueRing6()) {
|
||||
|
||||
try {
|
||||
|
||||
// required for sending/receiving packets
|
||||
// let's keep it in front, just in case someone
|
||||
// wants to send anything during initialization
|
||||
|
||||
// control_buf_ = boost::scoped_array<char>();
|
||||
|
||||
detectIfaces();
|
||||
|
||||
} catch (const std::exception& ex) {
|
||||
@@ -269,17 +280,33 @@ void IfaceMgr::closeSockets() {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
IfaceMgr::closeSockets(const uint16_t family) {
|
||||
BOOST_FOREACH(IfacePtr iface, ifaces_) {
|
||||
iface->closeSockets(family);
|
||||
void IfaceMgr::stopReceiver() {
|
||||
if (receiver_thread_) {
|
||||
terminate_watch_.markReady();
|
||||
receiver_thread_->wait();
|
||||
receiver_thread_.reset();
|
||||
error_watch_.clearReady();
|
||||
}
|
||||
receiver_error_ = "no error";
|
||||
if (packet_queue4_) {
|
||||
packet_queue4_->clear();
|
||||
}
|
||||
|
||||
if (packet_queue6_) {
|
||||
packet_queue6_->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
IfaceMgr::closeSockets(const uint16_t) {
|
||||
isc_throw(NotImplemented, "closeSockets(family) is obsolete");
|
||||
}
|
||||
|
||||
IfaceMgr::~IfaceMgr() {
|
||||
// control_buf_ is deleted automatically (scoped_ptr)
|
||||
control_buf_len_ = 0;
|
||||
|
||||
stopReceiver();
|
||||
closeSockets();
|
||||
}
|
||||
|
||||
@@ -636,6 +663,25 @@ IfaceMgr::openSockets6(const uint16_t port,
|
||||
return (count > 0);
|
||||
}
|
||||
|
||||
void
|
||||
IfaceMgr::startDHCPReceiver(const uint16_t family) {
|
||||
if (receiver_thread_) {
|
||||
isc_throw(Unexpected, "a receiver thread already exits");
|
||||
}
|
||||
|
||||
switch (family) {
|
||||
case AF_INET:
|
||||
receiver_thread_.reset(new Thread(boost::bind(&IfaceMgr::receiveDHCP4Packets, this)));
|
||||
break;
|
||||
case AF_INET6:
|
||||
receiver_thread_.reset(new Thread(boost::bind(&IfaceMgr::receiveDHCP6Packets, this)));
|
||||
break;
|
||||
default:
|
||||
isc_throw (BadValue, "startDHCPReceiver: invalid family: " << family);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
|
||||
BOOST_FOREACH(IfacePtr iface, ifaces_) {
|
||||
@@ -893,31 +939,11 @@ Pkt4Ptr IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
|
||||
isc_throw(BadValue, "fractional timeout must be shorter than"
|
||||
" one million microseconds");
|
||||
}
|
||||
boost::scoped_ptr<SocketInfo> candidate;
|
||||
IfacePtr iface;
|
||||
fd_set sockets;
|
||||
int maxfd = 0;
|
||||
|
||||
FD_ZERO(&sockets);
|
||||
|
||||
/// @todo: marginal performance optimization. We could create the set once
|
||||
/// and then use its copy for select(). Please note that select() modifies
|
||||
/// provided set to indicated which sockets have something to read.
|
||||
BOOST_FOREACH(iface, ifaces_) {
|
||||
BOOST_FOREACH(SocketInfo s, iface->getSockets()) {
|
||||
|
||||
// Only deal with IPv4 addresses.
|
||||
if (s.addr_.isV4()) {
|
||||
|
||||
// Add this socket to listening set
|
||||
FD_SET(s.sockfd_, &sockets);
|
||||
if (maxfd < s.sockfd_) {
|
||||
maxfd = s.sockfd_;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if there are any callbacks for external sockets registered...
|
||||
if (!callbacks_.empty()) {
|
||||
BOOST_FOREACH(SocketCallbackInfo s, callbacks_) {
|
||||
@@ -928,16 +954,31 @@ Pkt4Ptr IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
|
||||
}
|
||||
}
|
||||
|
||||
// add watch sockets.
|
||||
FD_SET(receive_watch_.getSelectFd(), &sockets);
|
||||
if (maxfd < receive_watch_.getSelectFd()) {
|
||||
maxfd = receive_watch_.getSelectFd();
|
||||
}
|
||||
FD_SET(error_watch_.getSelectFd(), &sockets);
|
||||
if (maxfd < error_watch_.getSelectFd()) {
|
||||
maxfd = error_watch_.getSelectFd();
|
||||
}
|
||||
|
||||
struct timeval select_timeout;
|
||||
select_timeout.tv_sec = timeout_sec;
|
||||
select_timeout.tv_usec = timeout_usec;
|
||||
if (packet_queue4_->empty()) {
|
||||
select_timeout.tv_sec = timeout_sec;
|
||||
select_timeout.tv_usec = timeout_usec;
|
||||
} else {
|
||||
select_timeout.tv_sec = 0;
|
||||
select_timeout.tv_usec = 0;
|
||||
}
|
||||
|
||||
// zero out the errno to be safe
|
||||
errno = 0;
|
||||
|
||||
int result = select(maxfd + 1, &sockets, NULL, NULL, &select_timeout);
|
||||
|
||||
if (result == 0) {
|
||||
if ((result == 0) && packet_queue4_->empty()) {
|
||||
// nothing received and timeout has been reached
|
||||
return (Pkt4Ptr()); // NULL
|
||||
|
||||
@@ -956,6 +997,13 @@ Pkt4Ptr IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
|
||||
}
|
||||
}
|
||||
|
||||
// Check errors.
|
||||
if (FD_ISSET(error_watch_.getSelectFd(), &sockets)) {
|
||||
string msg = receiver_error_;
|
||||
error_watch_.clearReady();
|
||||
isc_throw(SocketReadError, msg);
|
||||
}
|
||||
|
||||
// Let's find out which socket has the data
|
||||
BOOST_FOREACH(SocketCallbackInfo s, callbacks_) {
|
||||
if (!FD_ISSET(s.socket_, &sockets)) {
|
||||
@@ -974,26 +1022,16 @@ Pkt4Ptr IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
|
||||
return (Pkt4Ptr());
|
||||
}
|
||||
|
||||
// Let's find out which interface/socket has the data
|
||||
BOOST_FOREACH(iface, ifaces_) {
|
||||
BOOST_FOREACH(SocketInfo s, iface->getSockets()) {
|
||||
if (FD_ISSET(s.sockfd_, &sockets)) {
|
||||
candidate.reset(new SocketInfo(s));
|
||||
break;
|
||||
}
|
||||
// Protected packet queue access.
|
||||
{
|
||||
Mutex::Locker lock(receiver_lock_);
|
||||
Pkt4Ptr pkt = packet_queue4_->dequeuePacket();
|
||||
if (!pkt) {
|
||||
receive_watch_.clearReady();
|
||||
}
|
||||
if (candidate) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!candidate) {
|
||||
isc_throw(SocketReadError, "received data over unknown socket");
|
||||
return (pkt);
|
||||
}
|
||||
|
||||
// Now we have a socket, let's get some data from it!
|
||||
// Assuming that packet filter is not NULL, because its modifier checks it.
|
||||
return (packet_filter_->receive(*iface, *candidate));
|
||||
}
|
||||
|
||||
Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ ) {
|
||||
@@ -1003,30 +1041,11 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
|
||||
" one million microseconds");
|
||||
}
|
||||
|
||||
boost::scoped_ptr<SocketInfo> candidate;
|
||||
fd_set sockets;
|
||||
int maxfd = 0;
|
||||
|
||||
FD_ZERO(&sockets);
|
||||
|
||||
/// @todo: marginal performance optimization. We could create the set once
|
||||
/// and then use its copy for select(). Please note that select() modifies
|
||||
/// provided set to indicated which sockets have something to read.
|
||||
BOOST_FOREACH(IfacePtr iface, ifaces_) {
|
||||
|
||||
BOOST_FOREACH(SocketInfo s, iface->getSockets()) {
|
||||
// Only deal with IPv6 addresses.
|
||||
if (s.addr_.isV6()) {
|
||||
|
||||
// Add this socket to listening set
|
||||
FD_SET(s.sockfd_, &sockets);
|
||||
if (maxfd < s.sockfd_) {
|
||||
maxfd = s.sockfd_;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if there are any callbacks for external sockets registered...
|
||||
if (!callbacks_.empty()) {
|
||||
BOOST_FOREACH(SocketCallbackInfo s, callbacks_) {
|
||||
@@ -1038,16 +1057,31 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
|
||||
}
|
||||
}
|
||||
|
||||
// add watch sockets.
|
||||
FD_SET(receive_watch_.getSelectFd(), &sockets);
|
||||
if (maxfd < receive_watch_.getSelectFd()) {
|
||||
maxfd = receive_watch_.getSelectFd();
|
||||
}
|
||||
FD_SET(error_watch_.getSelectFd(), &sockets);
|
||||
if (maxfd < error_watch_.getSelectFd()) {
|
||||
maxfd = error_watch_.getSelectFd();
|
||||
}
|
||||
|
||||
struct timeval select_timeout;
|
||||
select_timeout.tv_sec = timeout_sec;
|
||||
select_timeout.tv_usec = timeout_usec;
|
||||
if (packet_queue6_->empty()) {
|
||||
select_timeout.tv_sec = timeout_sec;
|
||||
select_timeout.tv_usec = timeout_usec;
|
||||
} else {
|
||||
select_timeout.tv_sec = 0;
|
||||
select_timeout.tv_usec = 0;
|
||||
}
|
||||
|
||||
// zero out the errno to be safe
|
||||
errno = 0;
|
||||
|
||||
int result = select(maxfd + 1, &sockets, NULL, NULL, &select_timeout);
|
||||
|
||||
if (result == 0) {
|
||||
if ((result == 0) && packet_queue6_->empty()) {
|
||||
// nothing received and timeout has been reached
|
||||
return (Pkt6Ptr()); // NULL
|
||||
|
||||
@@ -1066,6 +1100,13 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
|
||||
}
|
||||
}
|
||||
|
||||
// Check errors.
|
||||
if (FD_ISSET(error_watch_.getSelectFd(), &sockets)) {
|
||||
string msg = receiver_error_;
|
||||
error_watch_.clearReady();
|
||||
isc_throw(SocketReadError, msg);
|
||||
}
|
||||
|
||||
// Let's find out which socket has the data
|
||||
BOOST_FOREACH(SocketCallbackInfo s, callbacks_) {
|
||||
if (!FD_ISSET(s.socket_, &sockets)) {
|
||||
@@ -1084,24 +1125,245 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
|
||||
return (Pkt6Ptr());
|
||||
}
|
||||
|
||||
// Let's find out which interface/socket has the data
|
||||
BOOST_FOREACH(IfacePtr iface, ifaces_) {
|
||||
BOOST_FOREACH(SocketInfo s, iface->getSockets()) {
|
||||
if (FD_ISSET(s.sockfd_, &sockets)) {
|
||||
candidate.reset(new SocketInfo(s));
|
||||
break;
|
||||
}
|
||||
// Protected DHCP packet queue access.
|
||||
{
|
||||
Mutex::Locker lock(receiver_lock_);
|
||||
Pkt6Ptr pkt = packet_queue6_->dequeuePacket();
|
||||
if (!pkt) {
|
||||
receive_watch_.clearReady();
|
||||
}
|
||||
if (candidate) {
|
||||
break;
|
||||
|
||||
return (pkt);
|
||||
}
|
||||
}
|
||||
|
||||
void IfaceMgr::receiveDHCP4Packets() {
|
||||
IfacePtr iface;
|
||||
fd_set sockets;
|
||||
int maxfd = 0;
|
||||
|
||||
FD_ZERO(&sockets);
|
||||
|
||||
// Add terminate watch socket.
|
||||
FD_SET(terminate_watch_.getSelectFd(), &sockets);
|
||||
if (maxfd < terminate_watch_.getSelectFd()) {
|
||||
maxfd = terminate_watch_.getSelectFd();
|
||||
}
|
||||
|
||||
// Add Interface sockets.
|
||||
BOOST_FOREACH(iface, ifaces_) {
|
||||
BOOST_FOREACH(SocketInfo s, iface->getSockets()) {
|
||||
|
||||
// Only deal with IPv4 addresses.
|
||||
if (s.addr_.isV4()) {
|
||||
// Add this socket to listening set.
|
||||
FD_SET(s.sockfd_, &sockets);
|
||||
if (maxfd < s.sockfd_) {
|
||||
maxfd = s.sockfd_;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!candidate) {
|
||||
isc_throw(SocketReadError, "received data over unknown socket");
|
||||
for (;;) {
|
||||
// Check the watch socket.
|
||||
if (terminate_watch_.isReady()) {
|
||||
terminate_watch_.clearReady();
|
||||
return;
|
||||
}
|
||||
|
||||
fd_set rd_set;
|
||||
FD_COPY(&sockets, &rd_set);
|
||||
|
||||
// zero out the errno to be safe.
|
||||
errno = 0;
|
||||
|
||||
// Note we wait until something happen.
|
||||
int result = select(maxfd + 1, &rd_set, 0, 0, 0);
|
||||
|
||||
// Re-check the watch socket.
|
||||
if (terminate_watch_.isReady()) {
|
||||
terminate_watch_.clearReady();
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == 0) {
|
||||
// nothing received?
|
||||
continue;
|
||||
|
||||
} else if (result < 0) {
|
||||
// This thread should not get signals?
|
||||
if (errno != EINTR) {
|
||||
// Signal the error to receive4.
|
||||
receiver_error_ = strerror(errno);
|
||||
error_watch_.markReady();
|
||||
sleep(1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Let's find out which interface/socket has data.
|
||||
BOOST_FOREACH(iface, ifaces_) {
|
||||
BOOST_FOREACH(SocketInfo s, iface->getSockets()) {
|
||||
if (FD_ISSET(s.sockfd_, &sockets)) {
|
||||
receiveDHCP4Packet(*iface, s);
|
||||
// Can take time so check one more time the watch socket.
|
||||
if (terminate_watch_.isReady()) {
|
||||
terminate_watch_.clearReady();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void IfaceMgr::receiveDHCP6Packets() {
|
||||
IfacePtr iface;
|
||||
fd_set sockets;
|
||||
int maxfd = 0;
|
||||
|
||||
FD_ZERO(&sockets);
|
||||
|
||||
// Add terminate watch socket.
|
||||
FD_SET(terminate_watch_.getSelectFd(), &sockets);
|
||||
if (maxfd < terminate_watch_.getSelectFd()) {
|
||||
maxfd = terminate_watch_.getSelectFd();
|
||||
}
|
||||
|
||||
// Add Interface sockets.
|
||||
BOOST_FOREACH(iface, ifaces_) {
|
||||
BOOST_FOREACH(SocketInfo s, iface->getSockets()) {
|
||||
|
||||
// Only deal with IPv6 addresses.
|
||||
if (s.addr_.isV6()) {
|
||||
|
||||
// Add this socket to listening set.
|
||||
FD_SET(s.sockfd_, &sockets);
|
||||
if (maxfd < s.sockfd_) {
|
||||
maxfd = s.sockfd_;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
// Check the watch socket.
|
||||
if (terminate_watch_.isReady()) {
|
||||
terminate_watch_.clearReady();
|
||||
return;
|
||||
}
|
||||
|
||||
fd_set rd_set;
|
||||
FD_COPY(&sockets, &rd_set);
|
||||
|
||||
// zero out the errno to be safe.
|
||||
errno = 0;
|
||||
|
||||
// Note we wait until something happen.
|
||||
int result = select(maxfd + 1, &rd_set, 0, 0, 0);
|
||||
|
||||
// Re-check the watch socket.
|
||||
if (terminate_watch_.isReady()) {
|
||||
terminate_watch_.clearReady();
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == 0) {
|
||||
// nothing received?
|
||||
continue;
|
||||
} else if (result < 0) {
|
||||
// This thread should not get signals?
|
||||
if (errno != EINTR) {
|
||||
// Signal the error to receive6.
|
||||
receiver_error_ = strerror(errno);
|
||||
error_watch_.markReady();
|
||||
sleep(1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Let's find out which interface/socket has data.
|
||||
BOOST_FOREACH(iface, ifaces_) {
|
||||
BOOST_FOREACH(SocketInfo s, iface->getSockets()) {
|
||||
if (FD_ISSET(s.sockfd_, &sockets)) {
|
||||
receiveDHCP6Packet(s);
|
||||
// Can take time so check one more time the watch socket.
|
||||
if (terminate_watch_.isReady()) {
|
||||
terminate_watch_.clearReady();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IfaceMgr::receiveDHCP4Packet(Iface& iface, const SocketInfo& socket_info) {
|
||||
int len;
|
||||
int result = ioctl(socket_info.sockfd_, FIONREAD, &len);
|
||||
if (result < 0) {
|
||||
// Signal the error to receive4.
|
||||
receiver_error_ = strerror(errno);
|
||||
error_watch_.markReady();
|
||||
return;
|
||||
}
|
||||
if (len == 0) {
|
||||
// Nothing to read.
|
||||
return;
|
||||
}
|
||||
|
||||
Pkt4Ptr pkt;
|
||||
|
||||
try {
|
||||
pkt = packet_filter_->receive(iface, socket_info);
|
||||
} catch (const std::exception& ex) {
|
||||
receiver_error_ = ex.what();
|
||||
error_watch_.markReady();
|
||||
} catch (...) {
|
||||
receiver_error_ = "packet filter receive() failed";
|
||||
error_watch_.markReady();
|
||||
}
|
||||
|
||||
if (pkt) {
|
||||
Mutex::Locker lock(receiver_lock_);
|
||||
packet_queue4_->enqueuePacket(pkt, socket_info);
|
||||
receive_watch_.markReady();
|
||||
}
|
||||
}
|
||||
|
||||
void IfaceMgr::receiveDHCP6Packet(const SocketInfo& socket_info) {
|
||||
int len;
|
||||
int result = ioctl(socket_info.sockfd_, FIONREAD, &len);
|
||||
if (result < 0) {
|
||||
// Signal the error to receive6.
|
||||
receiver_error_ = strerror(errno);
|
||||
error_watch_.markReady();
|
||||
return;
|
||||
}
|
||||
if (len == 0) {
|
||||
// Nothing to read.
|
||||
return;
|
||||
}
|
||||
|
||||
Pkt6Ptr pkt;
|
||||
|
||||
try {
|
||||
pkt = packet_filter6_->receive(socket_info);
|
||||
} catch (const std::exception& ex) {
|
||||
receiver_error_ = ex.what();
|
||||
error_watch_.markReady();
|
||||
} catch (...) {
|
||||
receiver_error_ = "packet filter receive() failed";
|
||||
error_watch_.markReady();
|
||||
}
|
||||
|
||||
if (pkt) {
|
||||
Mutex::Locker lock(receiver_lock_);
|
||||
packet_queue6_->enqueuePacket(pkt, socket_info);
|
||||
receive_watch_.markReady();
|
||||
}
|
||||
// Assuming that packet filter is not NULL, because its modifier checks it.
|
||||
return (packet_filter6_->receive(*candidate));
|
||||
}
|
||||
|
||||
uint16_t IfaceMgr::getSocket(const isc::dhcp::Pkt6& pkt) {
|
||||
@@ -1193,5 +1455,51 @@ IfaceMgr::getSocket(isc::dhcp::Pkt4 const& pkt) {
|
||||
return (*candidate);
|
||||
}
|
||||
|
||||
void
|
||||
IfaceMgr::setPacketQueue4(PacketQueue4Ptr& packet_queue4) {
|
||||
if (!packet_queue4) {
|
||||
isc_throw(BadValue, "IfaceMgr::setPacketQueue4 "
|
||||
" queue pointer cannot be empty");
|
||||
}
|
||||
|
||||
// On the off chance the existing impl doesn't clear on
|
||||
// destruction, we will as a safe guard.
|
||||
packet_queue4_->clear();
|
||||
packet_queue4_ = packet_queue4;
|
||||
}
|
||||
|
||||
void
|
||||
IfaceMgr::setPacketQueue6(PacketQueue6Ptr& packet_queue6) {
|
||||
if (!packet_queue6) {
|
||||
isc_throw(BadValue, "IfaceMgr::setPacketQueue6 "
|
||||
" queue pointer cannot be empty");
|
||||
}
|
||||
|
||||
// On the off chance the existing impl doesn't clear on
|
||||
// destruction, we will as a safe guard.
|
||||
packet_queue6_->clear();
|
||||
packet_queue6_ = packet_queue6;
|
||||
}
|
||||
|
||||
size_t
|
||||
IfaceMgr::getPacketQueueCapacity4() const {
|
||||
return (packet_queue4_->getCapacity());
|
||||
}
|
||||
|
||||
void
|
||||
IfaceMgr::setPacketQueueCapacity4(const size_t new_capacity) {
|
||||
packet_queue4_->setCapacity(new_capacity);
|
||||
}
|
||||
|
||||
size_t
|
||||
IfaceMgr::getPacketQueueCapacity6() const {
|
||||
return (packet_queue6_->getCapacity());
|
||||
}
|
||||
|
||||
void
|
||||
IfaceMgr::setPacketQueueCapacity6(const size_t new_capacity) {
|
||||
packet_queue6_->setCapacity(new_capacity);
|
||||
}
|
||||
|
||||
} // end of namespace isc::dhcp
|
||||
} // end of namespace isc
|
||||
|
@@ -12,14 +12,19 @@
|
||||
#include <dhcp/dhcp6.h>
|
||||
#include <dhcp/pkt4.h>
|
||||
#include <dhcp/pkt6.h>
|
||||
#include <dhcp/packet_queue.h>
|
||||
#include <dhcp/pkt_filter.h>
|
||||
#include <dhcp/pkt_filter6.h>
|
||||
#include <util/optional_value.h>
|
||||
#include <util/watch_socket.h>
|
||||
#include <util/threads/thread.h>
|
||||
#include <util/threads/sync.h>
|
||||
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <boost/scoped_array.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/circular_buffer.hpp>
|
||||
|
||||
#include <list>
|
||||
#include <vector>
|
||||
@@ -88,54 +93,6 @@ public:
|
||||
isc::Exception(file, line, what) { };
|
||||
};
|
||||
|
||||
|
||||
/// Holds information about socket.
|
||||
struct SocketInfo {
|
||||
|
||||
isc::asiolink::IOAddress addr_; /// bound address
|
||||
uint16_t port_; /// socket port
|
||||
uint16_t family_; /// IPv4 or IPv6
|
||||
|
||||
/// @brief Socket descriptor (a.k.a. primary socket).
|
||||
int sockfd_;
|
||||
|
||||
/// @brief Fallback socket descriptor.
|
||||
///
|
||||
/// This socket descriptor holds the handle to the fallback socket.
|
||||
/// The fallback socket is created when there is a need for the regular
|
||||
/// datagram socket to be bound to an IP address and port, besides
|
||||
/// primary socket (sockfd_) which is actually used to receive and process
|
||||
/// the DHCP messages. The fallback socket (if exists) is always associated
|
||||
/// with the primary socket. In particular, the need for the fallback socket
|
||||
/// arises when raw socket is a primary one. When primary socket is open,
|
||||
/// it is bound to an interface not the address and port. The implications
|
||||
/// include the possibility that the other process (e.g. the other instance
|
||||
/// of DHCP server) will bind to the same address and port through which the
|
||||
/// raw socket receives the DHCP messages.Another implication is that the
|
||||
/// kernel, being unaware of the DHCP server operating through the raw
|
||||
/// socket, will respond with the ICMP "Destination port unreachable"
|
||||
/// messages when DHCP messages are only received through the raw socket.
|
||||
/// In order to workaround the issues mentioned here, the fallback socket
|
||||
/// should be opened so as/ the kernel is aware that the certain address
|
||||
/// and port is in use.
|
||||
///
|
||||
/// The fallback description is supposed to be set to a negative value if
|
||||
/// the fallback socket is closed (not open).
|
||||
int fallbackfd_;
|
||||
|
||||
/// @brief SocketInfo constructor.
|
||||
///
|
||||
/// @param addr An address the socket is bound to.
|
||||
/// @param port A port the socket is bound to.
|
||||
/// @param sockfd Socket descriptor.
|
||||
/// @param fallbackfd A descriptor of the fallback socket.
|
||||
SocketInfo(const isc::asiolink::IOAddress& addr, const uint16_t port,
|
||||
const int sockfd, const int fallbackfd = -1)
|
||||
: addr_(addr), port_(port), family_(addr.getFamily()),
|
||||
sockfd_(sockfd), fallbackfd_(fallbackfd) { }
|
||||
|
||||
};
|
||||
|
||||
/// @brief Represents a single network interface
|
||||
///
|
||||
/// Iface structure represents network interface with all useful
|
||||
@@ -873,7 +830,7 @@ public:
|
||||
///
|
||||
/// @warning This function does not check if there has been any sockets
|
||||
/// already open by the @c IfaceMgr. Therefore a caller should call
|
||||
/// @c IfaceMgr::closeSockets(AF_INET6) before calling this function.
|
||||
/// @c IfaceMgr::closeSockets() before calling this function.
|
||||
/// If there are any sockets open, the function may either throw an
|
||||
/// exception or invoke an error handler on attempt to bind the new socket
|
||||
/// to the same address and port.
|
||||
@@ -939,7 +896,7 @@ public:
|
||||
///
|
||||
/// @warning This function does not check if there has been any sockets
|
||||
/// already open by the @c IfaceMgr. Therefore a caller should call
|
||||
/// @c IfaceMgr::closeSockets(AF_INET) before calling this function.
|
||||
/// @c IfaceMgr::closeSockets() before calling this function.
|
||||
/// If there are any sockets open, the function may either throw an
|
||||
/// exception or invoke an error handler on attempt to bind the new socket
|
||||
/// to the same address and port.
|
||||
@@ -963,23 +920,8 @@ public:
|
||||
|
||||
/// @brief Closes all IPv4 or IPv6 sockets.
|
||||
///
|
||||
/// This function closes sockets of the specific 'type' and closes them.
|
||||
/// The 'type' of the socket indicates whether it is used to send IPv4
|
||||
/// or IPv6 packets. The allowed values of the parameter are AF_INET and
|
||||
/// AF_INET6 for IPv4 and IPv6 packets respectively. It is important
|
||||
/// to realize that the actual types of sockets may be different than
|
||||
/// AF_INET for IPv4 packets. This is because, historically the IfaceMgr
|
||||
/// always used AF_INET sockets for IPv4 traffic. This is no longer the
|
||||
/// case when the Direct IPv4 traffic must be supported. In order to support
|
||||
/// direct traffic, the IfaceMgr operates on raw sockets, e.g. AF_PACKET
|
||||
/// family sockets on Linux.
|
||||
///
|
||||
/// @todo Replace the AF_INET and AF_INET6 values with an enum
|
||||
/// which will not be confused with the actual socket type.
|
||||
///
|
||||
/// @param family type of the sockets to be closed (AF_INET or AF_INET6)
|
||||
///
|
||||
/// @throw BadValue if family value is different than AF_INET or AF_INET6.
|
||||
/// Obsolete!
|
||||
/// @throw NotImplemented
|
||||
void closeSockets(const uint16_t family);
|
||||
|
||||
/// @brief Returns number of detected interfaces.
|
||||
@@ -1089,6 +1031,66 @@ public:
|
||||
/// @return true if there is a socket bound to the specified address.
|
||||
bool hasOpenSocket(const isc::asiolink::IOAddress& addr) const;
|
||||
|
||||
/// @brief Sets the DHCPv4 packet queue
|
||||
///
|
||||
/// Replaces the existing DHCPv4 packet queue with the
|
||||
/// given queue. Any packets contained in the existing
|
||||
/// queue will be discarded. This method is intended to
|
||||
/// be used by hook developers to install their own packet
|
||||
/// queue implementation(s).
|
||||
///
|
||||
/// @param packet_queue4 pointer to a PacketQueue4 instance
|
||||
/// to use to manage inbound DHCPv4 packets.
|
||||
///
|
||||
/// @throw BadValue if given an empty pointer
|
||||
void setPacketQueue4(PacketQueue4Ptr& packet_queue4);
|
||||
|
||||
/// @brief Sets the DHCPv6 packet queue
|
||||
///
|
||||
/// Replaces the existing DHCPv6 packet queue with the
|
||||
/// given queue. Any packets contained in the existing
|
||||
/// queue will be discarded. This method is intended to
|
||||
/// be used by hook developers to install their own packet
|
||||
/// queue implementation(s).
|
||||
///
|
||||
/// @param packet_queue6 pointer to a PacketQueue6 instance
|
||||
/// to use to manage inbound DHCPv6 packets.
|
||||
///
|
||||
/// @throw BadValue if given an empty pointer
|
||||
void setPacketQueue6(PacketQueue6Ptr& packet_queue6);
|
||||
|
||||
/// @brief Returns the current capacity of the DHCPv4 packet queue buffer.
|
||||
size_t getPacketQueueCapacity4() const;
|
||||
|
||||
/// @brief Set the capacity of the DHCPv4 packet queue buffer.
|
||||
///
|
||||
/// @param new_capacity New capacity of the buffer.
|
||||
void setPacketQueueCapacity4(const size_t new_capacity);
|
||||
|
||||
/// @brief Returns the current capacity of the DHCPv6 packet queue buffer.
|
||||
size_t getPacketQueueCapacity6() const;
|
||||
|
||||
/// @brief Set the capacity of the DHCPv6 packet queue buffer.
|
||||
///
|
||||
/// @param new_capacity New capacity of the buffer.
|
||||
void setPacketQueueCapacity6(const size_t new_capacity);
|
||||
|
||||
/// @brief Starts DHCP packet receiver.
|
||||
///
|
||||
/// Starts the DHCP packet receiver thread for the given.
|
||||
/// protocol, AF_NET or AF_INET6
|
||||
///
|
||||
/// @param family indicates which receiver to start,
|
||||
/// (AF_INET or AF_INET6)
|
||||
///
|
||||
/// @throw Unexpected if the thread already exists.
|
||||
void startDHCPReceiver(const uint16_t family);
|
||||
|
||||
/// @brief Stops the DHCP packet receiver.
|
||||
///
|
||||
/// Stops the receiver and delete the dedicated thread.
|
||||
void stopReceiver();
|
||||
|
||||
// don't use private, we need derived classes in tests
|
||||
protected:
|
||||
|
||||
@@ -1138,8 +1140,7 @@ protected:
|
||||
/// and pretends to detect such interface. First interface name and
|
||||
/// link-local IPv6 address or IPv4 address is read from the
|
||||
/// interfaces.txt file.
|
||||
void
|
||||
stubDetectIfaces();
|
||||
void stubDetectIfaces();
|
||||
|
||||
// TODO: having 2 maps (ifindex->iface and ifname->iface would)
|
||||
// probably be better for performance reasons
|
||||
@@ -1205,6 +1206,52 @@ private:
|
||||
const uint16_t port,
|
||||
IfaceMgrErrorMsgCallback error_handler = 0);
|
||||
|
||||
/// @brief DHCPv4 receiver method.
|
||||
///
|
||||
/// Loops forever reading DHCPv4 packets from the interface sockets
|
||||
/// and adds them to the packet queue. It monitors the "terminate"
|
||||
/// watch socket, and exits if it is marked ready. This is method
|
||||
/// is used as the worker function in the thread created by @c
|
||||
/// startDHCP4Receiver(). It currently uses select() to monitor
|
||||
/// socket readiness. If the select errors out (other than EINTR),
|
||||
/// it marks the "error" watch socket as ready.
|
||||
void receiveDHCP4Packets();
|
||||
|
||||
/// @brief Receives a single DHCPv4 packet from an interface socket
|
||||
///
|
||||
/// Called by @c receiveDHPC4Packets when a socket fd is flagged as
|
||||
/// ready. It uses the DHCPv4 packet filter to receive a single packet
|
||||
/// from the given interface socket, adds it to the packet queue, and
|
||||
/// marks the "receive" watch socket ready. If an error occurs during
|
||||
/// the read, the "error" watch socket is marked ready.
|
||||
///
|
||||
/// @param iface interface
|
||||
/// @param socket_info structure holding socket information
|
||||
void receiveDHCP4Packet(Iface& iface, const SocketInfo& socket_info);
|
||||
|
||||
/// @brief DHCPv6 receiver method.
|
||||
///
|
||||
/// Loops forever reading DHCPv6 packets from the interface sockets
|
||||
/// and adds them to the packet queue. It monitors the "terminate"
|
||||
/// watch socket, and exits if it is marked ready. This is method
|
||||
/// is used as the worker function in the thread created by @c
|
||||
/// startDHCP6Receiver(). It currently uses select() to monitor
|
||||
/// socket readiness. If the select errors out (other than EINTR),
|
||||
/// it marks the "error" watch socket as ready.
|
||||
void receiveDHCP6Packets();
|
||||
|
||||
/// @brief Receives a single DHCPv6 packet from an interface socket
|
||||
///
|
||||
/// Called by @c receiveDHPC6Packets when a socket fd is flagged as
|
||||
/// ready. It uses the DHCPv6 packet filter to receive a single packet
|
||||
/// from the given interface socket, adds it to the packet queue, and
|
||||
/// marks the "receive" watch socket ready. If an error occurs during
|
||||
/// the read, the "error" watch socket is marked ready.
|
||||
///
|
||||
/// @param iface interface
|
||||
/// @param socket_info structure holding socket information
|
||||
void receiveDHCP6Packet(const SocketInfo& socket_info);
|
||||
|
||||
/// Holds instance of a class derived from PktFilter, used by the
|
||||
/// IfaceMgr to open sockets and send/receive packets through these
|
||||
/// sockets. It is possible to supply custom object using
|
||||
@@ -1229,6 +1276,43 @@ private:
|
||||
|
||||
/// @brief Allows to use loopback
|
||||
bool allow_loopback_;
|
||||
|
||||
/// @brief Error message of the last DHCP packet receive error.
|
||||
std::string receiver_error_;
|
||||
|
||||
/// @brief DHCPv4 receiver packet queue.
|
||||
///
|
||||
/// Incoming packets are read by the receiver thread and
|
||||
/// added to this queue. @c receive4() dequeues and
|
||||
/// returns them.
|
||||
PacketQueue4Ptr packet_queue4_;
|
||||
|
||||
/// @brief DHCPv6 receiver packet queue.
|
||||
///
|
||||
/// Incoming packets are read by the receiver thread and
|
||||
/// added to this queue. @c receive6() dequeues and
|
||||
/// returns them.
|
||||
PacketQueue6Ptr packet_queue6_;
|
||||
|
||||
/// @brief DHCP packet receive error watch socket.
|
||||
/// Marked as ready when the DHCP packet receiver experiences
|
||||
/// an I/O error.
|
||||
isc::util::WatchSocket error_watch_;
|
||||
|
||||
/// @brief DHCP packet receive watch socket.
|
||||
/// Marked as ready when the DHCP packet receiver adds a packet
|
||||
/// to the packet queue.
|
||||
isc::util::WatchSocket receive_watch_;
|
||||
|
||||
/// @brief Packet receiver terminate watch socket.
|
||||
/// Marked as ready when the DHCP packet receiver thread should terminate.
|
||||
isc::util::WatchSocket terminate_watch_;
|
||||
|
||||
/// DHCP packet receiver mutex.
|
||||
isc::util::thread::Mutex receiver_lock_;
|
||||
|
||||
/// DHCP packet receiver thread.
|
||||
isc::util::thread::ThreadPtr receiver_thread_;
|
||||
};
|
||||
|
||||
}; // namespace isc::dhcp
|
||||
|
300
src/lib/dhcp/packet_queue.h
Normal file
300
src/lib/dhcp/packet_queue.h
Normal file
@@ -0,0 +1,300 @@
|
||||
// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#ifndef PACKET_QUEUE_H
|
||||
#define PACKET_QUEUE_H
|
||||
|
||||
#include <dhcp/socket_info.h>
|
||||
#include <dhcp/pkt4.h>
|
||||
#include <dhcp/pkt6.h>
|
||||
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/circular_buffer.hpp>
|
||||
|
||||
namespace isc {
|
||||
|
||||
namespace dhcp {
|
||||
|
||||
/// @brief Enumerates choices between the two ends of the queue.
|
||||
enum class QueueEnd {
|
||||
FRONT, // Typically the end packets are read from
|
||||
BACK // Typically the end packets are written to
|
||||
};
|
||||
|
||||
/// @brief Interface for managing a queue of inbound DHCP packets
|
||||
template<typename PacketTypePtr>
|
||||
class PacketQueue {
|
||||
public:
|
||||
|
||||
/// @brief Constructor
|
||||
PacketQueue() {}
|
||||
|
||||
/// @brief virtual Destructor
|
||||
virtual ~PacketQueue(){};
|
||||
|
||||
/// @brief Adds a packet to the queue
|
||||
///
|
||||
/// Calls @c dropPacket to determine if the packet should be queued
|
||||
/// or dropped. If it should be queued it is added to the end of the
|
||||
/// specified by the "to" parameter.
|
||||
///
|
||||
/// @param packet packet to enqueue
|
||||
/// @param source socket the packet came from - this can be
|
||||
/// @param to end of the queue from which to remove packet(s).
|
||||
/// Defaults to BACK.
|
||||
///
|
||||
void enqueuePacket(PacketTypePtr packet, const SocketInfo& source,
|
||||
const QueueEnd& to=QueueEnd::BACK) {
|
||||
if (!dropPacket(packet, source)) {
|
||||
pushPacket(packet, to);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Dequeues the next packet from the queue
|
||||
///
|
||||
/// Calls @eatPackets to discard packets as needed, and then
|
||||
/// dequeues the next packet (if any) and returns it. Packets
|
||||
/// are dequeued from the end of the queue specified by the "from"
|
||||
/// parameter.
|
||||
///
|
||||
/// @param from end of the queue from which to remove packet(s).
|
||||
/// Defaults to FRONT.
|
||||
///
|
||||
/// @return A pointer to dequeued packet, or an empty pointer
|
||||
/// if the queue is empty.
|
||||
PacketTypePtr dequeuePacket(const QueueEnd& from=QueueEnd::FRONT) {
|
||||
eatPackets(from);
|
||||
return(popPacket(from));
|
||||
}
|
||||
|
||||
/// @brief Determines if a packet should be discarded.
|
||||
///
|
||||
/// This function is called at the beginning of @enqueuePacket and
|
||||
/// provides an opportunity to examine the packet and its source
|
||||
/// and decide whether it should be dropped or added to the queue.
|
||||
/// Derivations are expected to provide implementations based on
|
||||
/// their own requirements. Bear in mind that the packet has NOT
|
||||
/// been unpacked at this point. The default implementation simply
|
||||
/// returns false.
|
||||
///
|
||||
/// @param packet the packet under consideration
|
||||
/// @param source the socket the packet came from
|
||||
///
|
||||
/// @return true if the packet should be dropped, false if it should be
|
||||
/// kept.
|
||||
virtual bool dropPacket(PacketTypePtr /* packet */,
|
||||
const SocketInfo& /* source */) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
/// Discards packets from one end of the queue.
|
||||
///
|
||||
/// This function is called at the beginning of @c dequeuPacket and
|
||||
/// provides an opportunity to examine and discard packets from
|
||||
/// the queue prior to dequeuing the next packet to be
|
||||
/// processed. Derivations are expected to provide implementations
|
||||
/// based on their own requirements. The default implemenation is to
|
||||
/// to simply returns without skipping any packets.
|
||||
///
|
||||
/// @param from end of the queue from which packets should discarded
|
||||
/// This is passed in from @c dequeuePackets.
|
||||
///
|
||||
/// @return The number of packets discarded.
|
||||
virtual int eatPackets(const QueueEnd& /* from */) {
|
||||
return (0);
|
||||
}
|
||||
|
||||
/// @brief Pushes a packet onto the queue
|
||||
///
|
||||
/// Adds a packet onto the end of queue specified.
|
||||
virtual void pushPacket(PacketTypePtr& packet, const QueueEnd& to=QueueEnd::BACK) = 0;
|
||||
|
||||
/// @brief Pops a packet from the queue
|
||||
///
|
||||
/// Removes a packet from the end of the queue specified and returns it.
|
||||
///
|
||||
/// @return A pointer to dequeued packet, or an empty pointer
|
||||
/// if the queue is empty.
|
||||
virtual PacketTypePtr popPacket(const QueueEnd& from=QueueEnd::FRONT) = 0;
|
||||
|
||||
/// @brief Gets the packet currently at one end of the queue
|
||||
///
|
||||
/// Returns a pointer the packet at the specified end of the
|
||||
/// queue without dequeuing it.
|
||||
///
|
||||
/// @return A pointer to packet, or an empty pointer if the
|
||||
/// queue is empty.
|
||||
virtual const PacketTypePtr peek(const QueueEnd& from=QueueEnd::FRONT) const = 0;
|
||||
|
||||
/// @brief return True if the queue is empty.
|
||||
virtual bool empty() const = 0;
|
||||
|
||||
/// @brief Returns the maximum number of packets allowed in the buffer.
|
||||
virtual size_t getCapacity() const = 0;
|
||||
|
||||
/// @brief Sets the maximum number of packets allowed in the buffer.
|
||||
virtual void setCapacity(size_t capacity) = 0;
|
||||
|
||||
/// @brief Returns the current number of packets in the buffer.
|
||||
virtual size_t getSize() const = 0;
|
||||
|
||||
/// @brief Discards all packets currently in the buffer.
|
||||
virtual void clear() = 0;
|
||||
};
|
||||
|
||||
/// @brief Defines pointer to the DHCPv4 queue interface used at the application level.
|
||||
typedef boost::shared_ptr<PacketQueue<Pkt4Ptr>> PacketQueue4Ptr;
|
||||
|
||||
/// @brief Defines pointer to the DHCPv6 queue interface used at the application level.
|
||||
typedef boost::shared_ptr<PacketQueue<Pkt6Ptr>> PacketQueue6Ptr;
|
||||
|
||||
/// @brief Provides an abstract ring-buffer implementation of the PacketQueue interface.
|
||||
template<typename PacketTypePtr>
|
||||
class PacketQueueRing : public PacketQueue<PacketTypePtr> {
|
||||
public:
|
||||
static const size_t MIN_RING_CAPACITY = 5;
|
||||
|
||||
/// @brief Constructor
|
||||
///
|
||||
/// @param queue_capacity maximum number of packets the queue can hold
|
||||
/// Defaults to DEFAULT_RING_CAPACITY.
|
||||
PacketQueueRing(size_t capacity) {
|
||||
queue_.set_capacity(capacity);
|
||||
}
|
||||
|
||||
/// @brief virtual Destructor
|
||||
virtual ~PacketQueueRing(){};
|
||||
|
||||
/// @brief Pushes a packet onto the queue
|
||||
///
|
||||
/// Adds a packet onto the end of queue specified.
|
||||
virtual void pushPacket(PacketTypePtr& packet, const QueueEnd& to=QueueEnd::BACK) {
|
||||
if (to == QueueEnd::BACK) {
|
||||
queue_.push_back(packet);
|
||||
} else {
|
||||
queue_.push_front(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Pops a packet from the queue
|
||||
///
|
||||
/// Removes a packet from the end of the queue specified and returns it.
|
||||
///
|
||||
/// @return A pointer to dequeued packet, or an empty pointer
|
||||
/// if the queue is empty.
|
||||
virtual PacketTypePtr popPacket(const QueueEnd& from = QueueEnd::FRONT) {
|
||||
PacketTypePtr packet;
|
||||
if (queue_.empty()) {
|
||||
return (packet);
|
||||
}
|
||||
|
||||
if (from == QueueEnd::FRONT) {
|
||||
packet = queue_.front();
|
||||
queue_.pop_front();
|
||||
} else {
|
||||
packet = queue_.back();
|
||||
queue_.pop_back();
|
||||
}
|
||||
|
||||
return (packet);
|
||||
}
|
||||
|
||||
|
||||
/// @brief Gets the packet currently at one end of the queue
|
||||
///
|
||||
/// Returns a pointer the packet at the specified end of the
|
||||
/// queue without dequeuing it.
|
||||
///
|
||||
/// @return A pointer to packet, or an empty pointer if the
|
||||
/// queue is empty.
|
||||
virtual const PacketTypePtr peek(const QueueEnd& from=QueueEnd::FRONT) const {
|
||||
PacketTypePtr packet;
|
||||
if (!queue_.empty()) {
|
||||
packet = (from == QueueEnd::FRONT ? queue_.front() : queue_.back());
|
||||
}
|
||||
|
||||
return (packet);
|
||||
}
|
||||
|
||||
/// @brief Returns True if the queue is empty.
|
||||
virtual bool empty() const {
|
||||
return(queue_.empty());
|
||||
}
|
||||
|
||||
/// @brief Returns the maximum number of packets allowed in the buffer.
|
||||
virtual size_t getCapacity() const {
|
||||
return (queue_.capacity());
|
||||
}
|
||||
|
||||
/// @brief Sets the maximum number of packets allowed in the buffer.
|
||||
///
|
||||
/// @throw BadValue if capacity is too low.
|
||||
virtual void setCapacity(size_t capacity) {
|
||||
if (capacity < MIN_RING_CAPACITY) {
|
||||
isc_throw(BadValue, "Queue capacity of " << capacity
|
||||
<< " is invalid. It must be at least "
|
||||
<< MIN_RING_CAPACITY);
|
||||
}
|
||||
|
||||
/// @todo should probably throw if it's zero
|
||||
queue_.set_capacity(capacity);
|
||||
}
|
||||
|
||||
/// @brief Returns the current number of packets in the buffer.
|
||||
virtual size_t getSize() const {
|
||||
return (queue_.size());
|
||||
}
|
||||
|
||||
/// @brief Discards all packets currently in the buffer.
|
||||
virtual void clear() {
|
||||
queue_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/// @brief Packet queue
|
||||
boost::circular_buffer<PacketTypePtr> queue_;
|
||||
};
|
||||
|
||||
|
||||
/// @brief Default DHCPv4 packet queue buffer implementation
|
||||
class PacketQueueRing4 : public PacketQueueRing<Pkt4Ptr> {
|
||||
public:
|
||||
static const size_t DEFAULT_RING_CAPACITY = 500;
|
||||
|
||||
/// @brief Constructor
|
||||
///
|
||||
/// @param capacity maximum number of packets the queue can hold
|
||||
PacketQueueRing4(size_t capacity=DEFAULT_RING_CAPACITY)
|
||||
: PacketQueueRing(capacity) {
|
||||
};
|
||||
|
||||
/// @brief virtual Destructor
|
||||
virtual ~PacketQueueRing4(){};
|
||||
};
|
||||
|
||||
/// @brief Default DHCPv6 packet queue buffer implementation
|
||||
class PacketQueueRing6 : public PacketQueueRing<Pkt6Ptr> {
|
||||
public:
|
||||
static const size_t DEFAULT_RING_CAPACITY = 500;
|
||||
|
||||
/// @brief Constructor
|
||||
///
|
||||
/// @param capacity maximum number of packets the queue can hold
|
||||
PacketQueueRing6(size_t capacity=DEFAULT_RING_CAPACITY)
|
||||
: PacketQueueRing(capacity) {
|
||||
};
|
||||
|
||||
/// @brief virtual Destructor
|
||||
virtual ~PacketQueueRing6(){};
|
||||
};
|
||||
|
||||
|
||||
|
||||
}; // namespace isc::dhcp
|
||||
}; // namespace isc
|
||||
|
||||
#endif // PACKET_QUEUE_H
|
66
src/lib/dhcp/socket_info.h
Normal file
66
src/lib/dhcp/socket_info.h
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#ifndef DHCP_SOCKET_INFO_H
|
||||
#define DHCP_SOCKET_INFO_H
|
||||
|
||||
#include <asiolink/io_address.h>
|
||||
|
||||
namespace isc {
|
||||
|
||||
namespace dhcp {
|
||||
|
||||
/// Holds information about socket.
|
||||
struct SocketInfo {
|
||||
|
||||
isc::asiolink::IOAddress addr_; /// bound address
|
||||
uint16_t port_; /// socket port
|
||||
uint16_t family_; /// IPv4 or IPv6
|
||||
|
||||
/// @brief Socket descriptor (a.k.a. primary socket).
|
||||
int sockfd_;
|
||||
|
||||
/// @brief Fallback socket descriptor.
|
||||
///
|
||||
/// This socket descriptor holds the handle to the fallback socket.
|
||||
/// The fallback socket is created when there is a need for the regular
|
||||
/// datagram socket to be bound to an IP address and port, besides
|
||||
/// primary socket (sockfd_) which is actually used to receive and process
|
||||
/// the DHCP messages. The fallback socket (if exists) is always associated
|
||||
/// with the primary socket. In particular, the need for the fallback socket
|
||||
/// arises when raw socket is a primary one. When primary socket is open,
|
||||
/// it is bound to an interface not the address and port. The implications
|
||||
/// include the possibility that the other process (e.g. the other instance
|
||||
/// of DHCP server) will bind to the same address and port through which the
|
||||
/// raw socket receives the DHCP messages.Another implication is that the
|
||||
/// kernel, being unaware of the DHCP server operating through the raw
|
||||
/// socket, will respond with the ICMP "Destination port unreachable"
|
||||
/// messages when DHCP messages are only received through the raw socket.
|
||||
/// In order to workaround the issues mentioned here, the fallback socket
|
||||
/// should be opened so as/ the kernel is aware that the certain address
|
||||
/// and port is in use.
|
||||
///
|
||||
/// The fallback description is supposed to be set to a negative value if
|
||||
/// the fallback socket is closed (not open).
|
||||
int fallbackfd_;
|
||||
|
||||
/// @brief SocketInfo constructor.
|
||||
///
|
||||
/// @param addr An address the socket is bound to.
|
||||
/// @param port A port the socket is bound to.
|
||||
/// @param sockfd Socket descriptor.
|
||||
/// @param fallbackfd A descriptor of the fallback socket.
|
||||
SocketInfo(const isc::asiolink::IOAddress& addr, const uint16_t port,
|
||||
const int sockfd, const int fallbackfd = -1)
|
||||
: addr_(addr), port_(port), family_(addr.getFamily()),
|
||||
sockfd_(sockfd), fallbackfd_(fallbackfd) { }
|
||||
|
||||
};
|
||||
|
||||
}; // namespace isc::dhcp
|
||||
}; // namespace isc
|
||||
|
||||
#endif // DHCP_SOCKET_INFO_H
|
@@ -70,6 +70,8 @@ libdhcp___unittests_SOURCES += option_string_unittest.cc
|
||||
libdhcp___unittests_SOURCES += option_vendor_unittest.cc
|
||||
libdhcp___unittests_SOURCES += option_vendor_class_unittest.cc
|
||||
libdhcp___unittests_SOURCES += pkt_captures4.cc pkt_captures6.cc pkt_captures.h
|
||||
libdhcp___unittests_SOURCES += packet_queue4_unittest.cc
|
||||
libdhcp___unittests_SOURCES += packet_queue6_unittest.cc
|
||||
libdhcp___unittests_SOURCES += pkt4_unittest.cc
|
||||
libdhcp___unittests_SOURCES += pkt6_unittest.cc
|
||||
libdhcp___unittests_SOURCES += pkt4o6_unittest.cc
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@@ -37,6 +37,7 @@ IfaceMgrTestConfig::IfaceMgrTestConfig(const bool default_config) {
|
||||
}
|
||||
|
||||
IfaceMgrTestConfig::~IfaceMgrTestConfig() {
|
||||
IfaceMgr::instance().stopReceiver();
|
||||
IfaceMgr::instance().closeSockets();
|
||||
IfaceMgr::instance().clearIfaces();
|
||||
IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterInet()));
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@@ -138,6 +138,7 @@ public:
|
||||
/// Closes all currently opened sockets, removes current interfaces and
|
||||
/// sets the default packet filtering classes. The default packet filtering
|
||||
/// classes are used for IO operations on real sockets/interfaces.
|
||||
/// Receiver is stopped.
|
||||
///
|
||||
/// Destructor also re-detects real interfaces.
|
||||
~IfaceMgrTestConfig();
|
||||
|
@@ -525,65 +525,6 @@ TEST_F(IfaceMgrTest, instance) {
|
||||
EXPECT_NO_THROW(IfaceMgr::instance());
|
||||
}
|
||||
|
||||
// This test verifies that sockets can be closed selectively, i.e. all
|
||||
// IPv4 sockets can be closed first and all IPv6 sockets remain open.
|
||||
TEST_F(IfaceMgrTest, closeSockets) {
|
||||
// Will be using local loopback addresses for this test.
|
||||
IOAddress loaddr("127.0.0.1");
|
||||
IOAddress loaddr6("::1");
|
||||
|
||||
// Create instance of IfaceMgr.
|
||||
boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr());
|
||||
ASSERT_TRUE(iface_mgr);
|
||||
|
||||
// Out constructor does not detect interfaces by itself. We need
|
||||
// to create one and add.
|
||||
int ifindex = if_nametoindex(LOOPBACK);
|
||||
ASSERT_GT(ifindex, 0);
|
||||
IfacePtr lo_iface(new Iface(LOOPBACK, ifindex));
|
||||
iface_mgr->getIfacesLst().push_back(lo_iface);
|
||||
|
||||
// Create set of V4 and V6 sockets on the loopback interface.
|
||||
// They must differ by a port they are bound to.
|
||||
for (unsigned i = 0; i < 6; ++i) {
|
||||
// Every other socket will be IPv4.
|
||||
if (i % 2) {
|
||||
ASSERT_NO_THROW(
|
||||
iface_mgr->openSocket(LOOPBACK, loaddr, 10000 + i)
|
||||
);
|
||||
} else {
|
||||
ASSERT_NO_THROW(
|
||||
iface_mgr->openSocket(LOOPBACK, loaddr6, 10000 + i)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// At the end we should have 3 IPv4 and 3 IPv6 sockets open.
|
||||
IfacePtr iface = iface_mgr->getIface(LOOPBACK);
|
||||
ASSERT_TRUE(iface != NULL);
|
||||
|
||||
int v4_sockets_count = getOpenSocketsCount(*iface, AF_INET);
|
||||
ASSERT_EQ(3, v4_sockets_count);
|
||||
int v6_sockets_count = getOpenSocketsCount(*iface, AF_INET6);
|
||||
ASSERT_EQ(3, v6_sockets_count);
|
||||
|
||||
// Let's try to close only IPv4 sockets.
|
||||
ASSERT_NO_THROW(iface_mgr->closeSockets(AF_INET));
|
||||
v4_sockets_count = getOpenSocketsCount(*iface, AF_INET);
|
||||
EXPECT_EQ(0, v4_sockets_count);
|
||||
// The IPv6 sockets should remain open.
|
||||
v6_sockets_count = getOpenSocketsCount(*iface, AF_INET6);
|
||||
EXPECT_EQ(3, v6_sockets_count);
|
||||
|
||||
// Let's try to close IPv6 sockets.
|
||||
ASSERT_NO_THROW(iface_mgr->closeSockets(AF_INET6));
|
||||
v4_sockets_count = getOpenSocketsCount(*iface, AF_INET);
|
||||
EXPECT_EQ(0, v4_sockets_count);
|
||||
// They should have been closed now.
|
||||
v6_sockets_count = getOpenSocketsCount(*iface, AF_INET6);
|
||||
EXPECT_EQ(0, v6_sockets_count);
|
||||
}
|
||||
|
||||
TEST_F(IfaceMgrTest, ifaceClass) {
|
||||
// Basic tests for Iface inner class
|
||||
|
||||
@@ -699,6 +640,60 @@ TEST_F(IfaceMgrTest, clearIfaces) {
|
||||
EXPECT_EQ(0, ifacemgr.countIfaces());
|
||||
}
|
||||
|
||||
// Verify that we can manipulate the DHCPv4 packet queue.
|
||||
TEST_F(IfaceMgrTest, packetQueue4) {
|
||||
NakedIfaceMgr ifacemgr;
|
||||
|
||||
// Verify the default packet queue exists and has the default capacity.
|
||||
size_t default_cap = PacketQueueRing4::DEFAULT_RING_CAPACITY;
|
||||
EXPECT_EQ(default_cap, ifacemgr.getPacketQueueCapacity4());
|
||||
|
||||
PacketQueue4Ptr myQueue;
|
||||
// Verify we cannot set the queue to an empty pointer.
|
||||
ASSERT_THROW(ifacemgr.setPacketQueue4(myQueue), BadValue);
|
||||
|
||||
// Verify we can replace the default packet queue with our own.
|
||||
myQueue.reset(new PacketQueueRing4(default_cap + 1));
|
||||
ASSERT_NO_THROW(ifacemgr.setPacketQueue4(myQueue));
|
||||
|
||||
// Verify the new queue has the expected capacity.
|
||||
EXPECT_EQ(default_cap + 1, ifacemgr.getPacketQueueCapacity4());
|
||||
|
||||
// Verify we can't set the capacity to an invalid value.
|
||||
ASSERT_THROW(ifacemgr.setPacketQueueCapacity4(0), BadValue);
|
||||
|
||||
// Verify we can set the capacity to an invalid value.
|
||||
ASSERT_NO_THROW(ifacemgr.setPacketQueueCapacity4(default_cap + 2));
|
||||
EXPECT_EQ(default_cap + 2, ifacemgr.getPacketQueueCapacity4());
|
||||
}
|
||||
|
||||
// Verify that we can manipulate the DHCPv6 packet queue.
|
||||
TEST_F(IfaceMgrTest, packetQueue6) {
|
||||
NakedIfaceMgr ifacemgr;
|
||||
|
||||
// Verify the default packet queue exists and has the default capacity.
|
||||
size_t default_cap = PacketQueueRing6::DEFAULT_RING_CAPACITY;
|
||||
EXPECT_EQ(default_cap, ifacemgr.getPacketQueueCapacity6());
|
||||
|
||||
PacketQueue6Ptr myQueue;
|
||||
// Verify we cannot set the queue to an empty pointer.
|
||||
ASSERT_THROW(ifacemgr.setPacketQueue6(myQueue), BadValue);
|
||||
|
||||
// Verify we can replace the default packet queue with our own.
|
||||
myQueue.reset(new PacketQueueRing6(default_cap + 1));
|
||||
ASSERT_NO_THROW(ifacemgr.setPacketQueue6(myQueue));
|
||||
|
||||
// Verify the new queue has the expected capacity.
|
||||
EXPECT_EQ(default_cap + 1, ifacemgr.getPacketQueueCapacity6());
|
||||
|
||||
// Verify we can't set the capacity to an invalid value.
|
||||
ASSERT_THROW(ifacemgr.setPacketQueueCapacity6(0), BadValue);
|
||||
|
||||
// Verify we can set the capacity to an invalid value.
|
||||
ASSERT_NO_THROW(ifacemgr.setPacketQueueCapacity6(default_cap + 2));
|
||||
EXPECT_EQ(default_cap + 2, ifacemgr.getPacketQueueCapacity6());
|
||||
}
|
||||
|
||||
TEST_F(IfaceMgrTest, receiveTimeout6) {
|
||||
using namespace boost::posix_time;
|
||||
std::cout << "Testing DHCPv6 packet reception timeouts."
|
||||
@@ -714,6 +709,8 @@ TEST_F(IfaceMgrTest, receiveTimeout6) {
|
||||
);
|
||||
// Socket is open if result is non-negative.
|
||||
ASSERT_GE(socket1, 0);
|
||||
// Start receiver.
|
||||
ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET6));
|
||||
|
||||
// Remember when we call receive6().
|
||||
ptime start_time = microsec_clock::universal_time();
|
||||
@@ -749,6 +746,9 @@ TEST_F(IfaceMgrTest, receiveTimeout6) {
|
||||
// Test with invalid fractional timeout values.
|
||||
EXPECT_THROW(ifacemgr->receive6(0, 1000000), isc::BadValue);
|
||||
EXPECT_THROW(ifacemgr->receive6(1, 1000010), isc::BadValue);
|
||||
|
||||
// Stop receiver.
|
||||
EXPECT_NO_THROW(ifacemgr->stopReceiver());
|
||||
}
|
||||
|
||||
TEST_F(IfaceMgrTest, receiveTimeout4) {
|
||||
@@ -766,6 +766,8 @@ TEST_F(IfaceMgrTest, receiveTimeout4) {
|
||||
);
|
||||
// Socket is open if returned value is non-negative.
|
||||
ASSERT_GE(socket1, 0);
|
||||
// Start receiver.
|
||||
ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
|
||||
|
||||
Pkt4Ptr pkt;
|
||||
// Remember when we call receive4().
|
||||
@@ -801,6 +803,9 @@ TEST_F(IfaceMgrTest, receiveTimeout4) {
|
||||
// Test with invalid fractional timeout values.
|
||||
EXPECT_THROW(ifacemgr->receive6(0, 1000000), isc::BadValue);
|
||||
EXPECT_THROW(ifacemgr->receive6(2, 1000005), isc::BadValue);
|
||||
|
||||
// Stop receiver.
|
||||
EXPECT_NO_THROW(ifacemgr->stopReceiver());
|
||||
}
|
||||
|
||||
TEST_F(IfaceMgrTest, multipleSockets) {
|
||||
@@ -1089,6 +1094,7 @@ TEST_F(IfaceMgrTest, sendReceive6) {
|
||||
EXPECT_GE(socket1, 0);
|
||||
EXPECT_GE(socket2, 0);
|
||||
|
||||
ifacemgr->startDHCPReceiver(AF_INET6);
|
||||
|
||||
// prepare dummy payload
|
||||
uint8_t data[128];
|
||||
@@ -1124,6 +1130,8 @@ TEST_F(IfaceMgrTest, sendReceive6) {
|
||||
// assume the one or the other will always be chosen for sending data. Therefore
|
||||
// we should accept both values as source ports.
|
||||
EXPECT_TRUE((rcvPkt->getRemotePort() == 10546) || (rcvPkt->getRemotePort() == 10547));
|
||||
|
||||
ifacemgr->stopReceiver();
|
||||
}
|
||||
|
||||
TEST_F(IfaceMgrTest, sendReceive4) {
|
||||
@@ -1142,6 +1150,8 @@ TEST_F(IfaceMgrTest, sendReceive4) {
|
||||
|
||||
EXPECT_GE(socket1, 0);
|
||||
|
||||
ifacemgr->startDHCPReceiver(AF_INET);
|
||||
|
||||
boost::shared_ptr<Pkt4> sendPkt(new Pkt4(DHCPDISCOVER, 1234) );
|
||||
|
||||
sendPkt->setLocalAddr(IOAddress("127.0.0.1"));
|
||||
@@ -1208,31 +1218,23 @@ TEST_F(IfaceMgrTest, sendReceive4) {
|
||||
// assume the one or the other will always be chosen for sending data. We should
|
||||
// skip checking source port of sent address.
|
||||
|
||||
|
||||
// Close the socket. Further we will test if errors are reported
|
||||
// properly on attempt to use closed socket.
|
||||
close(socket1);
|
||||
|
||||
// Warning: kernel bug on FreeBSD. The following code checks that attempt to
|
||||
// read through invalid descriptor will result in exception. The reason why
|
||||
// this failure is expected is that select() function should result in EBADF
|
||||
// error when invalid descriptor is passed to it. In particular, closed socket
|
||||
// descriptor is invalid. On the following OS:
|
||||
//
|
||||
// 8.1-RELEASE FreeBSD 8.1-RELEASE #0: Mon Jul 19 02:55:53 UTC 2010
|
||||
//
|
||||
// calling select() using invalid descriptor results in timeout and eventually
|
||||
// value of 0 is returned. This has been identified and reported as a bug in
|
||||
// FreeBSD: http://www.freebsd.org/cgi/query-pr.cgi?pr=155606
|
||||
//
|
||||
// @todo: This part of the test is currently disabled on all BSD systems as it was
|
||||
// the quick fix. We need a more elegant (config-based) solution to disable
|
||||
// this check on affected systems only. The ticket has been submitted for this
|
||||
// work: http://kea.isc.org/ticket/2971
|
||||
#ifndef OS_BSD
|
||||
#if 0
|
||||
// @todo Closing the socket does NOT cause a read error out of the
|
||||
// receiveDHCP<X>Packets() select. Apparently this is because the
|
||||
// thread is already inside the select when the socket is closed,
|
||||
// and (at least under Centos 7.5), this does not interrupt the
|
||||
// select.
|
||||
EXPECT_THROW(ifacemgr->receive4(10), SocketReadError);
|
||||
#endif
|
||||
|
||||
EXPECT_THROW(ifacemgr->send(sendPkt), SocketWriteError);
|
||||
|
||||
ifacemgr->stopReceiver();
|
||||
}
|
||||
|
||||
// Verifies that it is possible to set custom packet filter object
|
||||
@@ -1271,8 +1273,8 @@ TEST_F(IfaceMgrTest, setPacketFilter) {
|
||||
EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter),
|
||||
PacketFilterChangeDenied);
|
||||
|
||||
// So, let's close the open IPv4 sockets and retry. Now it should succeed.
|
||||
iface_mgr->closeSockets(AF_INET);
|
||||
// So, let's close the open sockets and retry. Now it should succeed.
|
||||
iface_mgr->closeSockets();
|
||||
EXPECT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter));
|
||||
}
|
||||
|
||||
@@ -1312,8 +1314,8 @@ TEST_F(IfaceMgrTest, setPacketFilter6) {
|
||||
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);
|
||||
// So, let's close the sockets and retry. Now it should succeed.
|
||||
iface_mgr->closeSockets();
|
||||
EXPECT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter));
|
||||
|
||||
}
|
||||
@@ -2528,6 +2530,8 @@ TEST_F(IfaceMgrTest, SingleExternalSocket4) {
|
||||
EXPECT_TRUE(pipe(pipefd) == 0);
|
||||
EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback));
|
||||
|
||||
ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
|
||||
|
||||
Pkt4Ptr pkt4;
|
||||
ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1));
|
||||
|
||||
@@ -2552,6 +2556,8 @@ TEST_F(IfaceMgrTest, SingleExternalSocket4) {
|
||||
// close both pipe ends
|
||||
close(pipefd[1]);
|
||||
close(pipefd[0]);
|
||||
|
||||
ASSERT_NO_THROW(ifacemgr->stopReceiver());
|
||||
}
|
||||
|
||||
// Tests if multiple external sockets and their callbacks can be passed and
|
||||
|
289
src/lib/dhcp/tests/packet_queue4_unittest.cc
Normal file
289
src/lib/dhcp/tests/packet_queue4_unittest.cc
Normal file
@@ -0,0 +1,289 @@
|
||||
// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <asiolink/io_address.h>
|
||||
#include <dhcp/packet_queue.h>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace isc;
|
||||
using namespace isc::dhcp;
|
||||
|
||||
namespace {
|
||||
|
||||
class TestQueue4 : public PacketQueueRing<Pkt4Ptr> {
|
||||
public:
|
||||
|
||||
/// @brief Constructor
|
||||
///
|
||||
/// @param queue_size maximum number of packets the queue can hold
|
||||
TestQueue4(size_t queue_size)
|
||||
: PacketQueueRing(queue_size), drop_enabled_(false), eat_count_(0) {
|
||||
};
|
||||
|
||||
/// @brief virtual Destructor
|
||||
virtual ~TestQueue4(){};
|
||||
|
||||
/// @brief Determines is a packet should be dropped.
|
||||
///
|
||||
/// If drop is enabled and either the packet transaction
|
||||
/// id or the socket source port are even numbers, drop the packet
|
||||
///
|
||||
/// @param packet the packet under consideration
|
||||
/// @param source the socket the packet came from
|
||||
///
|
||||
/// @return True if the packet should be dropped.
|
||||
virtual bool dropPacket(Pkt4Ptr packet,
|
||||
const SocketInfo& source) {
|
||||
if (drop_enabled_) {
|
||||
return ((packet->getTransid() % 2 == 0) ||
|
||||
(source.port_ % 2 == 0));
|
||||
}
|
||||
|
||||
return (false);
|
||||
}
|
||||
|
||||
/// @brief Discards a number of packets from one end of the queue
|
||||
///
|
||||
/// Dequeue and discard eat_count_ packets from the given end of
|
||||
/// the queue_.
|
||||
///
|
||||
/// @param from end of the queue from which packets should discarded
|
||||
///
|
||||
/// @return The number of packets discarded.
|
||||
virtual int eatPackets(const QueueEnd& from) {
|
||||
int eaten = 0;
|
||||
for ( ; eaten < eat_count_; ++eaten) {
|
||||
Pkt4Ptr pkt = popPacket(from);
|
||||
if (!pkt) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (eaten);
|
||||
}
|
||||
|
||||
bool drop_enabled_;
|
||||
int eat_count_;
|
||||
};
|
||||
|
||||
TEST(TestQueue4, interfaceBasics) {
|
||||
PacketQueue4Ptr q4(new TestQueue4(100));
|
||||
ASSERT_TRUE(q4);
|
||||
EXPECT_TRUE(q4->empty());
|
||||
EXPECT_EQ(100, q4->getCapacity());
|
||||
EXPECT_EQ(0, q4->getSize());
|
||||
|
||||
size_t min = TestQueue4::MIN_RING_CAPACITY;
|
||||
ASSERT_THROW(q4->setCapacity(min - 1), BadValue);
|
||||
ASSERT_NO_THROW(q4->setCapacity(min));
|
||||
EXPECT_EQ(min, q4->getCapacity());
|
||||
}
|
||||
|
||||
TEST(TestQueue4, ringTest) {
|
||||
PacketQueue4Ptr q4(new TestQueue4(3));
|
||||
|
||||
EXPECT_EQ(3, q4->getCapacity());
|
||||
// Enqueue five packets. The first two should be pushed off.
|
||||
SocketInfo sock1(isc::asiolink::IOAddress("127.0.0.1"), 777, 10);
|
||||
for (int i = 1; i < 6; ++i) {
|
||||
Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1000+i));
|
||||
ASSERT_NO_THROW(q4->enqueuePacket(pkt, sock1));
|
||||
|
||||
int exp_size = (i > 3 ? 3 : i);
|
||||
EXPECT_EQ(exp_size, q4->getSize());
|
||||
}
|
||||
|
||||
// We should have transids 1005,1004,1003 (back to front)
|
||||
|
||||
// Peek front should be transid 1003.
|
||||
Pkt4Ptr pkt;
|
||||
ASSERT_NO_THROW(pkt = q4->peek(QueueEnd::FRONT));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1003, pkt->getTransid());
|
||||
|
||||
// Peek back should be transid 1005.
|
||||
ASSERT_NO_THROW(pkt = q4->peek(QueueEnd::BACK));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1005, pkt->getTransid());
|
||||
|
||||
// Pop front should return transid 1003.
|
||||
ASSERT_NO_THROW(pkt = q4->popPacket(QueueEnd::FRONT));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1003, pkt->getTransid());
|
||||
|
||||
// Pop back should return transid 1005.
|
||||
ASSERT_NO_THROW(pkt = q4->popPacket(QueueEnd::BACK));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1005, pkt->getTransid());
|
||||
|
||||
// Peek front should be transid 1004.
|
||||
ASSERT_NO_THROW(pkt = q4->peek(QueueEnd::FRONT));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1004, pkt->getTransid());
|
||||
|
||||
// Peek back should be transid 1004.
|
||||
ASSERT_NO_THROW(pkt = q4->peek(QueueEnd::BACK));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1004, pkt->getTransid());
|
||||
|
||||
// Pop front should return transid 1004.
|
||||
ASSERT_NO_THROW(pkt = q4->popPacket(QueueEnd::FRONT));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1004, pkt->getTransid());
|
||||
|
||||
// Pop front should return an empty pointer.
|
||||
ASSERT_NO_THROW(pkt = q4->popPacket(QueueEnd::BACK));
|
||||
ASSERT_FALSE(pkt);
|
||||
|
||||
// Enqueue three packets.
|
||||
for (int i = 1; i < 3; ++i) {
|
||||
Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1000+i));
|
||||
ASSERT_NO_THROW(q4->enqueuePacket(pkt, sock1));
|
||||
EXPECT_EQ(i, q4->getSize());
|
||||
}
|
||||
|
||||
// Let's flush the buffer and then verify it is empty.
|
||||
q4->clear();
|
||||
EXPECT_TRUE(q4->empty());
|
||||
EXPECT_EQ(0, q4->getSize());
|
||||
}
|
||||
|
||||
TEST(TestQueue4, enqueueDequeueTest) {
|
||||
PacketQueue4Ptr q4(new TestQueue4(100));
|
||||
EXPECT_TRUE(q4->empty());
|
||||
|
||||
SocketInfo sock1(isc::asiolink::IOAddress("127.0.0.1"), 777, 10);
|
||||
|
||||
// Enqueue the first packet.
|
||||
Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1002));
|
||||
ASSERT_NO_THROW(q4->enqueuePacket(pkt, sock1));
|
||||
EXPECT_EQ(1, q4->getSize());
|
||||
|
||||
// Enqueue a packet onto the front.
|
||||
pkt.reset(new Pkt4(DHCPDISCOVER, 1003));
|
||||
ASSERT_NO_THROW(q4->enqueuePacket(pkt, sock1, QueueEnd::FRONT));
|
||||
EXPECT_EQ(2, q4->getSize());
|
||||
|
||||
// Enqueue a packet onto the back.
|
||||
pkt.reset(new Pkt4(DHCPDISCOVER, 1001));
|
||||
ASSERT_NO_THROW(q4->enqueuePacket(pkt, sock1, QueueEnd::BACK));
|
||||
EXPECT_EQ(3, q4->getSize());
|
||||
|
||||
// By default we dequeue from the front. We should get transid 1003.
|
||||
ASSERT_NO_THROW(pkt = q4->dequeuePacket());
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1003, pkt->getTransid());
|
||||
|
||||
// Dequeue from the back, we should get transid 1001.
|
||||
ASSERT_NO_THROW(pkt = q4->dequeuePacket(QueueEnd::BACK));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1001, pkt->getTransid());
|
||||
|
||||
// Dequeue from the front, we should get transid 1002.
|
||||
ASSERT_NO_THROW(pkt = q4->dequeuePacket(QueueEnd::FRONT));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1002, pkt->getTransid());
|
||||
|
||||
// Queue should be empty.
|
||||
ASSERT_NO_THROW(pkt = q4->dequeuePacket());
|
||||
ASSERT_FALSE(pkt);
|
||||
}
|
||||
|
||||
TEST(TestQueue4, dropPacketTest) {
|
||||
TestQueue4 q4(100);
|
||||
EXPECT_TRUE(q4.empty());
|
||||
ASSERT_FALSE(q4.drop_enabled_);
|
||||
ASSERT_EQ(0, q4.eat_count_);
|
||||
|
||||
SocketInfo sockEven(isc::asiolink::IOAddress("127.0.0.1"), 888, 10);
|
||||
SocketInfo sockOdd(isc::asiolink::IOAddress("127.0.0.1"), 777, 11);
|
||||
|
||||
// Drop is not enabled.
|
||||
// We should be able to enqueu a packet with even numbered values.
|
||||
Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1002));
|
||||
ASSERT_NO_THROW(q4.enqueuePacket(pkt, sockEven));
|
||||
EXPECT_EQ(1, q4.getSize());
|
||||
|
||||
// We should be able to enqueu a packet with odd numbered values.
|
||||
pkt.reset(new Pkt4(DHCPDISCOVER, 1003));
|
||||
ASSERT_NO_THROW(q4.enqueuePacket(pkt, sockOdd));
|
||||
EXPECT_EQ(2, q4.getSize());
|
||||
|
||||
// Enable drop logic.
|
||||
q4.drop_enabled_ = true;
|
||||
|
||||
// We should not be able to add one with an even-numbered transid.
|
||||
pkt.reset(new Pkt4(DHCPDISCOVER, 1004));
|
||||
ASSERT_NO_THROW(q4.enqueuePacket(pkt, sockOdd));
|
||||
EXPECT_EQ(2, q4.getSize());
|
||||
|
||||
// We should not be able to add one with from even-numbered port.
|
||||
pkt.reset(new Pkt4(DHCPDISCOVER, 1005));
|
||||
ASSERT_NO_THROW(q4.enqueuePacket(pkt, sockEven));
|
||||
EXPECT_EQ(2, q4.getSize());
|
||||
|
||||
// We should be able to add one with an odd-numbered values.
|
||||
pkt.reset(new Pkt4(DHCPDISCOVER, 1007));
|
||||
ASSERT_NO_THROW(q4.enqueuePacket(pkt, sockOdd));
|
||||
EXPECT_EQ(3, q4.getSize());
|
||||
|
||||
// Dequeue them and make sure they are as expected: 1002,1003, and 1007.
|
||||
ASSERT_NO_THROW(pkt = q4.dequeuePacket());
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1002, pkt->getTransid());
|
||||
|
||||
ASSERT_NO_THROW(pkt = q4.dequeuePacket());
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1003, pkt->getTransid());
|
||||
|
||||
ASSERT_NO_THROW(pkt = q4.dequeuePacket());
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1007, pkt->getTransid());
|
||||
|
||||
// Queue should be empty.
|
||||
ASSERT_NO_THROW(pkt = q4.dequeuePacket());
|
||||
ASSERT_FALSE(pkt);
|
||||
}
|
||||
|
||||
TEST(TestQueue4, eatPacketsTest) {
|
||||
TestQueue4 q4(100);
|
||||
EXPECT_TRUE(q4.empty());
|
||||
ASSERT_FALSE(q4.drop_enabled_);
|
||||
ASSERT_EQ(0, q4.eat_count_);
|
||||
|
||||
SocketInfo sock(isc::asiolink::IOAddress("127.0.0.1"), 888, 10);
|
||||
|
||||
Pkt4Ptr pkt;
|
||||
// Let's add five packets.
|
||||
for (int i = 1; i < 6; ++i) {
|
||||
pkt.reset(new Pkt4(DHCPDISCOVER, 1000 + i));
|
||||
ASSERT_NO_THROW(q4.enqueuePacket(pkt, sock));
|
||||
EXPECT_EQ(i, q4.getSize());
|
||||
}
|
||||
|
||||
// Setting eat count to two and dequeuing (from the front, by default),
|
||||
// should discard 1001 and 1002, resulting in a dequeue of 1003.
|
||||
q4.eat_count_ = 2;
|
||||
ASSERT_NO_THROW(pkt = q4.dequeuePacket());
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1003, pkt->getTransid());
|
||||
EXPECT_EQ(2, q4.getSize());
|
||||
|
||||
// Setting eat count to one and dequeing from the back, should discard
|
||||
// 1005 and dequeue 104.
|
||||
q4.eat_count_ = 1;
|
||||
ASSERT_NO_THROW(pkt = q4.dequeuePacket(QueueEnd::BACK));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1004, pkt->getTransid());
|
||||
EXPECT_EQ(0, q4.getSize());
|
||||
}
|
||||
|
||||
} // end of anonymous namespace
|
293
src/lib/dhcp/tests/packet_queue6_unittest.cc
Normal file
293
src/lib/dhcp/tests/packet_queue6_unittest.cc
Normal file
@@ -0,0 +1,293 @@
|
||||
// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <asiolink/io_address.h>
|
||||
#include <dhcp/dhcp6.h>
|
||||
#include <dhcp/packet_queue.h>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
using namespace std;
|
||||
using namespace isc;
|
||||
using namespace isc::dhcp;
|
||||
|
||||
namespace {
|
||||
|
||||
class TestQueue6 : public PacketQueueRing<Pkt6Ptr> {
|
||||
public:
|
||||
|
||||
/// @brief Constructor
|
||||
///
|
||||
/// @param queue_size maximum number of packets the queue can hold
|
||||
TestQueue6(size_t queue_size)
|
||||
: PacketQueueRing(queue_size), drop_enabled_(false), eat_count_(0) {
|
||||
};
|
||||
|
||||
/// @brief virtual Destructor
|
||||
virtual ~TestQueue6(){};
|
||||
|
||||
/// @brief Determines is a packet should be dropped.
|
||||
///
|
||||
/// If drop is enabled and either the packet transaction
|
||||
/// id or the socket source port are even numbers, drop the packet
|
||||
///
|
||||
/// @param packet the packet under consideration
|
||||
/// @param source the socket the packet came from
|
||||
///
|
||||
/// @return True if the packet should be dropped.
|
||||
virtual bool dropPacket(Pkt6Ptr packet,
|
||||
const SocketInfo& source) {
|
||||
if (drop_enabled_) {
|
||||
return ((packet->getTransid() % 2 == 0) ||
|
||||
(source.port_ % 2 == 0));
|
||||
}
|
||||
|
||||
return (false);
|
||||
}
|
||||
|
||||
/// @brief Discards a number of packets from one end of the queue
|
||||
///
|
||||
/// Dequeue and discard eat_count_ packets from the given end of
|
||||
/// the queue_.
|
||||
///
|
||||
/// @param from end of the queue from which packets should discarded
|
||||
///
|
||||
/// @return The number of packets discarded.
|
||||
virtual int eatPackets(const QueueEnd& from) {
|
||||
int eaten = 0;
|
||||
for ( ; eaten < eat_count_; ++eaten) {
|
||||
Pkt6Ptr pkt = popPacket(from);
|
||||
if (!pkt) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (eaten);
|
||||
}
|
||||
|
||||
bool drop_enabled_;
|
||||
int eat_count_;
|
||||
};
|
||||
|
||||
TEST(TestQueue6, interfaceBasics) {
|
||||
PacketQueue6Ptr q6(new TestQueue6(100));
|
||||
ASSERT_TRUE(q6);
|
||||
EXPECT_TRUE(q6->empty());
|
||||
EXPECT_EQ(100, q6->getCapacity());
|
||||
EXPECT_EQ(0, q6->getSize());
|
||||
|
||||
size_t min = TestQueue6::MIN_RING_CAPACITY;
|
||||
ASSERT_THROW(q6->setCapacity(min - 1), BadValue);
|
||||
ASSERT_NO_THROW(q6->setCapacity(min));
|
||||
EXPECT_EQ(min, q6->getCapacity());
|
||||
}
|
||||
|
||||
TEST(TestQueue6, ringTest) {
|
||||
PacketQueue6Ptr q6(new TestQueue6(3));
|
||||
|
||||
EXPECT_EQ(3, q6->getCapacity());
|
||||
// Enqueue five packets. The first two should be pushed off.
|
||||
SocketInfo sock1(isc::asiolink::IOAddress("127.0.0.1"), 777, 10);
|
||||
for (int i = 1; i < 6; ++i) {
|
||||
Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 1000+i));
|
||||
ASSERT_NO_THROW(q6->enqueuePacket(pkt, sock1));
|
||||
|
||||
int exp_size = (i > 3 ? 3 : i);
|
||||
EXPECT_EQ(exp_size, q6->getSize());
|
||||
}
|
||||
|
||||
// We should have transids 1005,1004,1003 (back to front)
|
||||
|
||||
// Peek front should be transid 1003.
|
||||
Pkt6Ptr pkt;
|
||||
ASSERT_NO_THROW(pkt = q6->peek(QueueEnd::FRONT));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1003, pkt->getTransid());
|
||||
|
||||
// Peek back should be transid 1005.
|
||||
ASSERT_NO_THROW(pkt = q6->peek(QueueEnd::BACK));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1005, pkt->getTransid());
|
||||
|
||||
// Pop front should return transid 1003.
|
||||
ASSERT_NO_THROW(pkt = q6->popPacket(QueueEnd::FRONT));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1003, pkt->getTransid());
|
||||
|
||||
// Pop back should return transid 1005.
|
||||
ASSERT_NO_THROW(pkt = q6->popPacket(QueueEnd::BACK));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1005, pkt->getTransid());
|
||||
|
||||
// Peek front should be transid 1004.
|
||||
ASSERT_NO_THROW(pkt = q6->peek(QueueEnd::FRONT));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1004, pkt->getTransid());
|
||||
|
||||
// Peek back should be transid 1004.
|
||||
ASSERT_NO_THROW(pkt = q6->peek(QueueEnd::BACK));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1004, pkt->getTransid());
|
||||
|
||||
// Pop front should return transid 1004.
|
||||
ASSERT_NO_THROW(pkt = q6->popPacket(QueueEnd::FRONT));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1004, pkt->getTransid());
|
||||
|
||||
// Pop front should return an empty pointer.
|
||||
ASSERT_NO_THROW(pkt = q6->popPacket(QueueEnd::BACK));
|
||||
ASSERT_FALSE(pkt);
|
||||
|
||||
// Enqueue three packets.
|
||||
for (int i = 1; i < 3; ++i) {
|
||||
Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 1000+i));
|
||||
ASSERT_NO_THROW(q6->enqueuePacket(pkt, sock1));
|
||||
EXPECT_EQ(i, q6->getSize());
|
||||
}
|
||||
|
||||
// Let's flush the buffer and then verify it is empty.
|
||||
q6->clear();
|
||||
EXPECT_TRUE(q6->empty());
|
||||
EXPECT_EQ(0, q6->getSize());
|
||||
}
|
||||
|
||||
TEST(TestQueue6, enqueueDequeueTest) {
|
||||
PacketQueue6Ptr q6(new TestQueue6(100));
|
||||
EXPECT_TRUE(q6->empty());
|
||||
|
||||
SocketInfo sock1(isc::asiolink::IOAddress("127.0.0.1"), 777, 10);
|
||||
|
||||
// Enqueue the first packet.
|
||||
Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 1002));
|
||||
ASSERT_NO_THROW(q6->enqueuePacket(pkt, sock1));
|
||||
EXPECT_EQ(1, q6->getSize());
|
||||
|
||||
// Enqueue a packet onto the front.
|
||||
pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1003));
|
||||
ASSERT_NO_THROW(q6->enqueuePacket(pkt, sock1, QueueEnd::FRONT));
|
||||
EXPECT_EQ(2, q6->getSize());
|
||||
|
||||
// Enqueue a packet onto the back.
|
||||
pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1001));
|
||||
ASSERT_NO_THROW(q6->enqueuePacket(pkt, sock1, QueueEnd::BACK));
|
||||
EXPECT_EQ(3, q6->getSize());
|
||||
|
||||
// By default we dequeue from the front. We should get transid 1003.
|
||||
ASSERT_NO_THROW(pkt = q6->dequeuePacket());
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1003, pkt->getTransid());
|
||||
|
||||
// Dequeue from the back, we should get transid 1001.
|
||||
ASSERT_NO_THROW(pkt = q6->dequeuePacket(QueueEnd::BACK));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1001, pkt->getTransid());
|
||||
|
||||
// Dequeue from the front, we should get transid 1002.
|
||||
ASSERT_NO_THROW(pkt = q6->dequeuePacket(QueueEnd::FRONT));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1002, pkt->getTransid());
|
||||
|
||||
// Queue should be empty.
|
||||
ASSERT_NO_THROW(pkt = q6->dequeuePacket());
|
||||
ASSERT_FALSE(pkt);
|
||||
}
|
||||
|
||||
TEST(TestQueue6, dropPacketTest) {
|
||||
TestQueue6 q6(100);
|
||||
EXPECT_TRUE(q6.empty());
|
||||
ASSERT_FALSE(q6.drop_enabled_);
|
||||
ASSERT_EQ(0, q6.eat_count_);
|
||||
|
||||
SocketInfo sockEven(isc::asiolink::IOAddress("127.0.0.1"), 888, 10);
|
||||
SocketInfo sockOdd(isc::asiolink::IOAddress("127.0.0.1"), 777, 11);
|
||||
|
||||
// Drop is not enabled.
|
||||
// We should be able to enqueu a packet with even numbered values.
|
||||
Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 1002));
|
||||
ASSERT_NO_THROW(q6.enqueuePacket(pkt, sockEven));
|
||||
EXPECT_EQ(1, q6.getSize());
|
||||
|
||||
// We should be able to enqueu a packet with odd numbered values.
|
||||
pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1003));
|
||||
ASSERT_NO_THROW(q6.enqueuePacket(pkt, sockOdd));
|
||||
EXPECT_EQ(2, q6.getSize());
|
||||
|
||||
// Enable drop logic.
|
||||
q6.drop_enabled_ = true;
|
||||
|
||||
// We should not be able to add one with an even-numbered transid.
|
||||
pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1004));
|
||||
ASSERT_NO_THROW(q6.enqueuePacket(pkt, sockOdd));
|
||||
EXPECT_EQ(2, q6.getSize());
|
||||
|
||||
// We should not be able to add one with from even-numbered port.
|
||||
pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1005));
|
||||
ASSERT_NO_THROW(q6.enqueuePacket(pkt, sockEven));
|
||||
EXPECT_EQ(2, q6.getSize());
|
||||
|
||||
// We should be able to add one with an odd-numbered values.
|
||||
pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1007));
|
||||
ASSERT_NO_THROW(q6.enqueuePacket(pkt, sockOdd));
|
||||
EXPECT_EQ(3, q6.getSize());
|
||||
|
||||
// Dequeue them and make sure they are as expected: 1002,1003, and 1007.
|
||||
ASSERT_NO_THROW(pkt = q6.dequeuePacket());
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1002, pkt->getTransid());
|
||||
|
||||
ASSERT_NO_THROW(pkt = q6.dequeuePacket());
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1003, pkt->getTransid());
|
||||
|
||||
ASSERT_NO_THROW(pkt = q6.dequeuePacket());
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1007, pkt->getTransid());
|
||||
|
||||
// Queue should be empty.
|
||||
ASSERT_NO_THROW(pkt = q6.dequeuePacket());
|
||||
ASSERT_FALSE(pkt);
|
||||
}
|
||||
|
||||
TEST(TestQueue6, eatPacketsTest) {
|
||||
TestQueue6 q6(100);
|
||||
EXPECT_TRUE(q6.empty());
|
||||
ASSERT_FALSE(q6.drop_enabled_);
|
||||
ASSERT_EQ(0, q6.eat_count_);
|
||||
|
||||
SocketInfo sock(isc::asiolink::IOAddress("127.0.0.1"), 888, 10);
|
||||
|
||||
Pkt6Ptr pkt;
|
||||
// Let's add five packets.
|
||||
for (int i = 1; i < 6; ++i) {
|
||||
pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1000 + i));
|
||||
ASSERT_NO_THROW(q6.enqueuePacket(pkt, sock));
|
||||
EXPECT_EQ(i, q6.getSize());
|
||||
}
|
||||
|
||||
// Setting eat count to two and dequeuing (from the front, by default),
|
||||
// should discard 1001 and 1002, resulting in a dequeue of 1003.
|
||||
q6.eat_count_ = 2;
|
||||
ASSERT_NO_THROW(pkt = q6.dequeuePacket());
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1003, pkt->getTransid());
|
||||
EXPECT_EQ(2, q6.getSize());
|
||||
|
||||
// Setting eat count to one and dequeing from the back, should discard
|
||||
// 1005 and dequeue 104.
|
||||
q6.eat_count_ = 1;
|
||||
ASSERT_NO_THROW(pkt = q6.dequeuePacket(QueueEnd::BACK));
|
||||
ASSERT_TRUE(pkt);
|
||||
EXPECT_EQ(1004, pkt->getTransid());
|
||||
EXPECT_EQ(0, q6.getSize());
|
||||
}
|
||||
|
||||
} // end of anonymous namespace
|
@@ -28,6 +28,7 @@ CfgIface::CfgIface()
|
||||
|
||||
void
|
||||
CfgIface::closeSockets() const {
|
||||
IfaceMgr::instance().stopReceiver();
|
||||
IfaceMgr::instance().closeSockets();
|
||||
}
|
||||
|
||||
@@ -172,10 +173,14 @@ CfgIface::openSockets(const uint16_t family, const uint16_t port,
|
||||
// use_bcast is ignored for V6.
|
||||
sopen = IfaceMgr::instance().openSockets6(port, error_callback);
|
||||
}
|
||||
|
||||
// If no socket were opened, log a warning because the server will
|
||||
// not respond to any queries.
|
||||
if (!sopen) {
|
||||
|
||||
if (sopen) {
|
||||
// @todo we may consider starting/stopping this when DHCP service is
|
||||
// enable/disabled, rather then when we open sockets.
|
||||
IfaceMgr::instance().startDHCPReceiver(family);
|
||||
} else {
|
||||
// If no socket were opened, log a warning because the server will
|
||||
// not respond to any queries.
|
||||
LOG_WARN(dhcpsrv_logger, DHCPSRV_NO_SOCKETS_OPEN);
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@@ -157,6 +157,7 @@ public:
|
||||
CfgIface();
|
||||
|
||||
/// @brief Convenience function which closes all open sockets.
|
||||
/// It stops the receiver thread too.
|
||||
void closeSockets() const;
|
||||
|
||||
/// @brief Compares two @c CfgIface objects for equality.
|
||||
@@ -172,6 +173,7 @@ public:
|
||||
/// sockets bound to unicast address. See @c CfgIface::use function
|
||||
/// documentation for details how to specify interfaces and unicast
|
||||
/// addresses to bind the sockets to.
|
||||
/// This function starts the family receiver.
|
||||
///
|
||||
/// @param family Address family (AF_INET or AF_INET6).
|
||||
/// @param port Port number to be used to bind sockets to.
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@@ -47,6 +47,7 @@ IfacesConfigParser::parse(const CfgIfacePtr& cfg,
|
||||
if (re_detect) {
|
||||
// Interface clear will drop opened socket information
|
||||
// so close them if the caller did not.
|
||||
IfaceMgr::instance().stopReceiver();
|
||||
IfaceMgr::instance().closeSockets();
|
||||
IfaceMgr::instance().clearIfaces();
|
||||
IfaceMgr::instance().detectIfaces();
|
||||
|
@@ -20,6 +20,7 @@ public:
|
||||
/// Constructor
|
||||
DatabaseConnectionCallbackTest()
|
||||
: db_reconnect_ctl_(0) {
|
||||
DatabaseConnection::db_lost_callback = 0;
|
||||
}
|
||||
|
||||
/// @brief Callback to register with a DatabaseConnection
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2012-2018 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@@ -97,6 +97,9 @@ private:
|
||||
Impl* impl_;
|
||||
};
|
||||
|
||||
/// @brief Thread pointer type.
|
||||
typedef boost::shared_ptr<Thread> ThreadPtr;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user