2019-02-25 14:48:22 +01:00
|
|
|
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
2014-05-03 20:02:22 +02:00
|
|
|
|
2015-06-22 11:11:21 +02:00
|
|
|
#include "remote/jsonrpcconnection.hpp"
|
2014-05-25 16:23:35 +02:00
|
|
|
#include "remote/apilistener.hpp"
|
|
|
|
#include "remote/apifunction.hpp"
|
|
|
|
#include "remote/jsonrpc.hpp"
|
2015-08-15 20:28:05 +02:00
|
|
|
#include "base/configtype.hpp"
|
2019-02-19 13:57:36 +01:00
|
|
|
#include "base/io-engine.hpp"
|
2014-05-25 16:23:35 +02:00
|
|
|
#include "base/objectlock.hpp"
|
|
|
|
#include "base/utility.hpp"
|
2014-10-19 14:21:12 +02:00
|
|
|
#include "base/logger.hpp"
|
2014-05-25 16:23:35 +02:00
|
|
|
#include "base/exception.hpp"
|
2016-06-14 08:19:13 +02:00
|
|
|
#include "base/convert.hpp"
|
2019-02-19 13:57:36 +01:00
|
|
|
#include "base/tlsstream.hpp"
|
|
|
|
#include <memory>
|
|
|
|
#include <utility>
|
|
|
|
#include <boost/asio/spawn.hpp>
|
|
|
|
#include <boost/date_time/posix_time/ptime.hpp>
|
2015-02-27 20:18:20 +01:00
|
|
|
#include <boost/thread/once.hpp>
|
2014-05-03 20:02:22 +02:00
|
|
|
|
|
|
|
using namespace icinga;
|
|
|
|
|
2015-08-04 14:47:44 +02:00
|
|
|
static Value SetLogPositionHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
|
2014-05-03 20:02:22 +02:00
|
|
|
REGISTER_APIFUNCTION(SetLogPosition, log, &SetLogPositionHandler);
|
|
|
|
|
2015-09-22 17:58:12 +02:00
|
|
|
JsonRpcConnection::JsonRpcConnection(const String& identity, bool authenticated,
|
2019-02-19 13:57:36 +01:00
|
|
|
const std::shared_ptr<AsioTlsStream>& stream, ConnectionRole role)
|
|
|
|
: m_Identity(identity), m_Authenticated(authenticated), m_Stream(stream),
|
|
|
|
m_Role(role), m_Timestamp(Utility::GetTime()), m_IoStrand(stream->get_io_service()),
|
|
|
|
m_OutgoingMessagesQueued(stream->get_io_service()), m_ReaderHasError(false), m_RunningCoroutines(0)
|
2014-05-08 15:00:09 +02:00
|
|
|
{
|
2014-10-16 09:01:18 +02:00
|
|
|
if (authenticated)
|
|
|
|
m_Endpoint = Endpoint::GetByName(identity);
|
2019-02-19 13:57:36 +01:00
|
|
|
|
|
|
|
m_OutgoingMessagesQueued.expires_at(boost::posix_time::pos_infin);
|
|
|
|
}
|
|
|
|
|
|
|
|
void JsonRpcConnection::Start()
|
|
|
|
{
|
|
|
|
namespace asio = boost::asio;
|
|
|
|
|
|
|
|
m_RunningCoroutines = 2;
|
|
|
|
|
|
|
|
asio::spawn(m_IoStrand, [this](asio::yield_context yc) { HandleIncomingMessages(yc); });
|
|
|
|
asio::spawn(m_IoStrand, [this](asio::yield_context yc) { WriteOutgoingMessages(yc); });
|
2014-05-08 15:00:09 +02:00
|
|
|
}
|
2014-05-03 20:02:22 +02:00
|
|
|
|
2019-02-19 13:57:36 +01:00
|
|
|
void JsonRpcConnection::HandleIncomingMessages(boost::asio::yield_context yc)
|
2015-02-27 20:18:20 +01:00
|
|
|
{
|
2019-02-19 13:57:36 +01:00
|
|
|
Defer shutdownStreamOnce ([this, &yc]() {
|
|
|
|
m_ReaderHasError = true;
|
|
|
|
m_OutgoingMessagesQueued.expires_at(boost::posix_time::neg_infin);
|
2016-01-27 15:45:58 +01:00
|
|
|
|
2019-02-19 13:57:36 +01:00
|
|
|
ShutdownStreamOnce(yc);
|
|
|
|
});
|
2016-06-14 08:19:13 +02:00
|
|
|
|
2019-02-19 13:57:36 +01:00
|
|
|
for (;;) {
|
|
|
|
String message;
|
|
|
|
|
|
|
|
try {
|
|
|
|
message = JsonRpc::ReadMessage(m_Stream, yc, m_Endpoint ? -1 : 1024 * 1024);
|
|
|
|
} catch (const std::exception& ex) {
|
|
|
|
Log(LogWarning, "JsonRpcConnection")
|
|
|
|
<< "Error while reading JSON-RPC message for identity '" << m_Identity
|
|
|
|
<< "': " << DiagnosticInformation(ex);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
CpuBoundWork handleMessage (yc);
|
|
|
|
|
|
|
|
MessageHandler(message);
|
|
|
|
} catch (const std::exception& ex) {
|
|
|
|
Log(LogWarning, "JsonRpcConnection")
|
|
|
|
<< "Error while processing JSON-RPC message for identity '" << m_Identity
|
|
|
|
<< "': " << DiagnosticInformation(ex);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
2016-06-14 08:19:13 +02:00
|
|
|
}
|
2019-02-19 13:57:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void JsonRpcConnection::WriteOutgoingMessages(boost::asio::yield_context yc)
|
|
|
|
{
|
|
|
|
Defer shutdownStreamOnce ([this, &yc]() { ShutdownStreamOnce(yc); });
|
|
|
|
|
|
|
|
do {
|
|
|
|
try {
|
|
|
|
m_OutgoingMessagesQueued.async_wait(yc);
|
|
|
|
} catch (...) {
|
|
|
|
}
|
|
|
|
|
|
|
|
auto queue (std::move(m_OutgoingMessagesQueue));
|
|
|
|
|
|
|
|
m_OutgoingMessagesQueue.clear();
|
|
|
|
m_OutgoingMessagesQueued.expires_at(boost::posix_time::pos_infin);
|
2017-11-21 14:07:44 +01:00
|
|
|
|
2019-02-19 13:57:36 +01:00
|
|
|
if (!queue.empty()) {
|
|
|
|
try {
|
|
|
|
for (auto& message : queue) {
|
|
|
|
size_t bytesSent = JsonRpc::SendMessage(m_Stream, message, yc);
|
|
|
|
|
|
|
|
if (m_Endpoint) {
|
|
|
|
m_Endpoint->AddMessageSent(bytesSent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_Stream->async_flush(yc);
|
|
|
|
} catch (const std::exception& ex) {
|
|
|
|
std::ostringstream info;
|
|
|
|
info << "Error while sending JSON-RPC message for identity '" << m_Identity << "'";
|
|
|
|
Log(LogWarning, "JsonRpcConnection")
|
|
|
|
<< info.str() << "\n" << DiagnosticInformation(ex);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (!m_ReaderHasError);
|
2015-02-27 20:18:20 +01:00
|
|
|
}
|
|
|
|
|
2019-02-19 13:57:36 +01:00
|
|
|
void JsonRpcConnection::ShutdownStreamOnce(boost::asio::yield_context& yc)
|
2014-05-03 20:02:22 +02:00
|
|
|
{
|
2019-02-19 13:57:36 +01:00
|
|
|
if (!--m_RunningCoroutines) {
|
|
|
|
try {
|
|
|
|
m_Stream->next_layer().async_shutdown(yc);
|
|
|
|
} catch (...) {
|
|
|
|
// https://stackoverflow.com/questions/130117/throwing-exceptions-out-of-a-destructor
|
|
|
|
}
|
|
|
|
|
|
|
|
Log(LogWarning, "JsonRpcConnection")
|
|
|
|
<< "API client disconnected for identity '" << m_Identity << "'";
|
|
|
|
|
|
|
|
if (m_Endpoint) {
|
|
|
|
m_Endpoint->RemoveClient(this);
|
|
|
|
} else {
|
|
|
|
auto listener (ApiListener::GetInstance());
|
|
|
|
listener->RemoveAnonymousClient(this);
|
|
|
|
}
|
|
|
|
}
|
2014-05-03 20:02:22 +02:00
|
|
|
}
|
|
|
|
|
2018-01-04 04:25:35 +01:00
|
|
|
double JsonRpcConnection::GetTimestamp() const
|
2016-01-25 10:57:06 +01:00
|
|
|
{
|
|
|
|
return m_Timestamp;
|
|
|
|
}
|
|
|
|
|
2018-01-04 04:25:35 +01:00
|
|
|
String JsonRpcConnection::GetIdentity() const
|
2014-05-08 15:00:09 +02:00
|
|
|
{
|
|
|
|
return m_Identity;
|
|
|
|
}
|
|
|
|
|
2018-01-04 04:25:35 +01:00
|
|
|
bool JsonRpcConnection::IsAuthenticated() const
|
2014-10-16 09:01:18 +02:00
|
|
|
{
|
|
|
|
return m_Authenticated;
|
|
|
|
}
|
|
|
|
|
2018-01-04 04:25:35 +01:00
|
|
|
Endpoint::Ptr JsonRpcConnection::GetEndpoint() const
|
2014-05-03 20:02:22 +02:00
|
|
|
{
|
|
|
|
return m_Endpoint;
|
|
|
|
}
|
|
|
|
|
2019-02-19 13:57:36 +01:00
|
|
|
std::shared_ptr<AsioTlsStream> JsonRpcConnection::GetStream() const
|
2014-05-03 20:02:22 +02:00
|
|
|
{
|
|
|
|
return m_Stream;
|
|
|
|
}
|
|
|
|
|
2018-01-04 04:25:35 +01:00
|
|
|
ConnectionRole JsonRpcConnection::GetRole() const
|
2014-05-03 20:02:22 +02:00
|
|
|
{
|
|
|
|
return m_Role;
|
|
|
|
}
|
|
|
|
|
2015-06-22 11:11:21 +02:00
|
|
|
void JsonRpcConnection::SendMessage(const Dictionary::Ptr& message)
|
2014-05-03 20:02:22 +02:00
|
|
|
{
|
2019-02-19 13:57:36 +01:00
|
|
|
m_IoStrand.post([this, message]() {
|
|
|
|
m_OutgoingMessagesQueue.emplace_back(message);
|
|
|
|
m_OutgoingMessagesQueued.expires_at(boost::posix_time::neg_infin);
|
|
|
|
});
|
2016-01-27 16:43:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void JsonRpcConnection::MessageHandler(const String& jsonString)
|
|
|
|
{
|
|
|
|
Dictionary::Ptr message = JsonRpc::DecodeMessage(jsonString);
|
|
|
|
|
2014-05-08 15:00:09 +02:00
|
|
|
if (m_Endpoint && message->Contains("ts")) {
|
2014-05-03 20:02:22 +02:00
|
|
|
double ts = message->Get("ts");
|
|
|
|
|
|
|
|
/* ignore old messages */
|
|
|
|
if (ts < m_Endpoint->GetRemoteLogPosition())
|
2016-01-27 15:45:58 +01:00
|
|
|
return;
|
2014-05-03 20:02:22 +02:00
|
|
|
|
|
|
|
m_Endpoint->SetRemoteLogPosition(ts);
|
|
|
|
}
|
|
|
|
|
2015-08-04 14:47:44 +02:00
|
|
|
MessageOrigin::Ptr origin = new MessageOrigin();
|
|
|
|
origin->FromClient = this;
|
2014-05-03 20:02:22 +02:00
|
|
|
|
2014-05-08 15:00:09 +02:00
|
|
|
if (m_Endpoint) {
|
|
|
|
if (m_Endpoint->GetZone() != Zone::GetLocalZone())
|
2015-08-04 14:47:44 +02:00
|
|
|
origin->FromZone = m_Endpoint->GetZone();
|
2014-05-08 15:00:09 +02:00
|
|
|
else
|
2015-08-04 14:47:44 +02:00
|
|
|
origin->FromZone = Zone::GetByName(message->Get("originZone"));
|
2017-11-13 16:30:29 +01:00
|
|
|
|
|
|
|
m_Endpoint->AddMessageReceived(jsonString.GetLength());
|
2014-05-08 15:00:09 +02:00
|
|
|
}
|
2014-05-03 20:02:22 +02:00
|
|
|
|
2017-08-29 14:37:13 +02:00
|
|
|
Value vmethod;
|
|
|
|
|
|
|
|
if (!message->Get("method", &vmethod)) {
|
|
|
|
Value vid;
|
|
|
|
|
|
|
|
if (!message->Get("id", &vid))
|
|
|
|
return;
|
|
|
|
|
2017-09-07 10:39:00 +02:00
|
|
|
Log(LogWarning, "JsonRpcConnection",
|
2017-12-19 15:50:05 +01:00
|
|
|
"We received a JSON-RPC response message. This should never happen because we're only ever sending notifications.");
|
2017-08-29 14:37:13 +02:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
String method = vmethod;
|
2014-05-03 20:02:22 +02:00
|
|
|
|
2015-06-22 11:11:21 +02:00
|
|
|
Log(LogNotice, "JsonRpcConnection")
|
2017-12-19 15:50:05 +01:00
|
|
|
<< "Received '" << method << "' message from '" << m_Identity << "'";
|
2014-05-03 20:02:22 +02:00
|
|
|
|
2014-11-08 21:17:16 +01:00
|
|
|
Dictionary::Ptr resultMessage = new Dictionary();
|
2014-05-03 20:02:22 +02:00
|
|
|
|
|
|
|
try {
|
|
|
|
ApiFunction::Ptr afunc = ApiFunction::GetByName(method);
|
|
|
|
|
2017-11-27 12:09:42 +01:00
|
|
|
if (!afunc) {
|
|
|
|
Log(LogNotice, "JsonRpcConnection")
|
2017-12-19 15:50:05 +01:00
|
|
|
<< "Call to non-existent function '" << method << "' from endpoint '" << m_Identity << "'.";
|
2017-11-27 12:09:42 +01:00
|
|
|
} else {
|
2018-01-31 10:17:49 +01:00
|
|
|
Dictionary::Ptr params = message->Get("params");
|
|
|
|
if (params)
|
|
|
|
resultMessage->Set("result", afunc->Invoke(origin, params));
|
|
|
|
else
|
|
|
|
resultMessage->Set("result", Empty);
|
2017-11-27 12:09:42 +01:00
|
|
|
}
|
2014-08-25 08:35:35 +02:00
|
|
|
} catch (const std::exception& ex) {
|
2015-09-22 17:58:12 +02:00
|
|
|
/* TODO: Add a user readable error message for the remote caller */
|
2017-08-30 15:12:24 +02:00
|
|
|
String diagInfo = DiagnosticInformation(ex);
|
|
|
|
resultMessage->Set("error", diagInfo);
|
2015-06-22 11:11:21 +02:00
|
|
|
Log(LogWarning, "JsonRpcConnection")
|
2017-12-19 15:50:05 +01:00
|
|
|
<< "Error while processing message for identity '" << m_Identity << "'\n" << diagInfo;
|
2014-05-03 20:02:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (message->Contains("id")) {
|
|
|
|
resultMessage->Set("jsonrpc", "2.0");
|
|
|
|
resultMessage->Set("id", message->Get("id"));
|
2016-02-01 08:35:55 +01:00
|
|
|
|
2019-02-19 13:57:36 +01:00
|
|
|
m_OutgoingMessagesQueue.emplace_back(resultMessage);
|
|
|
|
m_OutgoingMessagesQueued.expires_at(boost::posix_time::neg_infin);
|
|
|
|
}
|
2014-05-03 20:02:22 +02:00
|
|
|
}
|
|
|
|
|
2015-08-04 14:47:44 +02:00
|
|
|
Value SetLogPositionHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
|
2014-05-03 20:02:22 +02:00
|
|
|
{
|
|
|
|
double log_position = params->Get("log_position");
|
2015-08-04 14:47:44 +02:00
|
|
|
Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
|
2014-05-03 20:02:22 +02:00
|
|
|
|
2014-05-08 15:00:09 +02:00
|
|
|
if (!endpoint)
|
|
|
|
return Empty;
|
|
|
|
|
2014-05-03 20:02:22 +02:00
|
|
|
if (log_position > endpoint->GetLocalLogPosition())
|
|
|
|
endpoint->SetLocalLogPosition(log_position);
|
|
|
|
|
|
|
|
return Empty;
|
|
|
|
}
|
2014-10-16 12:27:09 +02:00
|
|
|
|