return (isc::config::createAnswer(1, err.str()));
}
- // Configure packet queue
+ // Configure DHCP packet queueing
try {
data::ConstElementPtr qc;
qc = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl();
- if (!qc) {
- // @todo For now we're manually constructing default queue config
- // This probably needs to be built into the PQM?
- data::ElementPtr default_qc = data::Element::createMap();
- default_qc->set("queue-type", data::Element::create("kea-ring4"));
- default_qc->set("capacity", data::Element::create(static_cast<long int>(500)));
- PacketQueueMgr4::instance().createPacketQueue(default_qc);
- } else {
- PacketQueueMgr4::instance().createPacketQueue(qc);
+ if (IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, qc)) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CONFIG_PACKET_QUEUE)
+ .arg(PacketQueueMgr4::instance().getPacketQueue()->getInfoStr());
}
- LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CONFIG_PACKET_QUEUE)
- .arg(PacketQueueMgr4::instance().getPacketQueue()->getInfoStr());
-
} catch (const std::exception& ex) {
err << "Error setting packet queue controls after server reconfiguration: "
<< ex.what();
ElementPtr qc = $4;
ctx.stack_.back()->set("dhcp-queue-control", qc);
- if (!qc->contains("queue-type")) {
+ // Doing this manually, because dhcp-queue-control
+ // content is otherwise arbitrary
+ if (!qc->contains("enable-queue")) {
std::stringstream msg;
- msg << "'queue-type' is required: ";
+ msg << "'enable-queue' is required: ";
msg << qc->getPosition().str() << ")";
error(@1, msg.str());
}
+ ConstElementPtr enable_queue = qc->get("enable-queue");
+ if (enable_queue->getType() != Element::boolean) {
+ std::stringstream msg;
+ msg << "'enable-queue' must be boolean: ";
+ msg << qc->getPosition().str() << ")";
+ error(@1, msg.str());
+ }
+
+ if (enable_queue->boolValue()) {
+ if (!qc->contains("queue-type")) {
+ std::stringstream msg;
+ msg << "'queue-type' is required, when 'enable-queue' is true: ";
+ msg << qc->getPosition().str() << ")";
+ error(@1, msg.str());
+ }
+
+ ConstElementPtr queue_type = qc->get("queue-type");
+ if (queue_type->getType() != Element::string) {
+ std::stringstream msg;
+ msg << "'queue-type' must be a string: ";
+ msg << qc->getPosition().str() << ")";
+ error(@1, msg.str());
+ }
+ }
+
ctx.leave();
};
}
if (config_pair.first == "dhcp-queue-control") {
- DHCPQueueControlParser parser(AF_INET);
+ DHCPQueueControlParser parser;
srv_cfg->setDHCPQueueControl(parser.parse(config_pair.second));
continue;
}
""
},
{
- "valid entry",
+ "queue disabled",
"{ \n"
- " \"queue-type\": \"some-type\", \n"
- " \"capacity\": 75 \n"
+ " \"enable-queue\": false \n"
+ "} \n"
+ },
+ {
+ "queue disabled, arbitrary content allowed",
+ "{ \n"
+ " \"enable-queue\": false, \n"
+ " \"foo\": \"bogus\", \n"
+ " \"random-int\" : 1234 \n"
+ "} \n"
+ },
+ {
+ "queue enabled, with queue-type",
+ "{ \n"
+ " \"enable-queue\": true, \n"
+ " \"queue-type\": \"some-type\" \n"
"} \n"
},
{
- "valid arbitrary content",
+ "queue enabled with queue-type and arbitrary content",
"{ \n"
- " \"queue-type\": \"some-type\", \n"
- " \"capacity\": 90, \n"
- " \"user-context\": { \"comment\": \"some text\" },\n"
- " \"random-bool\" : false, \n"
- " \"random-int\" : 1234 \n"
+ " \"enable-queue\": true, \n"
+ " \"queue-type\": \"some-type\", \n"
+ " \"foo\": \"bogus\", \n"
+ " \"random-int\" : 1234 \n"
"} \n"
}
};
control = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl();
ASSERT_FALSE(control);
- // Iterate over the incorrect scenarios and verify they
- // fail as expected. Note, we use parseDHCP4() directly
- // as all of the errors above are enforced by the grammar.
+ // Iterate over the valid scenarios and verify they succeed.
data::ConstElementPtr exp_elems;
for (auto scenario : scenarios) {
SCOPED_TRACE(scenario.description_);
struct Scenario {
std::string description_;
std::string json_;
+ std::string exp_error_;
};
std::vector<Scenario> scenarios = {
{
- "not a map",
- "{ " + genIfaceConfig() + ", \n" +
- " \"subnet4\": [ ], \n"
- " \"dhcp-queue-control\": 75 \n"
- "} \n"
+ "not a map",
+ "75 \n",
+ "<string>:2.24-25: syntax error, unexpected integer, expecting {"
+ },
+ {
+ "enable-queue missing",
+ "{ \n"
+ " \"enable-type\": \"some-type\" \n"
+ "} \n",
+ "<string>:2.2-21: 'enable-queue' is required: <string>:2:24)"
},
{
- "queue type missing",
- "{ " + genIfaceConfig() + ", \n" +
- " \"subnet4\": [ ], \n"
- " \"dhcp-queue-control\": { \n"
- " \"capacity\": 100 \n"
- " } \n"
- "} \n"
+ "enable-queue not boolean",
+ "{ \n"
+ " \"enable-queue\": \"always\" \n"
+ "} \n",
+ "<string>:2.2-21: 'enable-queue' must be boolean: <string>:2:24)"
+ },
+ {
+ "queue enabled, type missing",
+ "{ \n"
+ " \"enable-queue\": true \n"
+ "} \n",
+ "<string>:2.2-21: 'queue-type' is required, when 'enable-queue' is true: <string>:2:24)"
+ },
+ {
+ "queue enabled, type not a string",
+ "{ \n"
+ " \"enable-queue\": true, \n"
+ " \"queue-type\": 7777 \n"
+ "} \n",
+ "<string>:2.2-21: 'queue-type' must be a string: <string>:2:24)"
}
};
for (auto scenario : scenarios) {
SCOPED_TRACE(scenario.description_);
{
- EXPECT_THROW(parseDHCP4(scenario.json_), Dhcp4ParseError);
+ // Construct the config JSON
+ std::stringstream os;
+ os << "{ " + genIfaceConfig();
+ os << ",\n \"dhcp-queue-control\": " << scenario.json_;
+ os << "} \n";
+
+ std::string error_msg = "";
+ try {
+ ASSERT_TRUE(parseDHCP4(os.str(), false)) << "parser returned empty element";
+ } catch(const std::exception& ex) {
+ error_msg = ex.what();
+ }
+
+ ASSERT_FALSE(error_msg.empty()) << "parseDHCP4 should have thrown";
+ EXPECT_EQ(scenario.exp_error_, error_msg);
}
}
}
-
}
return (isc::config::createAnswer(1, err.str()));
}
- // Configure DHCP packet queue
+ // Configure DHCP packet queueing
try {
data::ConstElementPtr qc;
qc = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl();
- if (!qc) {
- // @todo For now we're manually constructing default queue config
- // This probably needs to be built into the PQM?
- data::ElementPtr default_qc = data::Element::createMap();
- default_qc->set("queue-type", data::Element::create("kea-ring6"));
- default_qc->set("capacity", data::Element::create(static_cast<long int>(500)));
- PacketQueueMgr6::instance().createPacketQueue(default_qc);
- } else {
- PacketQueueMgr6::instance().createPacketQueue(qc);
+ if (IfaceMgr::instance().configureDHCPPacketQueue(AF_INET6, qc)) {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CONFIG_PACKET_QUEUE)
+ .arg(PacketQueueMgr6::instance().getPacketQueue()->getInfoStr());
}
- LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CONFIG_PACKET_QUEUE)
- .arg(PacketQueueMgr6::instance().getPacketQueue()->getInfoStr());
-
} catch (const std::exception& ex) {
std::ostringstream err;
- err << "Error setting DHCP packet queue controls after server reconfiguration: "
+ err << "Error setting packet queue controls after server reconfiguration: "
<< ex.what();
return (isc::config::createAnswer(1, err.str()));
}
ElementPtr qc = $4;
ctx.stack_.back()->set("dhcp-queue-control", qc);
- if (!qc->contains("queue-type")) {
+ // Doing this manually, because dhcp-queue-control
+ // content is otherwise arbitrary
+ if (!qc->contains("enable-queue")) {
std::stringstream msg;
- msg << "'queue-type' is required: ";
+ msg << "'enable-queue' is required: ";
msg << qc->getPosition().str() << ")";
error(@1, msg.str());
}
+ ConstElementPtr enable_queue = qc->get("enable-queue");
+ if (enable_queue->getType() != Element::boolean) {
+ std::stringstream msg;
+ msg << "'enable-queue' must be boolean: ";
+ msg << qc->getPosition().str() << ")";
+ error(@1, msg.str());
+ }
+
+ if (enable_queue->boolValue()) {
+ if (!qc->contains("queue-type")) {
+ std::stringstream msg;
+ msg << "'queue-type' is required, when 'enable-queue' is true: ";
+ msg << qc->getPosition().str() << ")";
+ error(@1, msg.str());
+ }
+
+ ConstElementPtr queue_type = qc->get("queue-type");
+ if (queue_type->getType() != Element::string) {
+ std::stringstream msg;
+ msg << "'queue-type' must be a string: ";
+ msg << qc->getPosition().str() << ")";
+ error(@1, msg.str());
+ }
+ }
+
+
ctx.leave();
};
}
if (config_pair.first == "dhcp-queue-control") {
- DHCPQueueControlParser parser(AF_INET);
+ DHCPQueueControlParser parser;
srv_config->setDHCPQueueControl(parser.parse(config_pair.second));
continue;
}
""
},
{
- "valid entry",
+ "queue disabled",
"{ \n"
- " \"queue-type\": \"some-type\", \n"
- " \"capacity\": 75 \n"
+ " \"enable-queue\": false \n"
+ "} \n"
+ },
+ {
+ "queue disabled, arbitrary content allowed",
+ "{ \n"
+ " \"enable-queue\": false, \n"
+ " \"foo\": \"bogus\", \n"
+ " \"random-int\" : 1234 \n"
"} \n"
},
{
- "valid arbitrary content",
+ "queue enabled, with queue-type",
"{ \n"
- " \"queue-type\": \"some-type\", \n"
- " \"capacity\": 90, \n"
- " \"user-context\": { \"comment\": \"some text\" },\n"
- " \"random-bool\" : false, \n"
- " \"random-int\" : 1236 \n"
+ " \"enable-queue\": true, \n"
+ " \"queue-type\": \"some-type\" \n"
+ "} \n"
+ },
+ {
+ "queue enabled with queue-type and arbitrary content",
+ "{ \n"
+ " \"enable-queue\": true, \n"
+ " \"queue-type\": \"some-type\", \n"
+ " \"foo\": \"bogus\", \n"
+ " \"random-int\" : 1234 \n"
"} \n"
}
};
control = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl();
ASSERT_FALSE(control);
- // Iterate over the incorrect scenarios and verify they
- // fail as expected. Note, we use parseDHCP6() directly
- // as all of the errors above are enforced by the grammar.
+ // Iterate over the valid scenarios and verify they succeed.
data::ConstElementPtr exp_elems;
for (auto scenario : scenarios) {
SCOPED_TRACE(scenario.description_);
struct Scenario {
std::string description_;
std::string json_;
+ std::string exp_error_;
};
std::vector<Scenario> scenarios = {
{
- "not a map",
- "{ " + genIfaceConfig() + ", \n" +
- " \"subnet6\": [ ], \n"
- " \"dhcp-queue-control\": 75 \n"
- "} \n"
+ "not a map",
+ "75 \n",
+ "<string>:2.24-25: syntax error, unexpected integer, expecting {"
+ },
+ {
+ "enable-queue missing",
+ "{ \n"
+ " \"enable-type\": \"some-type\" \n"
+ "} \n",
+ "<string>:2.2-21: 'enable-queue' is required: <string>:2:24)"
+ },
+ {
+ "enable-queue not boolean",
+ "{ \n"
+ " \"enable-queue\": \"always\" \n"
+ "} \n",
+ "<string>:2.2-21: 'enable-queue' must be boolean: <string>:2:24)"
},
{
- "queue type missing",
- "{ " + genIfaceConfig() + ", \n" +
- " \"subnet6\": [ ], \n"
- " \"dhcp-queue-control\": { \n"
- " \"capacity\": 100 \n"
- " } \n"
- "} \n"
+ "queue enabled, type missing",
+ "{ \n"
+ " \"enable-queue\": true \n"
+ "} \n",
+ "<string>:2.2-21: 'queue-type' is required, when 'enable-queue' is true: <string>:2:24)"
+ },
+ {
+ "queue enabled, type not a string",
+ "{ \n"
+ " \"enable-queue\": true, \n"
+ " \"queue-type\": 7777 \n"
+ "} \n",
+ "<string>:2.2-21: 'queue-type' must be a string: <string>:2:24)"
}
};
for (auto scenario : scenarios) {
SCOPED_TRACE(scenario.description_);
{
- EXPECT_THROW(parseDHCP6(scenario.json_), Dhcp6ParseError);
+ // Construct the config JSON
+ std::stringstream os;
+ os << "{ " + genIfaceConfig();
+ os << ",\n \"dhcp-queue-control\": " << scenario.json_;
+ os << "} \n";
+
+ std::string error_msg = "";
+ try {
+ ASSERT_TRUE(parseDHCP6(os.str(), false)) << "parser returned empty element";
+ } catch(const std::exception& ex) {
+ error_msg = ex.what();
+ }
+
+ ASSERT_FALSE(error_msg.empty()) << "parseDHCP6 should have thrown";
+ EXPECT_EQ(scenario.exp_error_, error_msg);
}
}
}
<< " specified when requested to close all sockets"
<< " which belong to this family");
}
+
// Search for the socket of the specific type.
SocketCollection::iterator sock = sockets_.begin();
while (sock != sockets_.end()) {
}
void IfaceMgr::closeSockets() {
+ // Stops the receiver thread if there is one.
+ stopDHCPReceiver();
+
BOOST_FOREACH(IfacePtr iface, ifaces_) {
iface->closeSockets();
}
receiver_thread_->wait();
receiver_thread_.reset();
error_watch_.clearReady();
+
}
receiver_error_ = "no error";
- getPacketQueue4()->clear();
- getPacketQueue4()->clear();
+ if (getPacketQueue4()) {
+ getPacketQueue4()->clear();
+ }
+
+ if (getPacketQueue6()) {
+ getPacketQueue6()->clear();
+ }
}
IfaceMgr::~IfaceMgr() {
}
}
+
+ if (count > 0) {
+ // starts the receiver thread (if queueing is enabled);
+ startDHCPReceiver(AF_INET);
+ }
+
return (count > 0);
}
}
}
+
+ if (count > 0) {
+ // starts the receiver thread (if queueing is enabled);
+ startDHCPReceiver(AF_INET6);
+ }
return (count > 0);
}
switch (family) {
case AF_INET:
if(!getPacketQueue4()) {
- isc_throw(Unexpected, "startDHCPRecever - no packet queue?");
+ return;
}
receiver_thread_.reset(new Thread(boost::bind(&IfaceMgr::receiveDHCP4Packets, this)));
break;
case AF_INET6:
if(!getPacketQueue6()) {
- isc_throw(Unexpected, "startDHCPRecever - no packet queue?");
+ return;
}
receiver_thread_.reset(new Thread(boost::bind(&IfaceMgr::receiveDHCP6Packets, this)));
return (packet_filter_->send(*iface, getSocket(*pkt).sockfd_, pkt));
}
-
Pkt4Ptr IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
+ if (receiver_thread_) {
+ return (receive4Indirect(timeout_sec, timeout_usec));
+ }
+
+ return (receive4Direct(timeout_sec, timeout_usec));
+}
+
+Pkt4Ptr IfaceMgr::receive4Indirect(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
// Sanity check for microsecond timeout.
if (timeout_usec >= 1000000) {
isc_throw(BadValue, "fractional timeout must be shorter than"
return (pkt);
}
-Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ ) {
+Pkt4Ptr IfaceMgr::receive4Direct(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
+ // Sanity check for microsecond timeout.
+ if (timeout_usec >= 1000000) {
+ 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_) {
+ FD_SET(s.socket_, &sockets);
+ if (maxfd < s.socket_) {
+ maxfd = s.socket_;
+ }
+ }
+ }
+
+ struct timeval select_timeout;
+ select_timeout.tv_sec = timeout_sec;
+ select_timeout.tv_usec = timeout_usec;
+
+ // zero out the errno to be safe
+ errno = 0;
+
+ int result = select(maxfd + 1, &sockets, NULL, NULL, &select_timeout);
+
+ if (result == 0) {
+ // nothing received and timeout has been reached
+ return (Pkt4Ptr()); // NULL
+
+ } else if (result < 0) {
+ // In most cases we would like to know whether select() returned
+ // an error because of a signal being received or for some other
+ // reason. This is because DHCP servers use signals to trigger
+ // certain actions, like reconfiguration or graceful shutdown.
+ // By catching a dedicated exception the caller will know if the
+ // error returned by the function is due to the reception of the
+ // signal or for some other reason.
+ if (errno == EINTR) {
+ isc_throw(SignalInterruptOnSelect, strerror(errno));
+ } else {
+ isc_throw(SocketReadError, strerror(errno));
+ }
+ }
+
+ // Let's find out which socket has the data
+ BOOST_FOREACH(SocketCallbackInfo s, callbacks_) {
+ if (!FD_ISSET(s.socket_, &sockets)) {
+ continue;
+ }
+
+ // something received over external socket
+
+ // Calling the external socket's callback provides its service
+ // layer access without integrating any specific features
+ // in IfaceMgr
+ if (s.callback_) {
+ s.callback_();
+ }
+
+ 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;
+ }
+ }
+ if (candidate) {
+ break;
+ }
+ }
+
+ if (!candidate) {
+ isc_throw(SocketReadError, "received data over unknown socket");
+ }
+
+ // 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 */) {
+ if (receiver_thread_) {
+ return (receive6Indirect(timeout_sec, timeout_usec));
+ }
+
+ return (receive6Direct(timeout_sec, timeout_usec));
+}
+
+Pkt6Ptr IfaceMgr::receive6Direct(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ ) {
+ // Sanity check for microsecond timeout.
+ if (timeout_usec >= 1000000) {
+ isc_throw(BadValue, "fractional timeout must be shorter than"
+ " 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_) {
+ // Add it to the set as well
+ FD_SET(s.socket_, &sockets);
+ if (maxfd < s.socket_) {
+ maxfd = s.socket_;
+ }
+ }
+ }
+
+ struct timeval select_timeout;
+ select_timeout.tv_sec = timeout_sec;
+ select_timeout.tv_usec = timeout_usec;
+
+ // zero out the errno to be safe
+ errno = 0;
+
+ int result = select(maxfd + 1, &sockets, NULL, NULL, &select_timeout);
+
+ if (result == 0) {
+ // nothing received and timeout has been reached
+ return (Pkt6Ptr()); // NULL
+
+ } else if (result < 0) {
+ // In most cases we would like to know whether select() returned
+ // an error because of a signal being received or for some other
+ // reason. This is because DHCP servers use signals to trigger
+ // certain actions, like reconfiguration or graceful shutdown.
+ // By catching a dedicated exception the caller will know if the
+ // error returned by the function is due to the reception of the
+ // signal or for some other reason.
+ if (errno == EINTR) {
+ isc_throw(SignalInterruptOnSelect, strerror(errno));
+ } else {
+ isc_throw(SocketReadError, strerror(errno));
+ }
+ }
+
+ // Let's find out which socket has the data
+ BOOST_FOREACH(SocketCallbackInfo s, callbacks_) {
+ if (!FD_ISSET(s.socket_, &sockets)) {
+ continue;
+ }
+
+ // something received over external socket
+
+ // Calling the external socket's callback provides its service
+ // layer access without integrating any specific features
+ // in IfaceMgr
+ if (s.callback_) {
+ s.callback_();
+ }
+
+ 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;
+ }
+ }
+ if (candidate) {
+ break;
+ }
+ }
+
+ if (!candidate) {
+ isc_throw(SocketReadError, "received data over unknown socket");
+ }
+ // Assuming that packet filter is not NULL, because its modifier checks it.
+ return (packet_filter6_->receive(*candidate));
+}
+
+
+Pkt6Ptr IfaceMgr::receive6Indirect(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ ) {
// Sanity check for microsecond timeout.
if (timeout_usec >= 1000000) {
isc_throw(BadValue, "fractional timeout must be shorter than"
return (*candidate);
}
+bool
+IfaceMgr::configureDHCPPacketQueue(uint16_t family, data::ConstElementPtr queue_control) {
+ if (receiver_thread_) {
+ isc_throw(InvalidOperation, "Cannot reconfigure queueing"
+ " while receiver thread is running");
+ }
+
+ bool enable_queue = false;
+ if (queue_control) {
+ try {
+ enable_queue = data::SimpleParser::getBoolean(queue_control, "enable-queue");
+ } catch (...) {
+ // @todo - for now swallow not found errors.
+ // if not present we assume default
+ }
+ }
+
+ if (enable_queue) {
+ // Try to create the queue as configured.
+ if (family == AF_INET) {
+ PacketQueueMgr4::instance().createPacketQueue(queue_control);
+ } else {
+ PacketQueueMgr6::instance().createPacketQueue(queue_control);
+ }
+ } else {
+ // Destroy the current queue (if one), this inherently disables threading.
+ if (family == AF_INET) {
+ PacketQueueMgr4::instance().destroyPacketQueue();
+ } else {
+ PacketQueueMgr6::instance().destroyPacketQueue();
+ }
+ }
+
+ return(enable_queue);
+}
+
+
} // end of namespace isc::dhcp
} // end of namespace isc
/// @return Pkt6 object representing received packet (or NULL)
Pkt6Ptr receive6(uint32_t timeout_sec, uint32_t timeout_usec = 0);
+ Pkt6Ptr receive6Direct(uint32_t timeout_sec, uint32_t timeout_usec = 0);
+ Pkt6Ptr receive6Indirect(uint32_t timeout_sec, uint32_t timeout_usec = 0);
+
/// @brief Tries to receive IPv4 packet over open IPv4 sockets.
///
/// Attempts to receive a single DHCPv4 message over any of the open
/// @return Pkt4 object representing received packet (or NULL)
Pkt4Ptr receive4(uint32_t timeout_sec, uint32_t timeout_usec = 0);
+ Pkt4Ptr receive4Direct(uint32_t timeout_sec, uint32_t timeout_usec = 0);
+ Pkt4Ptr receive4Indirect(uint32_t timeout_sec, uint32_t timeout_usec = 0);
+
/// Opens UDP/IP socket and binds it to address, interface and port.
///
/// Specific type of socket (UDP/IPv4 or UDP/IPv6) depends on passed addr
/// but it is not running, it is down, or is a loopback interface when
/// loopback is not allowed, an error is reported.
///
+ /// If sockets were successfully opened, it calls @ startDHCPReceiver to
+ /// start the receiver thread (if packet queueing is enabled).
+ ///
/// On the systems with multiple interfaces, it is often desired that the
/// failure to open a socket on a particular interface doesn't cause a
/// fatal error and sockets should be opened on remaining interfaces.
/// represented by a class derived from @c isc::dhcp::PktFilter abstract
/// class.
///
+ /// If sockets were successfully opened, it calls @ startDHCPReceiver to
+ /// start the receiver thread (if packet queueing is enabled).
+ ///
/// It is possible to specify whether sockets should be broadcast capable.
/// In most of the cases, the sockets should support broadcast traffic, e.g.
/// DHCPv4 server and relay need to listen to broadcast messages sent by
IfaceMgrErrorMsgCallback error_handler = 0);
/// @brief Closes all open sockets.
+ ///
+ /// It calls @c stopDHCPReceiver to stop the receiver thread and then
+ /// it closes all open interface sockets.
+ ///
/// Is used in destructor, but also from Dhcpv4Srv and Dhcpv6Srv classes.
void closeSockets();
/// @brief Starts DHCP packet receiver.
///
/// Starts the DHCP packet receiver thread for the given.
- /// protocol, AF_NET or AF_INET6
+ /// protocol, AF_NET or AF_INET6, if the packet queue
+ /// exists, otherwise it simply returns.
///
/// @param family indicates which receiver to start,
/// (AF_INET or AF_INET6)
/// @brief Stops the DHCP packet receiver.
///
- /// Stops the receiver and deletes the dedicated thread.
+ /// If the thread exists, it is stopped, deleted, and
+ /// the packet queue is flushed.
void stopDHCPReceiver();
+ /// @brief Configures DHCP packet queue
+ ///
+ /// If the given configuration enables packet queueing, then the
+ /// appropriate queue is created. Otherwise, the existing queue is
+ /// destroyed. If the receiver thread is running when this function
+ /// is invoked, it will throw.
+ ///
+ /// @param family indicates which receiver to start,
+ /// (AF_INET or AF_INET6)
+ /// @parm queue_control configuration containing "dhcp-queue-control"
+ /// content
+ /// @return true if packet queueuing has been enabled, false otherwise
+ /// @throw InvalidOperation if the receiver thread is currently running.
+ bool configureDHCPPacketQueue(const uint16_t family,
+ data::ConstElementPtr queue_control);
+
+
// don't use private, we need derived classes in tests
protected:
return (packet_queue_);
}
+ /// @brief Destroys the current packet queue.
+ /// Any queued packets will be discarded.
+ void destroyPacketQueue() {
+ packet_queue_.reset();
+ }
+
protected:
/// @brief A map holding registered backend factory functions.
std::map<std::string, Factory> factories_;
PacketQueue4Ptr queue(new PacketQueueRing4("kea-ring4", capacity));
return (queue);
});
-
- data::ElementPtr parameters = data::Element::createMap();
- parameters->set("queue-type", data::Element::create("kea-ring4"));
- parameters->set("capacity", data::Element::create(static_cast<long int>(500)));
- createPacketQueue(parameters);
}
boost::scoped_ptr<PacketQueueMgr4>&
private:
/// @brief Private constructor.
///
- /// It registers a default factory for DHCPv4 queues and creates
- /// an default DHCPv4 packet queue.
+ /// It registers a default factory for DHCPv4 queues.
PacketQueueMgr4();
/// @brief Returns a pointer to the currently instance of the
PacketQueue6Ptr queue(new PacketQueueRing6("kea-ring6", capacity));
return (queue);
});
-
- data::ElementPtr parameters = data::Element::createMap();
- parameters->set("queue-type", data::Element::create("kea-ring6"));
- parameters->set("capacity", data::Element::create(static_cast<long int>(500)));
- createPacketQueue(parameters);
}
boost::scoped_ptr<PacketQueueMgr6>&
private:
/// @brief Private constructor.
///
- /// It registers a default factory for DHCPv6 queues and creates
- /// an default DHCPv6 packet queue.
+ /// It registers a default factory for DHCPv6 queues.
PacketQueueMgr6();
/// @brief Returns a pointer to the currently used instance of the
TEST_F(IfaceMgrTest, packetQueue4) {
NakedIfaceMgr ifacemgr;
- // Get the default queue.
- PacketQueue4Ptr q4 = ifacemgr.getPacketQueue4();
- ASSERT_TRUE(q4);
+ // Should not have a queue at start up.
+ ASSERT_FALSE(ifacemgr.getPacketQueue4());
- // Verify that the queue is what we expect.
- checkInfo(q4, "{ \"capacity\": 500, \"queue-type\": \"kea-ring4\", \"size\": 0 }");
+ // Verify that we can create a queue with default factory.
+ data::ConstElementPtr config = makeQueueConfig("kea-ring4", 2000);
+ ASSERT_NO_THROW(PacketQueueMgr4::instance().createPacketQueue(config));
+ checkInfo(ifacemgr.getPacketQueue4(),
+ "{ \"capacity\": 2000, \"queue-type\": \"kea-ring4\", \"size\": 0 }");
// Verify that fetching the queue via IfaceMgr and PacketQueueMgr
// returns the same queue.
TEST_F(IfaceMgrTest, packetQueue6) {
NakedIfaceMgr ifacemgr;
- // Get the default queue.
- PacketQueue6Ptr q6 = ifacemgr.getPacketQueue6();
+ // Should not have a queue at start up.
+ ASSERT_FALSE(ifacemgr.getPacketQueue6());
- // Verify that we have a default queue and its info is correct.
- checkInfo(q6, "{ \"capacity\": 500, \"queue-type\": \"kea-ring6\", \"size\": 0 }");
+ // Verify that we can create a queue with default factory.
+ data::ConstElementPtr config = makeQueueConfig("kea-ring6", 2000);
+ ASSERT_NO_THROW(PacketQueueMgr6::instance().createPacketQueue(config));
+ checkInfo(ifacemgr.getPacketQueue6(),
+ "{ \"capacity\": 2000, \"queue-type\": \"kea-ring6\", \"size\": 0 }");
// Verify that fetching the queue via IfaceMgr and PacketQueueMgr
// returns the same queue.
ASSERT_EQ(ifacemgr.getPacketQueue6(), PacketQueueMgr6::instance().getPacketQueue());
}
-
TEST_F(IfaceMgrTest, receiveTimeout6) {
using namespace boost::posix_time;
std::cout << "Testing DHCPv6 packet reception timeouts."
TEST_F(IfaceMgrTest, receiveTimeout4) {
using namespace boost::posix_time;
- std::cout << "Testing DHCPv6 packet reception timeouts."
+ std::cout << "Testing DHCPv4 packet reception timeouts."
<< " Test will block for a few seconds when waiting"
<< " for timeout to occur." << std::endl;
);
// Socket is open if returned value is non-negative.
ASSERT_GE(socket1, 0);
+
// Start receiver.
ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
EXPECT_LE(duration.total_microseconds(), 700000);
// Test with invalid fractional timeout values.
- EXPECT_THROW(ifacemgr->receive6(0, 1000000), isc::BadValue);
- EXPECT_THROW(ifacemgr->receive6(2, 1000005), isc::BadValue);
+ EXPECT_THROW(ifacemgr->receive4(0, 1000000), isc::BadValue);
+ EXPECT_THROW(ifacemgr->receive4(2, 1000005), isc::BadValue);
// Stop receiver.
EXPECT_NO_THROW(ifacemgr->stopDHCPReceiver());
EXPECT_GE(socket1, 0);
+#if 0
ifacemgr->startDHCPReceiver(AF_INET);
+#endif
boost::shared_ptr<Pkt4> sendPkt(new Pkt4(DHCPDISCOVER, 1234) );
EXPECT_THROW(ifacemgr->send(sendPkt), SocketWriteError);
+#if 0
ifacemgr->stopDHCPReceiver();
+#endif
}
// Verifies that it is possible to set custom packet filter object
};
// Verifies that DHCPv4 PQM provides a default queue factory
-// and packet queue.
TEST_F(PacketQueueMgr4Test, defaultQueue) {
+ // Should not be a queue at start-up
+ ASSERT_FALSE(mgr().getPacketQueue());
- // Verify that we have a default queue and its info is correct.
- checkMyInfo("{ \"capacity\": 500, \"queue-type\": \"kea-ring4\", \"size\": 0 }");
-
+ // Verify that we can create a queue with default factory.
data::ConstElementPtr config = makeQueueConfig("kea-ring4", 2000);
-
- // Verify that we can replace the default queue with different capacity queue
ASSERT_NO_THROW(mgr().createPacketQueue(config));
checkMyInfo("{ \"capacity\": 2000, \"queue-type\": \"kea-ring4\", \"size\": 0 }");
// We should be able to recreate the manager.
ASSERT_NO_THROW(PacketQueueMgr4::create());
- // And be back to having the default queue.
- checkMyInfo("{ \"capacity\": 500, \"queue-type\": \"kea-ring4\", \"size\": 0 }");
+ // Should not be a queue.
+ ASSERT_FALSE(mgr().getPacketQueue());
}
// Verifies that PQM registry and creation of custome queue implementations.
// Register our adjustable-type factory
ASSERT_TRUE(addCustomQueueType("custom-queue"));
- // We still have our default queue.
- checkMyInfo("{ \"capacity\": 500, \"queue-type\": \"kea-ring4\", \"size\": 0 }");
-
- // Verify that we can replace the default queue with a "custom-queue" queue
+ // Verify that we can create a custom queue.
ASSERT_NO_THROW(mgr().createPacketQueue(config));
checkMyInfo("{ \"capacity\": 2000, \"queue-type\": \"custom-queue\", \"size\": 0 }");
};
// Verifies that DHCPv6 PQM provides a default queue factory
-// and packet queue.
TEST_F(PacketQueueMgr6Test, defaultQueue) {
+ // Should not be a queue at start-up
+ ASSERT_FALSE(mgr().getPacketQueue());
- // Verify that we have a default queue and its info is correct.
- checkMyInfo("{ \"capacity\": 500, \"queue-type\": \"kea-ring6\", \"size\": 0 }");
-
+ // Verify that we can create a queue with default factory.
data::ConstElementPtr config = makeQueueConfig("kea-ring6", 2000);
-
- // Verify that we can replace the default queue with different capacity queue
ASSERT_NO_THROW(mgr().createPacketQueue(config));
checkMyInfo("{ \"capacity\": 2000, \"queue-type\": \"kea-ring6\", \"size\": 0 }");
// We should be able to recreate the manager.
ASSERT_NO_THROW(PacketQueueMgr6::create());
- // And be back to having the default queue.
- checkMyInfo("{ \"capacity\": 500, \"queue-type\": \"kea-ring6\", \"size\": 0 }");
+ // Should not be a queue.
+ ASSERT_FALSE(mgr().getPacketQueue());
}
// Verifies that PQM registry and creation of custome queue implementations.
// Register our adjustable-type factory
ASSERT_TRUE(addCustomQueueType("custom-queue"));
- // We still have our default queue.
- checkMyInfo("{ \"capacity\": 500, \"queue-type\": \"kea-ring6\", \"size\": 0 }");
-
- // Verify that we can replace the default queue with a "custom-queue" queue
+ // Verify that we can create a custom queue.
ASSERT_NO_THROW(mgr().createPacketQueue(config));
checkMyInfo("{ \"capacity\": 2000, \"queue-type\": \"custom-queue\", \"size\": 0 }");
void
CfgIface::closeSockets() const {
- IfaceMgr::instance().stopDHCPReceiver();
IfaceMgr::instance().closeSockets();
}
sopen = IfaceMgr::instance().openSockets6(port, error_callback);
}
- 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 (!sopen) {
// 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);
namespace isc {
namespace dhcp {
-DHCPQueueControlParser::DHCPQueueControlParser(const uint16_t family)
- : family_(family) {
- // @todo Not sure we need family but just in case.
- if (family_ != AF_INET && family_ != AF_INET6) {
- isc_throw(BadValue, "DHCPQueueControlParser - invalid family: "
- << family_ << ", must be AF_INET or AF_INET6");
- }
-}
-
data::ElementPtr
DHCPQueueControlParser::parse(const isc::data::ConstElementPtr& control_elem) {
// All we really do here is verify that it is a map that
isc_throw(DhcpConfigError, "dhcp-queue-control must be a map");
}
- ConstElementPtr elem = control_elem->get("queue-type");
- if (!elem) {
- isc_throw(DhcpConfigError, "queue-type is required");
- } else {
- if (elem->getType() != Element::string) {
- isc_throw(DhcpConfigError, "queue-type must be a string");
+ // enable-queue is mandatory.
+ bool enable_queue = getBoolean(control_elem, "enable-queue");
+
+ if (enable_queue) {
+ ConstElementPtr elem = control_elem->get("queue-type");
+ if (!elem) {
+ isc_throw(DhcpConfigError, "when queue is enabled, queue-type is required");
+ } else {
+ if (elem->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "queue-type must be a string");
+ }
}
}
///
/// This parser parses the "dhcp-queue-control" parameter which holds the
/// the configurable parameters that tailor DHCP packet queue behavior.
-/// Currently "dhcp-queue-control" is treated as a map of arbitrary values,
-/// with only one required value, "queue-type". This was done to
-/// provide latitude for differing queue implementations.
+/// In order to provide wide latitude to packet queue implementators,
+/// 'dhcp-queue-control' is mostly treated as a map of arbitrary values.
+/// There is only mandatory value, 'enable-queue', which enables/disables
+/// DHCP packet queueing. If this value is true, then the content must
+/// also include a value for 'queue-type'. Beyond these values, the
+/// map may contain any combination of valid JSON elements.
+///
+/// Unlike most other parsers, this parser primarily serves to validate
+/// the aforementioned rules, and rather than instantiate an object as
+/// a result, it simply returns a copy original map of elements.
///
/// This parser is used in both DHCPv4 and DHCPv6. Derived parsers
/// are not needed.
/// @brief Constructor
///
- /// @param family AF_INET for DHCPv4 and AF_INET6 for DHCPv6.
- explicit DHCPQueueControlParser(const uint16_t family);
+ DHCPQueueControlParser(){};
/// @brief Parses content of the "dhcp-queue-control".
///
- /// @param values pointer to the content of parsed values
+ /// @param control_elem MapElement containing the queue control values to
+ /// parse
///
- /// @return A pointer to a newly constructed DHCPQueueControl populated
- /// with the parsed values
+ /// @return A copy of the of the input MapElement.
///
/// @throw DhcpConfigError if any of the values are invalid.
- data::ElementPtr parse(const isc::data::ConstElementPtr& values);
+ data::ElementPtr parse(const isc::data::ConstElementPtr& control_elem);
private:
- /// @brief AF_INET for DHCPv4 and AF_INET6 for DHCPv6.
- int family_;
};
}
libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
libdhcpsrv_unittests_SOURCES += d2_udp_unittest.cc
libdhcpsrv_unittests_SOURCES += database_connection_unittest.cc
+libdhcpsrv_unittests_SOURCES += dhcp_queue_control_parser_unittest.cc
libdhcpsrv_unittests_SOURCES += dhcp4o6_ipc_unittest.cc
libdhcpsrv_unittests_SOURCES += duid_config_parser_unittest.cc
libdhcpsrv_unittests_SOURCES += expiration_config_parser_unittest.cc
--- /dev/null
+// 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 <cc/data.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/parsers/dhcp_queue_control_parser.h>
+#include <testutils/test_to_element.h>
+#include <gtest/gtest.h>
+
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::test;
+
+namespace {
+
+/// @brief Test fixture class for @c DHCPQueueControlParser
+class DHCPQueueControlParserTest : public ::testing::Test {
+protected:
+
+ /// @brief Setup for each test.
+ ///
+ /// Clears the configuration in the @c CfgMgr.
+ virtual void SetUp();
+
+ /// @brief Cleans up after each test.
+ ///
+ /// Clears the configuration in the @c CfgMgr.
+ virtual void TearDown();
+
+};
+
+void
+DHCPQueueControlParserTest::SetUp() {
+ CfgMgr::instance().clear();
+}
+
+void
+DHCPQueueControlParserTest::TearDown() {
+ CfgMgr::instance().clear();
+}
+
+// Verifies that DHCPQueueControlParser handles
+// expected valid dhcp-queue-control contet
+TEST_F(DHCPQueueControlParserTest, validContent) {
+ struct Scenario {
+ std::string description_;
+ std::string json_;
+ };
+
+ std::vector<Scenario> scenarios = {
+ {
+ "queue disabled",
+ "{ \n"
+ " \"enable-queue\": false \n"
+ "} \n"
+ },
+ {
+ "queue disabled, arbitrary content allowed",
+ "{ \n"
+ " \"enable-queue\": false, \n"
+ " \"foo\": \"bogus\", \n"
+ " \"random-int\" : 1234 \n"
+ "} \n"
+ },
+ {
+ "queue enabled, with queue-type",
+ "{ \n"
+ " \"enable-queue\": true, \n"
+ " \"queue-type\": \"some-type\" \n"
+ "} \n"
+ },
+ {
+ "queue enabled with queue-type and arbitrary content",
+ "{ \n"
+ " \"enable-queue\": true, \n"
+ " \"queue-type\": \"some-type\", \n"
+ " \"foo\": \"bogus\", \n"
+ " \"random-int\" : 1234 \n"
+ "} \n"
+ }
+ };
+
+ // Iterate over the valid scenarios and verify they succeed.
+ ConstElementPtr config_elems;
+ ConstElementPtr queue_control;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+ {
+ // Construct the config JSON
+ ASSERT_NO_THROW(config_elems = Element::fromJSON(scenario.json_))
+ << "invalid JSON, test is broken";
+
+ // Parsing config into a queue control should succeed.
+ DHCPQueueControlParser parser;
+ try {
+ queue_control = parser.parse(config_elems);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "parser threw an exception: " << ex.what();
+ }
+
+ // Verify the resultant queue control.
+ ASSERT_TRUE(queue_control);
+
+ // The parser should have created a duplicate of the
+ // configuration elements.
+ ASSERT_TRUE(queue_control.get() != config_elems.get());
+ EXPECT_TRUE(queue_control->equals(*config_elems));
+ }
+ }
+}
+
+// Verifies that DHCPQueueControlParser correctly catches
+// invalid dhcp-queue-control content
+TEST_F(DHCPQueueControlParserTest, invalidContent) {
+ struct Scenario {
+ std::string description_;
+ std::string json_;
+ };
+
+ std::vector<Scenario> scenarios = {
+ {
+ "enable-queue missing",
+ "{ \n"
+ " \"enable-type\": \"some-type\" \n"
+ "} \n"
+ },
+ {
+ "enable-queue not boolean",
+ "{ \n"
+ " \"enable-queue\": \"always\" \n"
+ "} \n"
+ },
+ {
+ "queue enabled, type missing",
+ "{ \n"
+ " \"enable-queue\": true \n"
+ "} \n"
+ },
+ {
+ "queue enabled, type not a string",
+ "{ \n"
+ " \"enable-queue\": true, \n"
+ " \"queue-type\": 7777 \n"
+ "} \n"
+ }
+ };
+
+ // Iterate over the valid scenarios and verify they succeed.
+ ConstElementPtr config_elems;
+ ConstElementPtr queue_control;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+ {
+ // Construct the config JSON
+ ASSERT_NO_THROW(config_elems = Element::fromJSON(scenario.json_))
+ << "invalid JSON, test is broken";
+
+ // Parsing config into a queue control should succeed.
+ DHCPQueueControlParser parser;
+ EXPECT_THROW(parser.parse(config_elems), DhcpConfigError);
+ }
+ }
+}
+
+
+}; // anonymous namespace