#include <config.h>
+#include <asiolink/interval_timer.h>
#include <asiolink/io_service.h>
#include <cc/command_interpreter.h>
#include <config/command_mgr.h>
}
/// @brief Command handler which generates long response
- static ConstElementPtr longResponseHandler(const std::string&,
- const ConstElementPtr&) {
+ static ConstElementPtr longResponseHandler(const std::string&,
+ const ConstElementPtr&) {
ElementPtr arguments = Element::createList();
std::string arg = "responseresponseresponseresponseresponseresponse"
"response";
EXPECT_EQ(reference_response, response.str());
}
+// This test verifies that the server signals timeout if the transmission
+// takes too long.
+TEST_F(CtrlChannelDhcpv4SrvTest, connectionTimeout) {
+ createUnixChannelServer();
+
+ // Server's response will be assigned to this variable.
+ std::string response;
+
+ // It is useful to create a thread and run the server and the client
+ // at the same time and independently.
+ std::thread th([this, &response]() {
+
+ // IO service will be stopped automatically when this object goes
+ // out of scope and is destroyed. This is useful because we use
+ // asserts which may break the thread in various exit points.
+ IOServiceWork work(getIOService());
+
+ // Create the client and connect it to the server.
+ boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
+ ASSERT_TRUE(client);
+ ASSERT_TRUE(client->connectToServer(socket_path_));
+
+ // Send partial command. The server will be waiting for the remaining
+ // part to be sent and will eventually signal a timeout.
+ std::string command = "{ \"command\": \"foo\" ";
+ ASSERT_TRUE(client->sendCommand(command));
+
+ // Let's wait up to 10s for the server's response. The response
+ // should arrive sooner assuming that the timeout mechanism for
+ // the server is working properly.
+ const unsigned int timeout = 10;
+ ASSERT_TRUE(client->getResponse(response, 10));
+
+ // Explicitly close the client's connection.
+ client->disconnectFromServer();
+ });
+
+ // Run the server until stopped.
+ getIOService()->run();
+
+ // Wait for the thread to return.
+ th.join();
+
+ // Check that the server has signalled a timeout.
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"Connection over control channel"
+ " timed out\" }", response);
+}
+
+
} // End of anonymous namespace
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
#include <asiolink/io_service.h>
#include <asiolink/unix_domain_socket.h>
#include <asiolink/unix_domain_socket_acceptor.h>
/// @brief Maximum size of the data chunk sent/received over the socket.
const size_t BUF_SIZE = 8192;
+/// @brief Specifies connection timeout in milliseconds.
+///
+/// @todo Make it configurable.
+const unsigned CONNECTION_TIMEOUT = 5000;
+
class ConnectionPool;
/// @brief Represents a single connection over control socket.
/// for data transmission.
/// @param connection_pool Reference to the connection pool to which this
/// connection belongs.
- Connection(const boost::shared_ptr<UnixDomainSocket>& socket,
+ Connection(const IOServicePtr& io_service,
+ const boost::shared_ptr<UnixDomainSocket>& socket,
ConnectionPool& connection_pool)
- : socket_(socket), buf_(), response_(), connection_pool_(connection_pool),
- feed_(), response_in_progress_(false) {
+ : socket_(socket), timeout_timer_(*io_service), buf_(), response_(),
+ connection_pool_(connection_pool), feed_(),
+ response_in_progress_(false) {
LOG_INFO(command_logger, COMMAND_SOCKET_CONNECTION_OPENED)
.arg(socket_->getNative());
isc::dhcp::IfaceMgr::instance().addExternalSocket(socket_->getNative(), 0);
// Initialize state model for receiving and preparsing commands.
feed_.initModel();
+
+ // Start timer for detecting timeouts.
+ timeout_timer_.setup(boost::bind(&Connection::timeoutHandler, this),
+ CONNECTION_TIMEOUT, IntervalTimer::ONE_SHOT);
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Cancels timeout timer if one is scheduled.
+ ~Connection() {
+ timeout_timer_.cancel();
}
/// @brief Close current connection.
isc::dhcp::IfaceMgr::instance().deleteExternalSocket(socket_->getNative());
socket_->close();
+ timeout_timer_.cancel();
}
}
/// @param bytes_transferred Number of bytes sent.
void sendHandler(const boost::system::error_code& ec,
size_t bytes_trasferred);
+
+ /// @brief Handler invoked when timeout has occurred.
+ void timeoutHandler();
+
private:
/// @brief Pointer to the socket used for transmission.
boost::shared_ptr<UnixDomainSocket> socket_;
+ /// @brief Interval timer used to detect connection timeouts.
+ IntervalTimer timeout_timer_;
+
/// @brief Buffer used for received data.
std::array<char, BUF_SIZE> buf_;
connection_pool_.stop(shared_from_this());
}
+void
+Connection::timeoutHandler() {
+ ConstElementPtr rsp = createAnswer(CONTROL_RESULT_ERROR, "Connection over"
+ " control channel timed out");
+ response_ = rsp->str();
+ doSend();
+}
+
+
}
namespace isc {
acceptor_->asyncAccept(*socket_, [this](const boost::system::error_code& ec) {
if (!ec) {
// New connection is arriving. Start asynchronous transmission.
- ConnectionPtr connection(new Connection(socket_, connection_pool_));
+ ConnectionPtr connection(new Connection(io_service_, socket_,
+ connection_pool_));
connection_pool_.start(connection);
} else if (ec.value() != boost::asio::error::operation_aborted) {