From f933aafd299065fec012278acfaf91735c3c7068 Mon Sep 17 00:00:00 2001 From: Michael Friedrich Date: Thu, 23 May 2019 15:25:08 +0200 Subject: [PATCH 01/10] Quality: Purge old HTTP code in lib/remote --- lib/cli/consolecommand.cpp | 3 +- lib/perfdata/elasticsearchwriter.cpp | 2 - lib/perfdata/influxdbwriter.cpp | 2 - lib/remote/CMakeLists.txt | 4 - lib/remote/apiclient.cpp | 164 ----------------- lib/remote/apiclient.hpp | 43 ----- lib/remote/httpclientconnection.cpp | 159 ---------------- lib/remote/httpclientconnection.hpp | 61 ------- lib/remote/httphandler.hpp | 1 - lib/remote/httprequest.cpp | 248 ------------------------- lib/remote/httprequest.hpp | 69 ------- lib/remote/httpresponse.cpp | 259 --------------------------- lib/remote/httpresponse.hpp | 66 ------- lib/remote/httputility.cpp | 76 +------- lib/remote/httputility.hpp | 14 +- 15 files changed, 9 insertions(+), 1162 deletions(-) delete mode 100644 lib/remote/apiclient.cpp delete mode 100644 lib/remote/apiclient.hpp delete mode 100644 lib/remote/httpclientconnection.cpp delete mode 100644 lib/remote/httpclientconnection.hpp delete mode 100644 lib/remote/httprequest.cpp delete mode 100644 lib/remote/httprequest.hpp delete mode 100644 lib/remote/httpresponse.cpp delete mode 100644 lib/remote/httpresponse.hpp diff --git a/lib/cli/consolecommand.cpp b/lib/cli/consolecommand.cpp index 868c66cd4..848b8b07c 100644 --- a/lib/cli/consolecommand.cpp +++ b/lib/cli/consolecommand.cpp @@ -2,7 +2,6 @@ #include "cli/consolecommand.hpp" #include "config/configcompiler.hpp" -#include "remote/apiclient.hpp" #include "remote/consolehandler.hpp" #include "remote/url.hpp" #include "base/configwriter.hpp" @@ -721,4 +720,4 @@ Array::Ptr ConsoleCommand::AutoCompleteScript(const String& session, const Strin } return suggestions; -} \ No newline at end of file +} diff --git a/lib/perfdata/elasticsearchwriter.cpp b/lib/perfdata/elasticsearchwriter.cpp index 8ea4fdef1..f308d3dff 100644 --- a/lib/perfdata/elasticsearchwriter.cpp +++ b/lib/perfdata/elasticsearchwriter.cpp @@ -3,8 +3,6 @@ #include "perfdata/elasticsearchwriter.hpp" #include "perfdata/elasticsearchwriter-ti.cpp" #include "remote/url.hpp" -#include "remote/httprequest.hpp" -#include "remote/httpresponse.hpp" #include "icinga/compatutility.hpp" #include "icinga/service.hpp" #include "icinga/checkcommand.hpp" diff --git a/lib/perfdata/influxdbwriter.cpp b/lib/perfdata/influxdbwriter.cpp index c99164e4a..c5e3edb2f 100644 --- a/lib/perfdata/influxdbwriter.cpp +++ b/lib/perfdata/influxdbwriter.cpp @@ -3,8 +3,6 @@ #include "perfdata/influxdbwriter.hpp" #include "perfdata/influxdbwriter-ti.cpp" #include "remote/url.hpp" -#include "remote/httprequest.hpp" -#include "remote/httpresponse.hpp" #include "icinga/service.hpp" #include "icinga/macroprocessor.hpp" #include "icinga/icingaapplication.hpp" diff --git a/lib/remote/CMakeLists.txt b/lib/remote/CMakeLists.txt index 29c45a29b..bd0884876 100644 --- a/lib/remote/CMakeLists.txt +++ b/lib/remote/CMakeLists.txt @@ -9,7 +9,6 @@ set(remote_SOURCES i2-remote.hpp actionshandler.cpp actionshandler.hpp apiaction.cpp apiaction.hpp - apiclient.cpp apiclient.hpp apifunction.cpp apifunction.hpp apilistener.cpp apilistener.hpp apilistener-ti.hpp apilistener-configsync.cpp apilistener-filesync.cpp apilistener-authority.cpp @@ -27,10 +26,7 @@ set(remote_SOURCES eventshandler.cpp eventshandler.hpp filterutility.cpp filterutility.hpp httpchunkedencoding.cpp httpchunkedencoding.hpp - httpclientconnection.cpp httpclientconnection.hpp httphandler.cpp httphandler.hpp - httprequest.cpp httprequest.hpp - httpresponse.cpp httpresponse.hpp httpserverconnection.cpp httpserverconnection.hpp httputility.cpp httputility.hpp infohandler.cpp infohandler.hpp diff --git a/lib/remote/apiclient.cpp b/lib/remote/apiclient.cpp deleted file mode 100644 index 59d53d04c..000000000 --- a/lib/remote/apiclient.cpp +++ /dev/null @@ -1,164 +0,0 @@ -/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ - -#include "remote/apiclient.hpp" -#include "base/base64.hpp" -#include "base/json.hpp" -#include "base/logger.hpp" -#include "base/exception.hpp" -#include "base/convert.hpp" - -using namespace icinga; - -ApiClient::ApiClient(const String& host, const String& port, - String user, String password) - : m_Connection(new HttpClientConnection(host, port, true)), m_User(std::move(user)), m_Password(std::move(password)) -{ - m_Connection->Start(); -} - -void ApiClient::ExecuteScript(const String& session, const String& command, bool sandboxed, - const ExecuteScriptCompletionCallback& callback) const -{ - Url::Ptr url = new Url(); - url->SetScheme("https"); - url->SetHost(m_Connection->GetHost()); - url->SetPort(m_Connection->GetPort()); - url->SetPath({ "v1", "console", "execute-script" }); - - url->SetQuery({ - {"session", session}, - {"command", command}, - {"sandboxed", sandboxed ? "1" : "0"} - }); - - try { - std::shared_ptr req = m_Connection->NewRequest(); - req->RequestMethod = "POST"; - req->RequestUrl = url; - req->AddHeader("Authorization", "Basic " + Base64::Encode(m_User + ":" + m_Password)); - req->AddHeader("Accept", "application/json"); - m_Connection->SubmitRequest(req, std::bind(ExecuteScriptHttpCompletionCallback, _1, _2, callback)); - } catch (const std::exception&) { - callback(boost::current_exception(), Empty); - } -} - -void ApiClient::ExecuteScriptHttpCompletionCallback(HttpRequest& request, - HttpResponse& response, const ExecuteScriptCompletionCallback& callback) -{ - Dictionary::Ptr result; - - String body; - char buffer[1024]; - size_t count; - - while ((count = response.ReadBody(buffer, sizeof(buffer))) > 0) - body += String(buffer, buffer + count); - - try { - if (response.StatusCode < 200 || response.StatusCode > 299) { - std::string message = "HTTP request failed; Code: " + Convert::ToString(response.StatusCode) + "; Body: " + body; - - BOOST_THROW_EXCEPTION(ScriptError(message)); - } - - result = JsonDecode(body); - - Array::Ptr results = result->Get("results"); - Value result; - String errorMessage = "Unexpected result from API."; - - if (results && results->GetLength() > 0) { - Dictionary::Ptr resultInfo = results->Get(0); - errorMessage = resultInfo->Get("status"); - - if (resultInfo->Get("code") >= 200 && resultInfo->Get("code") <= 299) { - result = resultInfo->Get("result"); - } else { - DebugInfo di; - Dictionary::Ptr debugInfo = resultInfo->Get("debug_info"); - if (debugInfo) { - di.Path = debugInfo->Get("path"); - di.FirstLine = debugInfo->Get("first_line"); - di.FirstColumn = debugInfo->Get("first_column"); - di.LastLine = debugInfo->Get("last_line"); - di.LastColumn = debugInfo->Get("last_column"); - } - bool incompleteExpression = resultInfo->Get("incomplete_expression"); - BOOST_THROW_EXCEPTION(ScriptError(errorMessage, di, incompleteExpression)); - } - } - - callback(boost::exception_ptr(), result); - } catch (const std::exception&) { - callback(boost::current_exception(), Empty); - } -} - -void ApiClient::AutocompleteScript(const String& session, const String& command, bool sandboxed, - const AutocompleteScriptCompletionCallback& callback) const -{ - Url::Ptr url = new Url(); - url->SetScheme("https"); - url->SetHost(m_Connection->GetHost()); - url->SetPort(m_Connection->GetPort()); - url->SetPath({ "v1", "console", "auto-complete-script" }); - - url->SetQuery({ - {"session", session}, - {"command", command}, - {"sandboxed", sandboxed ? "1" : "0"} - }); - - try { - std::shared_ptr req = m_Connection->NewRequest(); - req->RequestMethod = "POST"; - req->RequestUrl = url; - req->AddHeader("Authorization", "Basic " + Base64::Encode(m_User + ":" + m_Password)); - req->AddHeader("Accept", "application/json"); - m_Connection->SubmitRequest(req, std::bind(AutocompleteScriptHttpCompletionCallback, _1, _2, callback)); - } catch (const std::exception&) { - callback(boost::current_exception(), nullptr); - } -} - -void ApiClient::AutocompleteScriptHttpCompletionCallback(HttpRequest& request, - HttpResponse& response, const AutocompleteScriptCompletionCallback& callback) -{ - Dictionary::Ptr result; - - String body; - char buffer[1024]; - size_t count; - - while ((count = response.ReadBody(buffer, sizeof(buffer))) > 0) - body += String(buffer, buffer + count); - - try { - if (response.StatusCode < 200 || response.StatusCode > 299) { - std::string message = "HTTP request failed; Code: " + Convert::ToString(response.StatusCode) + "; Body: " + body; - - BOOST_THROW_EXCEPTION(ScriptError(message)); - } - - result = JsonDecode(body); - - Array::Ptr results = result->Get("results"); - Array::Ptr suggestions; - String errorMessage = "Unexpected result from API."; - - if (results && results->GetLength() > 0) { - Dictionary::Ptr resultInfo = results->Get(0); - errorMessage = resultInfo->Get("status"); - - if (resultInfo->Get("code") >= 200 && resultInfo->Get("code") <= 299) - suggestions = resultInfo->Get("suggestions"); - else - BOOST_THROW_EXCEPTION(ScriptError(errorMessage)); - } - - callback(boost::exception_ptr(), suggestions); - } catch (const std::exception&) { - callback(boost::current_exception(), nullptr); - } -} diff --git a/lib/remote/apiclient.hpp b/lib/remote/apiclient.hpp deleted file mode 100644 index 001f4947b..000000000 --- a/lib/remote/apiclient.hpp +++ /dev/null @@ -1,43 +0,0 @@ -/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ - -#ifndef APICLIENT_H -#define APICLIENT_H - -#include "remote/httpclientconnection.hpp" -#include "base/value.hpp" -#include "base/exception.hpp" -#include - -namespace icinga -{ - -class ApiClient : public Object -{ -public: - DECLARE_PTR_TYPEDEFS(ApiClient); - - ApiClient(const String& host, const String& port, - String user, String password); - - typedef std::function ExecuteScriptCompletionCallback; - void ExecuteScript(const String& session, const String& command, bool sandboxed, - const ExecuteScriptCompletionCallback& callback) const; - - typedef std::function AutocompleteScriptCompletionCallback; - void AutocompleteScript(const String& session, const String& command, bool sandboxed, - const AutocompleteScriptCompletionCallback& callback) const; - -private: - HttpClientConnection::Ptr m_Connection; - String m_User; - String m_Password; - - static void ExecuteScriptHttpCompletionCallback(HttpRequest& request, - HttpResponse& response, const ExecuteScriptCompletionCallback& callback); - static void AutocompleteScriptHttpCompletionCallback(HttpRequest& request, - HttpResponse& response, const AutocompleteScriptCompletionCallback& callback); -}; - -} - -#endif /* APICLIENT_H */ diff --git a/lib/remote/httpclientconnection.cpp b/lib/remote/httpclientconnection.cpp deleted file mode 100644 index 2124b19ee..000000000 --- a/lib/remote/httpclientconnection.cpp +++ /dev/null @@ -1,159 +0,0 @@ -/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ - -#include "remote/httpclientconnection.hpp" -#include "base/configtype.hpp" -#include "base/objectlock.hpp" -#include "base/base64.hpp" -#include "base/utility.hpp" -#include "base/logger.hpp" -#include "base/exception.hpp" -#include "base/convert.hpp" -#include "base/tcpsocket.hpp" -#include "base/tlsstream.hpp" -#include "base/networkstream.hpp" - -using namespace icinga; - -HttpClientConnection::HttpClientConnection(String host, String port, bool tls) - : m_Host(std::move(host)), m_Port(std::move(port)), m_Tls(tls) -{ } - -void HttpClientConnection::Start() -{ - /* Nothing to do here atm. */ -} - -void HttpClientConnection::Reconnect() -{ - if (m_Stream) - m_Stream->Close(); - - m_Context.~StreamReadContext(); - new (&m_Context) StreamReadContext(); - - m_Requests.clear(); - m_CurrentResponse.reset(); - - TcpSocket::Ptr socket = new TcpSocket(); - socket->Connect(m_Host, m_Port); - - if (m_Tls) - m_Stream = new TlsStream(socket, m_Host, RoleClient); - else - ASSERT(!"Non-TLS HTTP connections not supported."); - /* m_Stream = new NetworkStream(socket); - * -- does not currently work because the NetworkStream class doesn't support async I/O - */ - - /* the stream holds an owning reference to this object through the callback we're registering here */ - m_Stream->RegisterDataHandler(std::bind(&HttpClientConnection::DataAvailableHandler, HttpClientConnection::Ptr(this), _1)); - if (m_Stream->IsDataAvailable()) - DataAvailableHandler(m_Stream); -} - -Stream::Ptr HttpClientConnection::GetStream() const -{ - return m_Stream; -} - -String HttpClientConnection::GetHost() const -{ - return m_Host; -} - -String HttpClientConnection::GetPort() const -{ - return m_Port; -} - -bool HttpClientConnection::GetTls() const -{ - return m_Tls; -} - -void HttpClientConnection::Disconnect() -{ - Log(LogDebug, "HttpClientConnection", "Http client disconnected"); - - m_Stream->Shutdown(); -} - -bool HttpClientConnection::ProcessMessage() -{ - bool res; - - if (m_Requests.empty()) { - m_Stream->Close(); - return false; - } - - const std::pair, HttpCompletionCallback>& currentRequest = *m_Requests.begin(); - HttpRequest& request = *currentRequest.first.get(); - const HttpCompletionCallback& callback = currentRequest.second; - - if (!m_CurrentResponse) - m_CurrentResponse = std::make_shared(m_Stream, request); - - std::shared_ptr currentResponse = m_CurrentResponse; - HttpResponse& response = *currentResponse.get(); - - try { - res = response.Parse(m_Context, false); - } catch (const std::exception&) { - callback(request, response); - - m_Stream->Shutdown(); - return false; - } - - if (response.Complete) { - callback(request, response); - - m_Requests.pop_front(); - m_CurrentResponse.reset(); - - return true; - } - - return res; -} - -void HttpClientConnection::DataAvailableHandler(const Stream::Ptr& stream) -{ - ASSERT(stream == m_Stream); - - bool close = false; - - if (!m_Stream->IsEof()) { - boost::mutex::scoped_lock lock(m_DataHandlerMutex); - - try { - while (ProcessMessage()) - ; /* empty loop body */ - } catch (const std::exception& ex) { - Log(LogWarning, "HttpClientConnection") - << "Error while reading Http response: " << DiagnosticInformation(ex); - - close = true; - Disconnect(); - } - } else - close = true; - - if (close) - m_Stream->Close(); -} - -std::shared_ptr HttpClientConnection::NewRequest() -{ - Reconnect(); - return std::make_shared(m_Stream); -} - -void HttpClientConnection::SubmitRequest(const std::shared_ptr& request, - const HttpCompletionCallback& callback) -{ - m_Requests.emplace_back(request, callback); - request->Finish(); -} - diff --git a/lib/remote/httpclientconnection.hpp b/lib/remote/httpclientconnection.hpp deleted file mode 100644 index bff0f51d6..000000000 --- a/lib/remote/httpclientconnection.hpp +++ /dev/null @@ -1,61 +0,0 @@ -/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ - -#ifndef HTTPCLIENTCONNECTION_H -#define HTTPCLIENTCONNECTION_H - -#include "remote/httprequest.hpp" -#include "remote/httpresponse.hpp" -#include "base/stream.hpp" -#include "base/timer.hpp" -#include - -namespace icinga -{ - -/** - * An HTTP client connection. - * - * @ingroup remote - */ -class HttpClientConnection final : public Object -{ -public: - DECLARE_PTR_TYPEDEFS(HttpClientConnection); - - HttpClientConnection(String host, String port, bool tls = true); - - void Start(); - - Stream::Ptr GetStream() const; - String GetHost() const; - String GetPort() const; - bool GetTls() const; - - void Disconnect(); - - std::shared_ptr NewRequest(); - - typedef std::function HttpCompletionCallback; - void SubmitRequest(const std::shared_ptr& request, const HttpCompletionCallback& callback); - -private: - String m_Host; - String m_Port; - bool m_Tls; - Stream::Ptr m_Stream; - std::deque, HttpCompletionCallback> > m_Requests; - std::shared_ptr m_CurrentResponse; - boost::mutex m_DataHandlerMutex; - - StreamReadContext m_Context; - - void Reconnect(); - bool ProcessMessage(); - void DataAvailableHandler(const Stream::Ptr& stream); - - void ProcessMessageAsync(HttpRequest& request); -}; - -} - -#endif /* HTTPCLIENTCONNECTION_H */ diff --git a/lib/remote/httphandler.hpp b/lib/remote/httphandler.hpp index 6c10d0aff..a6a730255 100644 --- a/lib/remote/httphandler.hpp +++ b/lib/remote/httphandler.hpp @@ -5,7 +5,6 @@ #include "remote/i2-remote.hpp" #include "remote/url.hpp" -#include "remote/httpresponse.hpp" #include "remote/httpserverconnection.hpp" #include "remote/apiuser.hpp" #include "base/registry.hpp" diff --git a/lib/remote/httprequest.cpp b/lib/remote/httprequest.cpp deleted file mode 100644 index 6c64dd609..000000000 --- a/lib/remote/httprequest.cpp +++ /dev/null @@ -1,248 +0,0 @@ -/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ - -#include "remote/httprequest.hpp" -#include "base/logger.hpp" -#include "base/application.hpp" -#include "base/convert.hpp" - -using namespace icinga; - -HttpRequest::HttpRequest(Stream::Ptr stream) - : CompleteHeaders(false), - CompleteHeaderCheck(false), - CompleteBody(false), - ProtocolVersion(HttpVersion11), - Headers(new Dictionary()), - m_Stream(std::move(stream)), - m_State(HttpRequestStart) -{ } - -bool HttpRequest::ParseHeaders(StreamReadContext& src, bool may_wait) -{ - if (!m_Stream) - return false; - - if (m_State != HttpRequestStart && m_State != HttpRequestHeaders) - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid HTTP state")); - - String line; - StreamReadStatus srs = m_Stream->ReadLine(&line, src, may_wait); - - if (srs != StatusNewItem) { - if (src.Size > 8 * 1024) - BOOST_THROW_EXCEPTION(std::invalid_argument("Line length for HTTP header exceeded")); - - return false; - } - - if (line.GetLength() > 8 * 1024) { -#ifdef I2_DEBUG /* I2_DEBUG */ - Log(LogDebug, "HttpRequest") - << "Header size: " << line.GetLength() << " content: '" << line << "'."; -#endif /* I2_DEBUG */ - - BOOST_THROW_EXCEPTION(std::invalid_argument("Line length for HTTP header exceeded")); - } - - if (m_State == HttpRequestStart) { - /* ignore trailing new-lines */ - if (line == "") - return true; - - std::vector tokens = line.Split(" "); - Log(LogDebug, "HttpRequest") - << "line: " << line << ", tokens: " << tokens.size(); - if (tokens.size() != 3) - BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request")); - - RequestMethod = tokens[0]; - RequestUrl = new class Url(tokens[1]); - - if (tokens[2] == "HTTP/1.0") - ProtocolVersion = HttpVersion10; - else if (tokens[2] == "HTTP/1.1") { - ProtocolVersion = HttpVersion11; - } else - BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported HTTP version")); - - m_State = HttpRequestHeaders; - return true; - } else { // m_State = HttpRequestHeaders - if (line == "") { - m_State = HttpRequestBody; - CompleteHeaders = true; - return true; - - } else { - if (Headers->GetLength() > 128) - BOOST_THROW_EXCEPTION(std::invalid_argument("Maximum number of HTTP request headers exceeded")); - - String::SizeType pos = line.FindFirstOf(":"); - if (pos == String::NPos) - BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request")); - - String key = line.SubStr(0, pos).ToLower().Trim(); - String value = line.SubStr(pos + 1).Trim(); - Headers->Set(key, value); - - if (key == "x-http-method-override") - RequestMethod = value; - - return true; - } - } -} - -bool HttpRequest::ParseBody(StreamReadContext& src, bool may_wait) -{ - if (!m_Stream) - return false; - - if (m_State != HttpRequestBody) - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid HTTP state")); - - /* we're done if the request doesn't contain a message body */ - if (!Headers->Contains("content-length") && !Headers->Contains("transfer-encoding")) { - CompleteBody = true; - return true; - } else if (!m_Body) - m_Body = new FIFO(); - - if (Headers->Get("transfer-encoding") == "chunked") { - if (!m_ChunkContext) - m_ChunkContext = std::make_shared(std::ref(src)); - - char *data; - size_t size; - StreamReadStatus srs = HttpChunkedEncoding::ReadChunkFromStream(m_Stream, &data, &size, *m_ChunkContext.get(), may_wait); - - if (srs != StatusNewItem) - return false; - - m_Body->Write(data, size); - - delete [] data; - - if (size == 0) { - CompleteBody = true; - } - - return true; - } - - if (src.Eof) - BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body")); - - if (src.MustRead) { - if (!src.FillFromStream(m_Stream, false)) { - src.Eof = true; - BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body")); - } - - src.MustRead = false; - } - - long length_indicator_signed = Convert::ToLong(Headers->Get("content-length")); - - if (length_indicator_signed < 0) - BOOST_THROW_EXCEPTION(std::invalid_argument("Content-Length must not be negative.")); - - size_t length_indicator = length_indicator_signed; - - if (src.Size < length_indicator) { - src.MustRead = true; - return false; - } - - m_Body->Write(src.Buffer, length_indicator); - src.DropData(length_indicator); - CompleteBody = true; - return true; -} - -size_t HttpRequest::ReadBody(char *data, size_t count) -{ - if (!m_Body) - return 0; - else - return m_Body->Read(data, count, true); -} - -void HttpRequest::AddHeader(const String& key, const String& value) -{ - ASSERT(m_State == HttpRequestStart || m_State == HttpRequestHeaders); - Headers->Set(key.ToLower(), value); -} - -void HttpRequest::FinishHeaders() -{ - if (m_State == HttpRequestStart) { - String rqline = RequestMethod + " " + RequestUrl->Format(true) + " HTTP/1." + (ProtocolVersion == HttpVersion10 ? "0" : "1") + "\r\n"; - m_Stream->Write(rqline.CStr(), rqline.GetLength()); - m_State = HttpRequestHeaders; - } - - if (m_State == HttpRequestHeaders) { - AddHeader("User-Agent", "Icinga/" + Application::GetAppVersion()); - - if (ProtocolVersion == HttpVersion11) { - AddHeader("Transfer-Encoding", "chunked"); - if (!Headers->Contains("Host")) - AddHeader("Host", RequestUrl->GetHost() + ":" + RequestUrl->GetPort()); - } - - ObjectLock olock(Headers); - for (const Dictionary::Pair& kv : Headers) - { - String header = kv.first + ": " + kv.second + "\r\n"; - m_Stream->Write(header.CStr(), header.GetLength()); - } - - m_Stream->Write("\r\n", 2); - - m_State = HttpRequestBody; - } -} - -void HttpRequest::WriteBody(const char *data, size_t count) -{ - ASSERT(m_State == HttpRequestStart || m_State == HttpRequestHeaders || m_State == HttpRequestBody); - - if (ProtocolVersion == HttpVersion10) { - if (!m_Body) - m_Body = new FIFO(); - - m_Body->Write(data, count); - } else { - FinishHeaders(); - - HttpChunkedEncoding::WriteChunkToStream(m_Stream, data, count); - } -} - -void HttpRequest::Finish() -{ - ASSERT(m_State != HttpRequestEnd); - - if (ProtocolVersion == HttpVersion10) { - if (m_Body) - AddHeader("Content-Length", Convert::ToString(m_Body->GetAvailableBytes())); - - FinishHeaders(); - - while (m_Body && m_Body->IsDataAvailable()) { - char buffer[1024]; - size_t rc = m_Body->Read(buffer, sizeof(buffer), true); - m_Stream->Write(buffer, rc); - } - } else { - if (m_State == HttpRequestStart || m_State == HttpRequestHeaders) - FinishHeaders(); - - WriteBody(nullptr, 0); - m_Stream->Write("\r\n", 2); - } - - m_State = HttpRequestEnd; -} - diff --git a/lib/remote/httprequest.hpp b/lib/remote/httprequest.hpp deleted file mode 100644 index 1f1a9734b..000000000 --- a/lib/remote/httprequest.hpp +++ /dev/null @@ -1,69 +0,0 @@ -/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ - -#ifndef HTTPREQUEST_H -#define HTTPREQUEST_H - -#include "remote/i2-remote.hpp" -#include "remote/httpchunkedencoding.hpp" -#include "remote/url.hpp" -#include "base/stream.hpp" -#include "base/fifo.hpp" -#include "base/dictionary.hpp" - -namespace icinga -{ - -enum HttpVersion -{ - HttpVersion10, - HttpVersion11 -}; - -enum HttpRequestState -{ - HttpRequestStart, - HttpRequestHeaders, - HttpRequestBody, - HttpRequestEnd -}; - -/** - * An HTTP request. - * - * @ingroup remote - */ -struct HttpRequest -{ -public: - bool CompleteHeaders; - bool CompleteHeaderCheck; - bool CompleteBody; - - String RequestMethod; - Url::Ptr RequestUrl; - HttpVersion ProtocolVersion; - - Dictionary::Ptr Headers; - - HttpRequest(Stream::Ptr stream); - - bool ParseHeaders(StreamReadContext& src, bool may_wait); - bool ParseBody(StreamReadContext& src, bool may_wait); - size_t ReadBody(char *data, size_t count); - - void AddHeader(const String& key, const String& value); - void WriteBody(const char *data, size_t count); - void Finish(); - -private: - Stream::Ptr m_Stream; - std::shared_ptr m_ChunkContext; - HttpRequestState m_State; - FIFO::Ptr m_Body; - - void FinishHeaders(); -}; - -} - -#endif /* HTTPREQUEST_H */ diff --git a/lib/remote/httpresponse.cpp b/lib/remote/httpresponse.cpp deleted file mode 100644 index d6e5d3f66..000000000 --- a/lib/remote/httpresponse.cpp +++ /dev/null @@ -1,259 +0,0 @@ -/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ - -#include "remote/httpresponse.hpp" -#include "remote/httpchunkedencoding.hpp" -#include "base/logger.hpp" -#include "base/application.hpp" -#include "base/convert.hpp" - -using namespace icinga; - -HttpResponse::HttpResponse(Stream::Ptr stream, const HttpRequest& request) - : Complete(false), m_State(HttpResponseStart), m_Request(&request), m_Stream(std::move(stream)) -{ } - -void HttpResponse::SetStatus(int code, const String& message) -{ - ASSERT(code >= 100 && code <= 599); - ASSERT(!message.IsEmpty()); - - if (m_State != HttpResponseStart) { - Log(LogWarning, "HttpResponse", "Tried to set Http response status after headers had already been sent."); - return; - } - - String status = "HTTP/"; - - if (m_Request->ProtocolVersion == HttpVersion10) - status += "1.0"; - else - status += "1.1"; - - status += " " + Convert::ToString(code) + " " + message + "\r\n"; - - m_Stream->Write(status.CStr(), status.GetLength()); - - m_State = HttpResponseHeaders; -} - -void HttpResponse::AddHeader(const String& key, const String& value) -{ - m_Headers.emplace_back(key + ": " + value + "\r\n"); -} - -void HttpResponse::FinishHeaders() -{ - if (m_State == HttpResponseHeaders) { - if (m_Request->ProtocolVersion == HttpVersion11) - AddHeader("Transfer-Encoding", "chunked"); - - AddHeader("Server", "Icinga/" + Application::GetAppVersion()); - - for (const String& header : m_Headers) - m_Stream->Write(header.CStr(), header.GetLength()); - - m_Stream->Write("\r\n", 2); - m_State = HttpResponseBody; - } -} - -void HttpResponse::WriteBody(const char *data, size_t count) -{ - ASSERT(m_State == HttpResponseHeaders || m_State == HttpResponseBody); - - if (m_Request->ProtocolVersion == HttpVersion10) { - if (!m_Body) - m_Body = new FIFO(); - - m_Body->Write(data, count); - } else { - FinishHeaders(); - - HttpChunkedEncoding::WriteChunkToStream(m_Stream, data, count); - } -} - -void HttpResponse::Finish() -{ - ASSERT(m_State != HttpResponseEnd); - - if (m_Request->ProtocolVersion == HttpVersion10) { - if (m_Body) - AddHeader("Content-Length", Convert::ToString(m_Body->GetAvailableBytes())); - - FinishHeaders(); - - while (m_Body && m_Body->IsDataAvailable()) { - char buffer[1024]; - size_t rc = m_Body->Read(buffer, sizeof(buffer), true); - m_Stream->Write(buffer, rc); - } - } else { - WriteBody(nullptr, 0); - m_Stream->Write("\r\n", 2); - } - - m_State = HttpResponseEnd; - - /* Close the connection on - * a) HTTP/1.0 - * b) Connection: close in the sent header. - * - * Do this here and not in DataAvailableHandler - there might still be incoming data in there. - */ - if (m_Request->ProtocolVersion == HttpVersion10 || m_Request->Headers->Get("connection") == "close") - m_Stream->Shutdown(); -} - -bool HttpResponse::Parse(StreamReadContext& src, bool may_wait) -{ - if (m_State != HttpResponseBody) { - String line; - StreamReadStatus srs = m_Stream->ReadLine(&line, src, may_wait); - - if (srs != StatusNewItem) - return false; - - if (m_State == HttpResponseStart) { - /* ignore trailing new-lines */ - if (line == "") - return true; - - std::vector tokens = line.Split(" "); - Log(LogDebug, "HttpRequest") - << "line: " << line << ", tokens: " << tokens.size(); - if (tokens.size() < 2) - BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP response (Status line)")); - - if (tokens[0] == "HTTP/1.0") - ProtocolVersion = HttpVersion10; - else if (tokens[0] == "HTTP/1.1") { - ProtocolVersion = HttpVersion11; - } else - BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported HTTP version")); - - StatusCode = Convert::ToLong(tokens[1]); - - if (tokens.size() >= 3) - StatusMessage = tokens[2]; // TODO: Join tokens[2..end] - - m_State = HttpResponseHeaders; - } else if (m_State == HttpResponseHeaders) { - if (!Headers) - Headers = new Dictionary(); - - if (line == "") { - m_State = HttpResponseBody; - m_Body = new FIFO(); - - return true; - - } else { - String::SizeType pos = line.FindFirstOf(":"); - if (pos == String::NPos) - BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request")); - String key = line.SubStr(0, pos).ToLower().Trim(); - - String value = line.SubStr(pos + 1).Trim(); - Headers->Set(key, value); - } - } else { - VERIFY(!"Invalid HTTP request state."); - } - } else if (m_State == HttpResponseBody) { - if (Headers->Get("transfer-encoding") == "chunked") { - if (!m_ChunkContext) - m_ChunkContext = std::make_shared(std::ref(src)); - - char *data; - size_t size; - StreamReadStatus srs = HttpChunkedEncoding::ReadChunkFromStream(m_Stream, &data, &size, *m_ChunkContext.get(), may_wait); - - if (srs != StatusNewItem) - return false; - - Log(LogNotice, "HttpResponse") - << "Read " << size << " bytes"; - - m_Body->Write(data, size); - - delete[] data; - - if (size == 0) { - Complete = true; - return true; - } - } else { - bool hasLengthIndicator = false; - size_t lengthIndicator = 0; - Value contentLengthHeader; - - if (Headers->Get("content-length", &contentLengthHeader)) { - hasLengthIndicator = true; - lengthIndicator = Convert::ToLong(contentLengthHeader); - } - - if (!hasLengthIndicator && ProtocolVersion != HttpVersion10 && !Headers->Contains("transfer-encoding")) { - Complete = true; - return true; - } - - if (hasLengthIndicator && src.Eof) - BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body")); - - if (src.MustRead) { - if (!src.FillFromStream(m_Stream, may_wait)) - src.Eof = true; - - src.MustRead = false; - } - - if (!hasLengthIndicator) - lengthIndicator = src.Size; - - if (src.Size < lengthIndicator) { - src.MustRead = true; - return may_wait; - } - - m_Body->Write(src.Buffer, lengthIndicator); - src.DropData(lengthIndicator); - - if (!hasLengthIndicator && !src.Eof) { - src.MustRead = true; - return may_wait; - } - - Complete = true; - return true; - } - } - - return true; -} - -size_t HttpResponse::ReadBody(char *data, size_t count) -{ - if (!m_Body) - return 0; - else - return m_Body->Read(data, count, true); -} - -size_t HttpResponse::GetBodySize() const -{ - if (!m_Body) - return 0; - else - return m_Body->GetAvailableBytes(); -} - -bool HttpResponse::IsPeerConnected() const -{ - return !m_Stream->IsEof(); -} - -void HttpResponse::RebindRequest(const HttpRequest& request) -{ - m_Request = &request; -} diff --git a/lib/remote/httpresponse.hpp b/lib/remote/httpresponse.hpp deleted file mode 100644 index 90b25764f..000000000 --- a/lib/remote/httpresponse.hpp +++ /dev/null @@ -1,66 +0,0 @@ -/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ - -#ifndef HTTPRESPONSE_H -#define HTTPRESPONSE_H - -#include "remote/httprequest.hpp" -#include "base/stream.hpp" -#include "base/fifo.hpp" -#include - -namespace icinga -{ - -enum HttpResponseState -{ - HttpResponseStart, - HttpResponseHeaders, - HttpResponseBody, - HttpResponseEnd -}; - -/** - * An HTTP response. - * - * @ingroup remote - */ -struct HttpResponse -{ -public: - bool Complete; - - HttpVersion ProtocolVersion; - int StatusCode; - String StatusMessage; - - Dictionary::Ptr Headers; - - HttpResponse(Stream::Ptr stream, const HttpRequest& request); - - bool Parse(StreamReadContext& src, bool may_wait); - size_t ReadBody(char *data, size_t count); - size_t GetBodySize() const; - - void SetStatus(int code, const String& message); - void AddHeader(const String& key, const String& value); - void WriteBody(const char *data, size_t count); - void Finish(); - - bool IsPeerConnected() const; - - void RebindRequest(const HttpRequest& request); - -private: - HttpResponseState m_State; - std::shared_ptr m_ChunkContext; - const HttpRequest *m_Request; - Stream::Ptr m_Stream; - FIFO::Ptr m_Body; - std::vector m_Headers; - - void FinishHeaders(); -}; - -} - -#endif /* HTTPRESPONSE_H */ diff --git a/lib/remote/httputility.cpp b/lib/remote/httputility.cpp index c97297cd0..91902ba50 100644 --- a/lib/remote/httputility.cpp +++ b/lib/remote/httputility.cpp @@ -37,29 +37,6 @@ Dictionary::Ptr HttpUtility::FetchRequestParameters(const Url::Ptr& url, const s return result; } -void HttpUtility::SendJsonBody(HttpResponse& response, const Dictionary::Ptr& params, const Value& val) -{ - response.AddHeader("Content-Type", "application/json"); - - bool prettyPrint = false; - - if (params) - prettyPrint = GetLastParameter(params, "pretty"); - - String body = JsonEncode(val, prettyPrint); - - response.WriteBody(body.CStr(), body.GetLength()); -} - -void HttpUtility::SendJsonBody(boost::beast::http::response& response, const Dictionary::Ptr& params, const Value& val) -{ - namespace http = boost::beast::http; - - response.set(http::field::content_type, "application/json"); - response.body() = JsonEncode(val, params && GetLastParameter(params, "pretty")); - response.set(http::field::content_length, response.body().size()); -} - Value HttpUtility::GetLastParameter(const Dictionary::Ptr& params, const String& key) { Value varr = params->Get(key); @@ -75,27 +52,13 @@ Value HttpUtility::GetLastParameter(const Dictionary::Ptr& params, const String& return arr->Get(arr->GetLength() - 1); } -void HttpUtility::SendJsonError(HttpResponse& response, const Dictionary::Ptr& params, - int code, const String& info, const String& diagnosticInformation) +void HttpUtility::SendJsonBody(boost::beast::http::response& response, const Dictionary::Ptr& params, const Value& val) { - Dictionary::Ptr result = new Dictionary(); - response.SetStatus(code, HttpUtility::GetErrorNameByCode(code)); - result->Set("error", code); + namespace http = boost::beast::http; - bool verbose = false; - - if (params) - verbose = HttpUtility::GetLastParameter(params, "verbose"); - - if (!info.IsEmpty()) - result->Set("status", info); - - if (verbose) { - if (!diagnosticInformation.IsEmpty()) - result->Set("diagnostic_information", diagnosticInformation); - } - - HttpUtility::SendJsonBody(response, params, result); + response.set(http::field::content_type, "application/json"); + response.body() = JsonEncode(val, params && GetLastParameter(params, "pretty")); + response.set(http::field::content_length, response.body().size()); } void HttpUtility::SendJsonError(boost::beast::http::response& response, @@ -115,32 +78,3 @@ void HttpUtility::SendJsonError(boost::beast::http::response #include +#include namespace icinga { @@ -23,17 +21,11 @@ class HttpUtility public: static Dictionary::Ptr FetchRequestParameters(const Url::Ptr& url, const std::string& body); - static void SendJsonBody(HttpResponse& response, const Dictionary::Ptr& params, const Value& val); - static void SendJsonBody(boost::beast::http::response& response, const Dictionary::Ptr& params, const Value& val); static Value GetLastParameter(const Dictionary::Ptr& params, const String& key); - static void SendJsonError(HttpResponse& response, const Dictionary::Ptr& params, const int code, - const String& verbose = String(), const String& diagnosticInformation = String()); + + static void SendJsonBody(boost::beast::http::response& response, const Dictionary::Ptr& params, const Value& val); static void SendJsonError(boost::beast::http::response& response, const Dictionary::Ptr& params, const int code, const String& verbose = String(), const String& diagnosticInformation = String()); - -private: - static String GetErrorNameByCode(int code); - }; } From e606d14705fb8aa43217ef1441aab7767a08fa2e Mon Sep 17 00:00:00 2001 From: Michael Friedrich Date: Thu, 23 May 2019 17:25:56 +0200 Subject: [PATCH 02/10] Quality: Clean JsonRPC class and add function docs --- lib/remote/jsonrpc.cpp | 81 ++++++++++++++++++++---------------------- lib/remote/jsonrpc.hpp | 4 +-- 2 files changed, 40 insertions(+), 45 deletions(-) diff --git a/lib/remote/jsonrpc.cpp b/lib/remote/jsonrpc.cpp index 63bc5ff85..d17b5780f 100644 --- a/lib/remote/jsonrpc.cpp +++ b/lib/remote/jsonrpc.cpp @@ -15,6 +15,11 @@ using namespace icinga; #ifdef I2_DEBUG +/** + * Determine whether the developer wants to see raw JSON messages. + * + * @return Internal.DebugJsonRpc boolean + */ static bool GetDebugJsonRpcCached() { static int debugJsonRpc = -1; @@ -40,25 +45,6 @@ static bool GetDebugJsonRpcCached() } #endif /* I2_DEBUG */ -/** - * Sends a message to the connected peer and returns the bytes sent. - * - * @param message The message. - * - * @return The amount of bytes sent. - */ -size_t JsonRpc::SendMessage(const Stream::Ptr& stream, const Dictionary::Ptr& message) -{ - String json = JsonEncode(message); - -#ifdef I2_DEBUG - if (GetDebugJsonRpcCached()) - std::cerr << ConsoleColorTag(Console_ForegroundBlue) << ">> " << json << ConsoleColorTag(Console_Normal) << "\n"; -#endif /* I2_DEBUG */ - - return NetString::WriteStringToStream(stream, json); -} - /** * Sends a message to the connected peer and returns the bytes sent. * @@ -90,13 +76,15 @@ size_t JsonRpc::SendMessage(const std::shared_ptr& stream, const return JsonRpc::SendRawMessage(stream, JsonEncode(message), yc); } -/** - * Sends a message to the connected peer and returns the bytes sent. - * - * @param message The message. - * - * @return The amount of bytes sent. - */ + /** + * Sends a raw message to the connected peer. + * + * @param stream ASIO TLS Stream + * @param json message + * @param yc Yield context required for ASIO + * + * @return bytes sent + */ size_t JsonRpc::SendRawMessage(const std::shared_ptr& stream, const String& json, boost::asio::yield_context yc) { #ifdef I2_DEBUG @@ -107,23 +95,14 @@ size_t JsonRpc::SendRawMessage(const std::shared_ptr& stream, con return NetString::WriteStringToStream(stream, json, yc); } -StreamReadStatus JsonRpc::ReadMessage(const Stream::Ptr& stream, String *message, StreamReadContext& src, bool may_wait, ssize_t maxMessageLength) -{ - String jsonString; - StreamReadStatus srs = NetString::ReadStringFromStream(stream, &jsonString, src, may_wait, maxMessageLength); - - if (srs != StatusNewItem) - return srs; - - *message = jsonString; - -#ifdef I2_DEBUG - if (GetDebugJsonRpcCached()) - std::cerr << ConsoleColorTag(Console_ForegroundBlue) << "<< " << jsonString << ConsoleColorTag(Console_Normal) << "\n"; -#endif /* I2_DEBUG */ - - return StatusNewItem; -} +/** + * Reads a message from the connected peer. + * + * @param stream ASIO TLS Stream + * @param maxMessageLength maximum size of bytes read. + * + * @return A JSON string + */ String JsonRpc::ReadMessage(const std::shared_ptr& stream, ssize_t maxMessageLength) { @@ -137,6 +116,15 @@ String JsonRpc::ReadMessage(const std::shared_ptr& stream, ssize_ return std::move(jsonString); } +/** + * Reads a message from the connected peer. + * + * @param stream ASIO TLS Stream + * @param yc Yield Context for ASIO + * @param maxMessageLength maximum size of bytes read. + * + * @return A JSON string + */ String JsonRpc::ReadMessage(const std::shared_ptr& stream, boost::asio::yield_context yc, ssize_t maxMessageLength) { String jsonString = NetString::ReadStringFromStream(stream, yc, maxMessageLength); @@ -149,6 +137,13 @@ String JsonRpc::ReadMessage(const std::shared_ptr& stream, boost: return std::move(jsonString); } +/** + * Decode message, enforce a Dictionary + * + * @param message JSON string + * + * @return Dictionary ptr + */ Dictionary::Ptr JsonRpc::DecodeMessage(const String& message) { Value value = JsonDecode(message); diff --git a/lib/remote/jsonrpc.hpp b/lib/remote/jsonrpc.hpp index 98187fe6c..cc4cc7b6d 100644 --- a/lib/remote/jsonrpc.hpp +++ b/lib/remote/jsonrpc.hpp @@ -21,13 +21,13 @@ namespace icinga class JsonRpc { public: - static size_t SendMessage(const Stream::Ptr& stream, const Dictionary::Ptr& message); static size_t SendMessage(const std::shared_ptr& stream, const Dictionary::Ptr& message); static size_t SendMessage(const std::shared_ptr& stream, const Dictionary::Ptr& message, boost::asio::yield_context yc); static size_t SendRawMessage(const std::shared_ptr& stream, const String& json, boost::asio::yield_context yc); - static StreamReadStatus ReadMessage(const Stream::Ptr& stream, String *message, StreamReadContext& src, bool may_wait = false, ssize_t maxMessageLength = -1); + static String ReadMessage(const std::shared_ptr& stream, ssize_t maxMessageLength = -1); static String ReadMessage(const std::shared_ptr& stream, boost::asio::yield_context yc, ssize_t maxMessageLength = -1); + static Dictionary::Ptr DecodeMessage(const String& message); private: From c7a2fc556c7c94056b7d366b712b9fa77c8a113a Mon Sep 17 00:00:00 2001 From: Michael Friedrich Date: Fri, 24 May 2019 09:19:15 +0200 Subject: [PATCH 03/10] Quality: Purge old TlsStream functionality --- lib/base/tlsstream.cpp | 440 +---------------------------------------- lib/base/tlsstream.hpp | 80 -------- 2 files changed, 1 insertion(+), 519 deletions(-) diff --git a/lib/base/tlsstream.cpp b/lib/base/tlsstream.cpp index 210d88002..b72a88030 100644 --- a/lib/base/tlsstream.cpp +++ b/lib/base/tlsstream.cpp @@ -1,7 +1,7 @@ /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ -#include "base/application.hpp" #include "base/tlsstream.hpp" +#include "base/application.hpp" #include "base/utility.hpp" #include "base/exception.hpp" #include "base/logger.hpp" @@ -16,446 +16,8 @@ #include #include -#ifndef _WIN32 -# include -#endif /* _WIN32 */ - -#define TLS_TIMEOUT_SECONDS 10 - using namespace icinga; -int TlsStream::m_SSLIndex; -bool TlsStream::m_SSLIndexInitialized = false; - -/** - * Constructor for the TlsStream class. - * - * @param role The role of the client. - * @param sslContext The SSL context for the client. - */ -TlsStream::TlsStream(const Socket::Ptr& socket, const String& hostname, ConnectionRole role, const std::shared_ptr& sslContext) - : TlsStream(socket, hostname, role, sslContext.get()) -{ -} - -/** - * Constructor for the TlsStream class. - * - * @param role The role of the client. - * @param sslContext The SSL context for the client. - */ -TlsStream::TlsStream(const Socket::Ptr& socket, const String& hostname, ConnectionRole role, const std::shared_ptr& sslContext) - : TlsStream(socket, hostname, role, sslContext->native_handle()) -{ -} - -/** - * Constructor for the TlsStream class. - * - * @param role The role of the client. - * @param sslContext The SSL context for the client. - */ -TlsStream::TlsStream(const Socket::Ptr& socket, const String& hostname, ConnectionRole role, SSL_CTX* sslContext) - : SocketEvents(socket), m_Eof(false), m_HandshakeOK(false), m_VerifyOK(true), m_ErrorCode(0), - m_ErrorOccurred(false), m_Socket(socket), m_Role(role), m_SendQ(new FIFO()), m_RecvQ(new FIFO()), - m_CurrentAction(TlsActionNone), m_Retry(false), m_Shutdown(false) -{ - std::ostringstream msgbuf; - char errbuf[256]; - - m_SSL = std::shared_ptr(SSL_new(sslContext), SSL_free); - - if (!m_SSL) { - msgbuf << "SSL_new() failed with code " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\""; - Log(LogCritical, "TlsStream", msgbuf.str()); - - BOOST_THROW_EXCEPTION(openssl_error() - << boost::errinfo_api_function("SSL_new") - << errinfo_openssl_error(ERR_peek_error())); - } - - if (!m_SSLIndexInitialized) { - m_SSLIndex = SSL_get_ex_new_index(0, const_cast("TlsStream"), nullptr, nullptr, nullptr); - m_SSLIndexInitialized = true; - } - - SSL_set_ex_data(m_SSL.get(), m_SSLIndex, this); - - SSL_set_verify(m_SSL.get(), SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, &TlsStream::ValidateCertificate); - - socket->MakeNonBlocking(); - - SSL_set_fd(m_SSL.get(), socket->GetFD()); - - if (m_Role == RoleServer) - SSL_set_accept_state(m_SSL.get()); - else { -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - if (!hostname.IsEmpty()) - SSL_set_tlsext_host_name(m_SSL.get(), hostname.CStr()); -#endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */ - - SSL_set_connect_state(m_SSL.get()); - } -} - -TlsStream::~TlsStream() -{ - CloseInternal(true); -} - -int TlsStream::ValidateCertificate(int preverify_ok, X509_STORE_CTX *ctx) -{ - auto *ssl = static_cast(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); - auto *stream = static_cast(SSL_get_ex_data(ssl, m_SSLIndex)); - - if (!preverify_ok) { - stream->m_VerifyOK = false; - - std::ostringstream msgbuf; - int err = X509_STORE_CTX_get_error(ctx); - msgbuf << "code " << err << ": " << X509_verify_cert_error_string(err); - stream->m_VerifyError = msgbuf.str(); - } - - return 1; -} - -bool TlsStream::IsVerifyOK() const -{ - return m_VerifyOK; -} - -String TlsStream::GetVerifyError() const -{ - return m_VerifyError; -} - -/** - * Retrieves the X509 certficate for this client. - * - * @returns The X509 certificate. - */ -std::shared_ptr TlsStream::GetClientCertificate() const -{ - boost::mutex::scoped_lock lock(m_Mutex); - return std::shared_ptr(SSL_get_certificate(m_SSL.get()), &Utility::NullDeleter); -} - -/** - * Retrieves the X509 certficate for the peer. - * - * @returns The X509 certificate. - */ -std::shared_ptr TlsStream::GetPeerCertificate() const -{ - boost::mutex::scoped_lock lock(m_Mutex); - return std::shared_ptr(SSL_get_peer_certificate(m_SSL.get()), X509_free); -} - -void TlsStream::OnEvent(int revents) -{ - int rc; - size_t count; - - boost::mutex::scoped_lock lock(m_Mutex); - - if (!m_SSL) - return; - - char buffer[64 * 1024]; - - if (m_CurrentAction == TlsActionNone) { - if (revents & (POLLIN | POLLERR | POLLHUP)) - m_CurrentAction = TlsActionRead; - else if (m_SendQ->GetAvailableBytes() > 0 && (revents & POLLOUT)) - m_CurrentAction = TlsActionWrite; - else { - ChangeEvents(POLLIN); - - return; - } - } - - bool success = false; - - /* Clear error queue for this thread before using SSL_{read,write,do_handshake}. - * Otherwise SSL_*_error() does not work reliably. - */ - ERR_clear_error(); - - size_t readTotal = 0; - - switch (m_CurrentAction) { - case TlsActionRead: - do { - rc = SSL_read(m_SSL.get(), buffer, sizeof(buffer)); - - if (rc > 0) { - m_RecvQ->Write(buffer, rc); - success = true; - - readTotal += rc; - } - -#ifdef I2_DEBUG /* I2_DEBUG */ - Log(LogDebug, "TlsStream") - << "Read bytes: " << rc << " Total read bytes: " << readTotal; -#endif /* I2_DEBUG */ - /* Limit read size. We cannot do this check inside the while loop - * since below should solely check whether OpenSSL has more data - * or not. */ - if (readTotal >= 64 * 1024) { -#ifdef I2_DEBUG /* I2_DEBUG */ - Log(LogWarning, "TlsStream") - << "Maximum read bytes exceeded: " << readTotal; -#endif /* I2_DEBUG */ - break; - } - - /* Use OpenSSL's state machine here to determine whether we need - * to read more data. SSL_has_pending() is available with 1.1.0. - */ - } while (SSL_pending(m_SSL.get())); - - if (success) - m_CV.notify_all(); - - break; - case TlsActionWrite: - count = m_SendQ->Peek(buffer, sizeof(buffer), true); - - rc = SSL_write(m_SSL.get(), buffer, count); - - if (rc > 0) { - m_SendQ->Read(nullptr, rc, true); - success = true; - } - - break; - case TlsActionHandshake: - rc = SSL_do_handshake(m_SSL.get()); - - if (rc > 0) { - success = true; - m_HandshakeOK = true; - m_CV.notify_all(); - } - - break; - default: - VERIFY(!"Invalid TlsAction"); - } - - if (rc <= 0) { - int err = SSL_get_error(m_SSL.get(), rc); - - switch (err) { - case SSL_ERROR_WANT_READ: - m_Retry = true; - ChangeEvents(POLLIN); - - break; - case SSL_ERROR_WANT_WRITE: - m_Retry = true; - ChangeEvents(POLLOUT); - - break; - case SSL_ERROR_ZERO_RETURN: - lock.unlock(); - - Close(); - - return; - default: - m_ErrorCode = ERR_peek_error(); - m_ErrorOccurred = true; - - if (m_ErrorCode != 0) { - char errbuf[256]; - Log(LogWarning, "TlsStream") - << "OpenSSL error: " << ERR_error_string(m_ErrorCode, errbuf); - } else { - Log(LogWarning, "TlsStream", "TLS stream was disconnected."); - } - - lock.unlock(); - - Close(); - - return; - } - } - - if (success) { - m_CurrentAction = TlsActionNone; - - if (!m_Eof) { - if (m_SendQ->GetAvailableBytes() > 0) - ChangeEvents(POLLIN|POLLOUT); - else - ChangeEvents(POLLIN); - } - - lock.unlock(); - - while (m_RecvQ->IsDataAvailable() && IsHandlingEvents()) - SignalDataAvailable(); - } - - if (m_Shutdown && !m_SendQ->IsDataAvailable()) { - if (!success) - lock.unlock(); - - Close(); - } -} - -void TlsStream::HandleError() const -{ - if (m_ErrorOccurred) { - BOOST_THROW_EXCEPTION(openssl_error() - << boost::errinfo_api_function("TlsStream::OnEvent") - << errinfo_openssl_error(m_ErrorCode)); - } -} - -void TlsStream::Handshake() -{ - boost::mutex::scoped_lock lock(m_Mutex); - - m_CurrentAction = TlsActionHandshake; - ChangeEvents(POLLOUT); - - boost::system_time const timeout = boost::get_system_time() + boost::posix_time::milliseconds(long(Configuration::TlsHandshakeTimeout * 1000)); - - while (!m_HandshakeOK && !m_ErrorOccurred && !m_Eof && timeout > boost::get_system_time()) - m_CV.timed_wait(lock, timeout); - - if (timeout < boost::get_system_time()) - BOOST_THROW_EXCEPTION(std::runtime_error("Timeout was reached (" + Convert::ToString(Configuration::TlsHandshakeTimeout) + ") during TLS handshake.")); - - if (m_Eof) - BOOST_THROW_EXCEPTION(std::runtime_error("Socket was closed during TLS handshake.")); - - HandleError(); -} - -/** - * Processes data for the stream. - */ -size_t TlsStream::Peek(void *buffer, size_t count, bool allow_partial) -{ - boost::mutex::scoped_lock lock(m_Mutex); - - if (!allow_partial) - while (m_RecvQ->GetAvailableBytes() < count && !m_ErrorOccurred && !m_Eof) - m_CV.wait(lock); - - HandleError(); - - return m_RecvQ->Peek(buffer, count, true); -} - -size_t TlsStream::Read(void *buffer, size_t count, bool allow_partial) -{ - boost::mutex::scoped_lock lock(m_Mutex); - - if (!allow_partial) - while (m_RecvQ->GetAvailableBytes() < count && !m_ErrorOccurred && !m_Eof) - m_CV.wait(lock); - - HandleError(); - - return m_RecvQ->Read(buffer, count, true); -} - -void TlsStream::Write(const void *buffer, size_t count) -{ - boost::mutex::scoped_lock lock(m_Mutex); - - m_SendQ->Write(buffer, count); - - ChangeEvents(POLLIN|POLLOUT); -} - -void TlsStream::Shutdown() -{ - m_Shutdown = true; - ChangeEvents(POLLOUT); -} - -/** - * Closes the stream. - */ -void TlsStream::Close() -{ - CloseInternal(false); -} - -void TlsStream::CloseInternal(bool inDestructor) -{ - if (m_Eof) - return; - - m_Eof = true; - - if (!inDestructor) - SignalDataAvailable(); - - SocketEvents::Unregister(); - - Stream::Close(); - - boost::mutex::scoped_lock lock(m_Mutex); - - if (!m_SSL) - return; - - /* https://www.openssl.org/docs/manmaster/man3/SSL_shutdown.html - * - * It is recommended to do a bidirectional shutdown by checking - * the return value of SSL_shutdown() and call it again until - * it returns 1 or a fatal error. A maximum of 2x pending + 2x data - * is recommended. - */ - int rc = 0; - - for (int i = 0; i < 4; i++) { - if ((rc = SSL_shutdown(m_SSL.get()))) - break; - } - - m_SSL.reset(); - - m_Socket->Close(); - m_Socket.reset(); - - m_CV.notify_all(); -} - -bool TlsStream::IsEof() const -{ - return m_Eof && m_RecvQ->GetAvailableBytes() < 1u; -} - -bool TlsStream::SupportsWaiting() const -{ - return true; -} - -bool TlsStream::IsDataAvailable() const -{ - boost::mutex::scoped_lock lock(m_Mutex); - - return m_RecvQ->GetAvailableBytes() > 0; -} - -Socket::Ptr TlsStream::GetSocket() const -{ - return m_Socket; -} - bool UnbufferedAsioTlsStream::IsVerifyOK() const { return m_VerifyOK; diff --git a/lib/base/tlsstream.hpp b/lib/base/tlsstream.hpp index ce782a33a..dd3b556df 100644 --- a/lib/base/tlsstream.hpp +++ b/lib/base/tlsstream.hpp @@ -20,86 +20,6 @@ namespace icinga { -enum TlsAction -{ - TlsActionNone, - TlsActionRead, - TlsActionWrite, - TlsActionHandshake -}; - -/** - * A TLS stream. - * - * @ingroup base - */ -class TlsStream final : public SocketEvents -{ -public: - DECLARE_PTR_TYPEDEFS(TlsStream); - - TlsStream(const Socket::Ptr& socket, const String& hostname, ConnectionRole role, const std::shared_ptr& sslContext = MakeSSLContext()); - TlsStream(const Socket::Ptr& socket, const String& hostname, ConnectionRole role, const std::shared_ptr& sslContext); - ~TlsStream() override; - - Socket::Ptr GetSocket() const; - - std::shared_ptr GetClientCertificate() const; - std::shared_ptr GetPeerCertificate() const; - - void Handshake(); - - void Close() override; - void Shutdown() override; - - size_t Peek(void *buffer, size_t count, bool allow_partial = false) override; - size_t Read(void *buffer, size_t count, bool allow_partial = false) override; - void Write(const void *buffer, size_t count) override; - - bool IsEof() const override; - - bool SupportsWaiting() const override; - bool IsDataAvailable() const override; - - bool IsVerifyOK() const; - String GetVerifyError() const; - -private: - std::shared_ptr m_SSL; - bool m_Eof; - mutable boost::mutex m_Mutex; - mutable boost::condition_variable m_CV; - bool m_HandshakeOK; - bool m_VerifyOK; - String m_VerifyError; - int m_ErrorCode; - bool m_ErrorOccurred; - - Socket::Ptr m_Socket; - ConnectionRole m_Role; - - FIFO::Ptr m_SendQ; - FIFO::Ptr m_RecvQ; - - TlsAction m_CurrentAction; - bool m_Retry; - bool m_Shutdown; - - static int m_SSLIndex; - static bool m_SSLIndexInitialized; - - TlsStream(const Socket::Ptr& socket, const String& hostname, ConnectionRole role, SSL_CTX* sslContext); - - void OnEvent(int revents) override; - - void HandleError() const; - - static int ValidateCertificate(int preverify_ok, X509_STORE_CTX *ctx); - static void NullCertificateDeleter(X509 *certificate); - - void CloseInternal(bool inDestructor); -}; - struct UnbufferedAsioTlsStreamParams { boost::asio::io_service& IoService; From 5dbb6ad3662397107a4a277c1804e1fc4b8f03f6 Mon Sep 17 00:00:00 2001 From: Michael Friedrich Date: Fri, 24 May 2019 10:07:51 +0200 Subject: [PATCH 04/10] Quality: Remove old SocketEvent functionality --- lib/base/CMakeLists.txt | 1 - lib/base/socketevents-epoll.cpp | 189 ------------------------------- lib/base/socketevents-poll.cpp | 190 -------------------------------- lib/base/socketevents.cpp | 142 ------------------------ lib/base/socketevents.hpp | 137 ----------------------- lib/base/tlsstream.hpp | 1 - 6 files changed, 660 deletions(-) delete mode 100644 lib/base/socketevents-epoll.cpp delete mode 100644 lib/base/socketevents-poll.cpp delete mode 100644 lib/base/socketevents.cpp delete mode 100644 lib/base/socketevents.hpp diff --git a/lib/base/CMakeLists.txt b/lib/base/CMakeLists.txt index 9260b31ed..fb3de3029 100644 --- a/lib/base/CMakeLists.txt +++ b/lib/base/CMakeLists.txt @@ -61,7 +61,6 @@ set(base_SOURCES serializer.cpp serializer.hpp singleton.hpp socket.cpp socket.hpp - socketevents.cpp socketevents-epoll.cpp socketevents-poll.cpp socketevents.hpp stacktrace.cpp stacktrace.hpp statsfunction.hpp stdiostream.cpp stdiostream.hpp diff --git a/lib/base/socketevents-epoll.cpp b/lib/base/socketevents-epoll.cpp deleted file mode 100644 index 0e75ee5e8..000000000 --- a/lib/base/socketevents-epoll.cpp +++ /dev/null @@ -1,189 +0,0 @@ -/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ - -#include "base/socketevents.hpp" -#include "base/exception.hpp" -#include "base/logger.hpp" -#include "base/utility.hpp" -#include -#include -#ifdef __linux__ -# include - -using namespace icinga; - -void SocketEventEngineEpoll::InitializeThread(int tid) -{ - m_PollFDs[tid] = epoll_create(128); - Utility::SetCloExec(m_PollFDs[tid]); - - SocketEventDescriptor sed; - - m_Sockets[tid][m_EventFDs[tid][0]] = sed; - m_FDChanged[tid] = true; - - epoll_event event; - memset(&event, 0, sizeof(event)); - event.data.fd = m_EventFDs[tid][0]; - event.events = EPOLLIN; - epoll_ctl(m_PollFDs[tid], EPOLL_CTL_ADD, m_EventFDs[tid][0], &event); -} - -int SocketEventEngineEpoll::PollToEpoll(int events) -{ - int result = 0; - - if (events & POLLIN) - result |= EPOLLIN; - - if (events & POLLOUT) - result |= EPOLLOUT; - - return events; -} - -int SocketEventEngineEpoll::EpollToPoll(int events) -{ - int result = 0; - - if (events & EPOLLIN) - result |= POLLIN; - - if (events & EPOLLOUT) - result |= POLLOUT; - - return events; -} - -void SocketEventEngineEpoll::ThreadProc(int tid) -{ - Utility::SetThreadName("SocketIO"); - - for (;;) { - { - boost::mutex::scoped_lock lock(m_EventMutex[tid]); - - if (m_FDChanged[tid]) { - m_FDChanged[tid] = false; - m_CV[tid].notify_all(); - } - } - - epoll_event pevents[64]; - int ready = epoll_wait(m_PollFDs[tid], pevents, sizeof(pevents) / sizeof(pevents[0]), -1); - - std::vector events; - - { - boost::mutex::scoped_lock lock(m_EventMutex[tid]); - - if (m_FDChanged[tid]) { - m_FDChanged[tid] = false; - - continue; - } - - for (int i = 0; i < ready; i++) { - if (pevents[i].data.fd == m_EventFDs[tid][0]) { - char buffer[512]; - if (recv(m_EventFDs[tid][0], buffer, sizeof(buffer), 0) < 0) - Log(LogCritical, "SocketEvents", "Read from event FD failed."); - - continue; - } - - if ((pevents[i].events & (EPOLLIN | EPOLLOUT | EPOLLHUP | EPOLLERR)) == 0) - continue; - - EventDescription event; - event.REvents = SocketEventEngineEpoll::EpollToPoll(pevents[i].events); - event.Descriptor = m_Sockets[tid][pevents[i].data.fd]; - - events.emplace_back(std::move(event)); - } - } - - for (const EventDescription& event : events) { - try { - event.Descriptor.EventInterface->OnEvent(event.REvents); - } catch (const std::exception& ex) { - Log(LogCritical, "SocketEvents") - << "Exception thrown in socket I/O handler:\n" - << DiagnosticInformation(ex); - } catch (...) { - Log(LogCritical, "SocketEvents", "Exception of unknown type thrown in socket I/O handler."); - } - } - } -} - -void SocketEventEngineEpoll::Register(SocketEvents *se) -{ - int tid = se->m_ID % SOCKET_IOTHREADS; - - { - boost::mutex::scoped_lock lock(m_EventMutex[tid]); - - VERIFY(se->m_FD != INVALID_SOCKET); - - SocketEventDescriptor desc; - desc.EventInterface = se; - - VERIFY(m_Sockets[tid].find(se->m_FD) == m_Sockets[tid].end()); - - m_Sockets[tid][se->m_FD] = desc; - - epoll_event event; - memset(&event, 0, sizeof(event)); - event.data.fd = se->m_FD; - event.events = 0; - epoll_ctl(m_PollFDs[tid], EPOLL_CTL_ADD, se->m_FD, &event); - - se->m_Events = true; - } -} - -void SocketEventEngineEpoll::Unregister(SocketEvents *se) -{ - int tid = se->m_ID % SOCKET_IOTHREADS; - - { - boost::mutex::scoped_lock lock(m_EventMutex[tid]); - - if (se->m_FD == INVALID_SOCKET) - return; - - m_Sockets[tid].erase(se->m_FD); - m_FDChanged[tid] = true; - - epoll_ctl(m_PollFDs[tid], EPOLL_CTL_DEL, se->m_FD, nullptr); - - se->m_FD = INVALID_SOCKET; - se->m_Events = false; - } - - WakeUpThread(tid, true); -} - -void SocketEventEngineEpoll::ChangeEvents(SocketEvents *se, int events) -{ - if (se->m_FD == INVALID_SOCKET) - BOOST_THROW_EXCEPTION(std::runtime_error("Tried to read/write from a closed socket.")); - - int tid = se->m_ID % SOCKET_IOTHREADS; - - { - boost::mutex::scoped_lock lock(m_EventMutex[tid]); - - auto it = m_Sockets[tid].find(se->m_FD); - - if (it == m_Sockets[tid].end()) - return; - - epoll_event event; - memset(&event, 0, sizeof(event)); - event.data.fd = se->m_FD; - event.events = SocketEventEngineEpoll::PollToEpoll(events); - epoll_ctl(m_PollFDs[tid], EPOLL_CTL_MOD, se->m_FD, &event); - } -} -#endif /* __linux__ */ diff --git a/lib/base/socketevents-poll.cpp b/lib/base/socketevents-poll.cpp deleted file mode 100644 index f8200d435..000000000 --- a/lib/base/socketevents-poll.cpp +++ /dev/null @@ -1,190 +0,0 @@ -/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ - -#include "base/socketevents.hpp" -#include "base/exception.hpp" -#include "base/logger.hpp" -#include "base/utility.hpp" -#include -#include - -using namespace icinga; - -void SocketEventEnginePoll::InitializeThread(int tid) -{ - SocketEventDescriptor sed; - sed.Events = POLLIN; - - m_Sockets[tid][m_EventFDs[tid][0]] = sed; - m_FDChanged[tid] = true; -} - -void SocketEventEnginePoll::ThreadProc(int tid) -{ - Utility::SetThreadName("SocketIO"); - - std::vector pfds; - std::vector descriptors; - - for (;;) { - { - boost::mutex::scoped_lock lock(m_EventMutex[tid]); - - if (m_FDChanged[tid]) { - pfds.resize(m_Sockets[tid].size()); - descriptors.resize(m_Sockets[tid].size()); - - int i = 0; - - typedef std::map::value_type kv_pair; - - for (const kv_pair& desc : m_Sockets[tid]) { - if (desc.second.Events == 0) - continue; - - int events = desc.second.Events; - - if (desc.second.EventInterface) { - desc.second.EventInterface->m_EnginePrivate = &pfds[i]; - - if (!desc.second.EventInterface->m_Events) - events = 0; - } - - pfds[i].fd = desc.first; - pfds[i].events = events; - descriptors[i] = desc.second; - - i++; - } - - pfds.resize(i); - - m_FDChanged[tid] = false; - m_CV[tid].notify_all(); - } - } - - ASSERT(!pfds.empty()); - -#ifdef _WIN32 - (void) WSAPoll(&pfds[0], pfds.size(), -1); -#else /* _WIN32 */ - (void) poll(&pfds[0], pfds.size(), -1); -#endif /* _WIN32 */ - - std::vector events; - - { - boost::mutex::scoped_lock lock(m_EventMutex[tid]); - - if (m_FDChanged[tid]) - continue; - - for (std::vector::size_type i = 0; i < pfds.size(); i++) { - if ((pfds[i].revents & (POLLIN | POLLOUT | POLLHUP | POLLERR)) == 0) - continue; - - if (pfds[i].fd == m_EventFDs[tid][0]) { - char buffer[512]; - if (recv(m_EventFDs[tid][0], buffer, sizeof(buffer), 0) < 0) - Log(LogCritical, "SocketEvents", "Read from event FD failed."); - - continue; - } - - EventDescription event; - event.REvents = pfds[i].revents; - event.Descriptor = descriptors[i]; - - events.emplace_back(std::move(event)); - } - } - - for (const EventDescription& event : events) { - try { - event.Descriptor.EventInterface->OnEvent(event.REvents); - } catch (const std::exception& ex) { - Log(LogCritical, "SocketEvents") - << "Exception thrown in socket I/O handler:\n" - << DiagnosticInformation(ex); - } catch (...) { - Log(LogCritical, "SocketEvents", "Exception of unknown type thrown in socket I/O handler."); - } - } - } -} - -void SocketEventEnginePoll::Register(SocketEvents *se) -{ - int tid = se->m_ID % SOCKET_IOTHREADS; - - { - boost::mutex::scoped_lock lock(m_EventMutex[tid]); - - VERIFY(se->m_FD != INVALID_SOCKET); - - SocketEventDescriptor desc; - desc.Events = 0; - desc.EventInterface = se; - - VERIFY(m_Sockets[tid].find(se->m_FD) == m_Sockets[tid].end()); - - m_Sockets[tid][se->m_FD] = desc; - - m_FDChanged[tid] = true; - - se->m_Events = true; - } - - WakeUpThread(tid, true); -} - -void SocketEventEnginePoll::Unregister(SocketEvents *se) -{ - int tid = se->m_ID % SOCKET_IOTHREADS; - - { - boost::mutex::scoped_lock lock(m_EventMutex[tid]); - - if (se->m_FD == INVALID_SOCKET) - return; - - m_Sockets[tid].erase(se->m_FD); - m_FDChanged[tid] = true; - - se->m_FD = INVALID_SOCKET; - se->m_Events = false; - } - - WakeUpThread(tid, true); -} - -void SocketEventEnginePoll::ChangeEvents(SocketEvents *se, int events) -{ - if (se->m_FD == INVALID_SOCKET) - BOOST_THROW_EXCEPTION(std::runtime_error("Tried to read/write from a closed socket.")); - - int tid = se->m_ID % SOCKET_IOTHREADS; - - { - boost::mutex::scoped_lock lock(m_EventMutex[tid]); - - auto it = m_Sockets[tid].find(se->m_FD); - - if (it == m_Sockets[tid].end()) - return; - - if (it->second.Events == events) - return; - - it->second.Events = events; - - if (se->m_EnginePrivate && std::this_thread::get_id() == m_Threads[tid].get_id()) - ((pollfd *)se->m_EnginePrivate)->events = events; - else - m_FDChanged[tid] = true; - } - - WakeUpThread(tid, false); -} - diff --git a/lib/base/socketevents.cpp b/lib/base/socketevents.cpp deleted file mode 100644 index f79d4218d..000000000 --- a/lib/base/socketevents.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ - -#include "base/socketevents.hpp" -#include "base/exception.hpp" -#include "base/logger.hpp" -#include "base/application.hpp" -#include "base/scriptglobal.hpp" -#include "base/utility.hpp" -#include -#include -#ifdef __linux__ -# include -#endif /* __linux__ */ - -using namespace icinga; - -static boost::once_flag l_SocketIOOnceFlag = BOOST_ONCE_INIT; -static SocketEventEngine *l_SocketIOEngine; - -int SocketEvents::m_NextID = 0; - -void SocketEventEngine::Start() -{ - for (int tid = 0; tid < SOCKET_IOTHREADS; tid++) { - Socket::SocketPair(m_EventFDs[tid]); - - Utility::SetNonBlockingSocket(m_EventFDs[tid][0]); - Utility::SetNonBlockingSocket(m_EventFDs[tid][1]); - -#ifndef _WIN32 - Utility::SetCloExec(m_EventFDs[tid][0]); - Utility::SetCloExec(m_EventFDs[tid][1]); -#endif /* _WIN32 */ - - InitializeThread(tid); - - m_Threads[tid] = std::thread(std::bind(&SocketEventEngine::ThreadProc, this, tid)); - } -} - -void SocketEventEngine::WakeUpThread(int sid, bool wait) -{ - int tid = sid % SOCKET_IOTHREADS; - - if (std::this_thread::get_id() == m_Threads[tid].get_id()) - return; - - if (wait) { - boost::mutex::scoped_lock lock(m_EventMutex[tid]); - - m_FDChanged[tid] = true; - - while (m_FDChanged[tid]) { - (void) send(m_EventFDs[tid][1], "T", 1, 0); - - boost::system_time const timeout = boost::get_system_time() + boost::posix_time::milliseconds(50); - m_CV[tid].timed_wait(lock, timeout); - } - } else { - (void) send(m_EventFDs[tid][1], "T", 1, 0); - } -} - -void SocketEvents::InitializeEngine() -{ - String eventEngine = Configuration::EventEngine; - - if (eventEngine.IsEmpty()) -#ifdef __linux__ - eventEngine = "epoll"; -#else /* __linux__ */ - eventEngine = "poll"; -#endif /* __linux__ */ - - if (eventEngine == "poll") - l_SocketIOEngine = new SocketEventEnginePoll(); -#ifdef __linux__ - else if (eventEngine == "epoll") - l_SocketIOEngine = new SocketEventEngineEpoll(); -#endif /* __linux__ */ - else { - Log(LogWarning, "SocketEvents") - << "Invalid event engine selected: " << eventEngine << " - Falling back to 'poll'"; - - eventEngine = "poll"; - - l_SocketIOEngine = new SocketEventEnginePoll(); - } - - l_SocketIOEngine->Start(); - - Configuration::EventEngine = eventEngine; -} - -/** - * Constructor for the SocketEvents class. - */ -SocketEvents::SocketEvents(const Socket::Ptr& socket) - : m_ID(m_NextID++), m_FD(socket->GetFD()), m_EnginePrivate(nullptr) -{ - boost::call_once(l_SocketIOOnceFlag, &SocketEvents::InitializeEngine); - - Register(); -} - -SocketEvents::~SocketEvents() -{ - VERIFY(m_FD == INVALID_SOCKET); -} - -void SocketEvents::Register() -{ - l_SocketIOEngine->Register(this); -} - -void SocketEvents::Unregister() -{ - l_SocketIOEngine->Unregister(this); -} - -void SocketEvents::ChangeEvents(int events) -{ - l_SocketIOEngine->ChangeEvents(this, events); -} - -boost::mutex& SocketEventEngine::GetMutex(int tid) -{ - return m_EventMutex[tid]; -} - -bool SocketEvents::IsHandlingEvents() const -{ - int tid = m_ID % SOCKET_IOTHREADS; - boost::mutex::scoped_lock lock(l_SocketIOEngine->GetMutex(tid)); - return m_Events; -} - -void SocketEvents::OnEvent(int revents) -{ - -} - diff --git a/lib/base/socketevents.hpp b/lib/base/socketevents.hpp deleted file mode 100644 index 5b0f39d74..000000000 --- a/lib/base/socketevents.hpp +++ /dev/null @@ -1,137 +0,0 @@ -/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ - -#ifndef SOCKETEVENTS_H -#define SOCKETEVENTS_H - -#include "base/i2-base.hpp" -#include "base/socket.hpp" -#include "base/stream.hpp" -#include -#include - -#ifndef _WIN32 -# include -#endif /* _WIN32 */ - -namespace icinga -{ - -/** - * Socket event interface - * - * @ingroup base - */ -class SocketEvents : public Stream -{ -public: - DECLARE_PTR_TYPEDEFS(SocketEvents); - - ~SocketEvents(); - - virtual void OnEvent(int revents); - - void Unregister(); - - void ChangeEvents(int events); - - bool IsHandlingEvents() const; - - void *GetEnginePrivate() const; - void SetEnginePrivate(void *priv); - -protected: - SocketEvents(const Socket::Ptr& socket); - -private: - int m_ID; - SOCKET m_FD; - bool m_Events; - void *m_EnginePrivate; - - static int m_NextID; - - static void InitializeEngine(); - - void WakeUpThread(bool wait = false); - - void Register(); - - friend class SocketEventEnginePoll; - friend class SocketEventEngineEpoll; -}; - -#define SOCKET_IOTHREADS 8 - -struct SocketEventDescriptor -{ - int Events{POLLIN}; - SocketEvents::Ptr EventInterface; -}; - -struct EventDescription -{ - int REvents; - SocketEventDescriptor Descriptor; -}; - -class SocketEventEngine -{ -public: - void Start(); - - void WakeUpThread(int sid, bool wait); - - boost::mutex& GetMutex(int tid); - -protected: - virtual void InitializeThread(int tid) = 0; - virtual void ThreadProc(int tid) = 0; - virtual void Register(SocketEvents *se) = 0; - virtual void Unregister(SocketEvents *se) = 0; - virtual void ChangeEvents(SocketEvents *se, int events) = 0; - - std::thread m_Threads[SOCKET_IOTHREADS]; - SOCKET m_EventFDs[SOCKET_IOTHREADS][2]; - bool m_FDChanged[SOCKET_IOTHREADS]; - boost::mutex m_EventMutex[SOCKET_IOTHREADS]; - boost::condition_variable m_CV[SOCKET_IOTHREADS]; - std::map m_Sockets[SOCKET_IOTHREADS]; - - friend class SocketEvents; -}; - -class SocketEventEnginePoll final : public SocketEventEngine -{ -public: - void Register(SocketEvents *se) override; - void Unregister(SocketEvents *se) override; - void ChangeEvents(SocketEvents *se, int events) override; - -protected: - void InitializeThread(int tid) override; - void ThreadProc(int tid) override; -}; - -#ifdef __linux__ -class SocketEventEngineEpoll : public SocketEventEngine -{ -public: - virtual void Register(SocketEvents *se); - virtual void Unregister(SocketEvents *se); - virtual void ChangeEvents(SocketEvents *se, int events); - -protected: - virtual void InitializeThread(int tid); - virtual void ThreadProc(int tid); - -private: - SOCKET m_PollFDs[SOCKET_IOTHREADS]; - - static int PollToEpoll(int events); - static int EpollToPoll(int events); -}; -#endif /* __linux__ */ - -} - -#endif /* SOCKETEVENTS_H */ diff --git a/lib/base/tlsstream.hpp b/lib/base/tlsstream.hpp index dd3b556df..44fe0b9c5 100644 --- a/lib/base/tlsstream.hpp +++ b/lib/base/tlsstream.hpp @@ -5,7 +5,6 @@ #include "base/i2-base.hpp" #include "base/socket.hpp" -#include "base/socketevents.hpp" #include "base/stream.hpp" #include "base/tlsutility.hpp" #include "base/fifo.hpp" From c77d6eb869a49db5fc5041f444060d8b1d262d5d Mon Sep 17 00:00:00 2001 From: Michael Friedrich Date: Fri, 24 May 2019 15:34:02 +0200 Subject: [PATCH 05/10] Quality: Drop unused boost/tuple header include --- lib/cli/daemoncommand.cpp | 1 - lib/compat/statusdatawriter.cpp | 1 - lib/db_ido_mysql/idomysqlconnection.cpp | 1 - lib/db_ido_pgsql/idopgsqlconnection.cpp | 1 - lib/livestatus/commentstable.cpp | 1 - lib/livestatus/contactstable.cpp | 1 - lib/livestatus/downtimestable.cpp | 1 - lib/livestatus/endpointstable.cpp | 1 - lib/livestatus/hoststable.cpp | 1 - lib/livestatus/livestatuslogutility.cpp | 1 - lib/livestatus/logtable.cpp | 1 - lib/livestatus/servicestable.cpp | 1 - lib/livestatus/statehisttable.cpp | 1 - lib/livestatus/table.cpp | 1 - 14 files changed, 14 deletions(-) diff --git a/lib/cli/daemoncommand.cpp b/lib/cli/daemoncommand.cpp index 1da4b1cc5..9fbe486ba 100644 --- a/lib/cli/daemoncommand.cpp +++ b/lib/cli/daemoncommand.cpp @@ -17,7 +17,6 @@ #include "base/context.hpp" #include "config.h" #include -#include #include #include diff --git a/lib/compat/statusdatawriter.cpp b/lib/compat/statusdatawriter.cpp index a75763c62..5a0f136bf 100644 --- a/lib/compat/statusdatawriter.cpp +++ b/lib/compat/statusdatawriter.cpp @@ -22,7 +22,6 @@ #include "base/application.hpp" #include "base/context.hpp" #include "base/statsfunction.hpp" -#include #include #include #include diff --git a/lib/db_ido_mysql/idomysqlconnection.cpp b/lib/db_ido_mysql/idomysqlconnection.cpp index d868ee01f..fc4def69b 100644 --- a/lib/db_ido_mysql/idomysqlconnection.cpp +++ b/lib/db_ido_mysql/idomysqlconnection.cpp @@ -13,7 +13,6 @@ #include "base/configtype.hpp" #include "base/exception.hpp" #include "base/statsfunction.hpp" -#include #include using namespace icinga; diff --git a/lib/db_ido_pgsql/idopgsqlconnection.cpp b/lib/db_ido_pgsql/idopgsqlconnection.cpp index 12bca3c35..96e5ec1e3 100644 --- a/lib/db_ido_pgsql/idopgsqlconnection.cpp +++ b/lib/db_ido_pgsql/idopgsqlconnection.cpp @@ -14,7 +14,6 @@ #include "base/exception.hpp" #include "base/context.hpp" #include "base/statsfunction.hpp" -#include #include using namespace icinga; diff --git a/lib/livestatus/commentstable.cpp b/lib/livestatus/commentstable.cpp index 1fe235936..3676ac3ac 100644 --- a/lib/livestatus/commentstable.cpp +++ b/lib/livestatus/commentstable.cpp @@ -6,7 +6,6 @@ #include "icinga/service.hpp" #include "base/configtype.hpp" #include "base/objectlock.hpp" -#include using namespace icinga; diff --git a/lib/livestatus/contactstable.cpp b/lib/livestatus/contactstable.cpp index 2649b1706..d6a04c41d 100644 --- a/lib/livestatus/contactstable.cpp +++ b/lib/livestatus/contactstable.cpp @@ -8,7 +8,6 @@ #include "base/objectlock.hpp" #include "base/json.hpp" #include "base/utility.hpp" -#include using namespace icinga; diff --git a/lib/livestatus/downtimestable.cpp b/lib/livestatus/downtimestable.cpp index 709568a94..b3eb121f9 100644 --- a/lib/livestatus/downtimestable.cpp +++ b/lib/livestatus/downtimestable.cpp @@ -6,7 +6,6 @@ #include "icinga/service.hpp" #include "base/configtype.hpp" #include "base/objectlock.hpp" -#include using namespace icinga; diff --git a/lib/livestatus/endpointstable.cpp b/lib/livestatus/endpointstable.cpp index b5c5c3d2f..3d407eb3e 100644 --- a/lib/livestatus/endpointstable.cpp +++ b/lib/livestatus/endpointstable.cpp @@ -10,7 +10,6 @@ #include "base/objectlock.hpp" #include "base/convert.hpp" #include "base/utility.hpp" -#include #include using namespace icinga; diff --git a/lib/livestatus/hoststable.cpp b/lib/livestatus/hoststable.cpp index 7a051da28..645c4387b 100644 --- a/lib/livestatus/hoststable.cpp +++ b/lib/livestatus/hoststable.cpp @@ -18,7 +18,6 @@ #include "base/json.hpp" #include "base/convert.hpp" #include "base/utility.hpp" -#include #include using namespace icinga; diff --git a/lib/livestatus/livestatuslogutility.cpp b/lib/livestatus/livestatuslogutility.cpp index cbcbdabc9..f598d5852 100644 --- a/lib/livestatus/livestatuslogutility.cpp +++ b/lib/livestatus/livestatuslogutility.cpp @@ -10,7 +10,6 @@ #include "base/utility.hpp" #include "base/convert.hpp" #include "base/logger.hpp" -#include #include #include #include diff --git a/lib/livestatus/logtable.cpp b/lib/livestatus/logtable.cpp index a44c714f0..ed21b722e 100644 --- a/lib/livestatus/logtable.cpp +++ b/lib/livestatus/logtable.cpp @@ -19,7 +19,6 @@ #include "base/logger.hpp" #include "base/application.hpp" #include "base/objectlock.hpp" -#include #include #include #include diff --git a/lib/livestatus/servicestable.cpp b/lib/livestatus/servicestable.cpp index 8b3e06535..98d27f84b 100644 --- a/lib/livestatus/servicestable.cpp +++ b/lib/livestatus/servicestable.cpp @@ -20,7 +20,6 @@ #include "base/json.hpp" #include "base/convert.hpp" #include "base/utility.hpp" -#include #include using namespace icinga; diff --git a/lib/livestatus/statehisttable.cpp b/lib/livestatus/statehisttable.cpp index 699eb7b48..e68d45dd9 100644 --- a/lib/livestatus/statehisttable.cpp +++ b/lib/livestatus/statehisttable.cpp @@ -19,7 +19,6 @@ #include "base/logger.hpp" #include "base/application.hpp" #include "base/objectlock.hpp" -#include #include #include #include diff --git a/lib/livestatus/table.cpp b/lib/livestatus/table.cpp index f11fef589..ac8d1745c 100644 --- a/lib/livestatus/table.cpp +++ b/lib/livestatus/table.cpp @@ -20,7 +20,6 @@ #include "base/array.hpp" #include "base/dictionary.hpp" #include -#include using namespace icinga; From 0466316019cf3795be776575928830242c0b8524 Mon Sep 17 00:00:00 2001 From: Michael Friedrich Date: Mon, 27 May 2019 15:09:26 +0200 Subject: [PATCH 06/10] Quality: Rewrite OpenTSDB to use Boost ASIO and I/O engine The connection handling and code isn't really good, but not really actively maintained either. Besides that, the "telnet" method doesn't allow for TLS, this needs a general rewrite against their HTTP API. I've also added function documentation where applicable. --- lib/perfdata/opentsdbwriter.cpp | 107 ++++++++++++++++++++++++++------ lib/perfdata/opentsdbwriter.hpp | 2 +- lib/perfdata/opentsdbwriter.ti | 5 ++ 3 files changed, 93 insertions(+), 21 deletions(-) diff --git a/lib/perfdata/opentsdbwriter.cpp b/lib/perfdata/opentsdbwriter.cpp index 013d51148..0e54b2846 100644 --- a/lib/perfdata/opentsdbwriter.cpp +++ b/lib/perfdata/opentsdbwriter.cpp @@ -28,6 +28,9 @@ REGISTER_TYPE(OpenTsdbWriter); REGISTER_STATSFUNCTION(OpenTsdbWriter, &OpenTsdbWriter::StatsFunc); +/* + * Enable HA capabilities once the config object is loaded. + */ void OpenTsdbWriter::OnConfigLoaded() { ObjectImpl::OnConfigLoaded(); @@ -42,17 +45,27 @@ void OpenTsdbWriter::OnConfigLoaded() } } +/** + * Feature stats interface + * + * @param status Key value pairs for feature stats + */ void OpenTsdbWriter::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr&) { DictionaryData nodes; for (const OpenTsdbWriter::Ptr& opentsdbwriter : ConfigType::GetObjectsByType()) { - nodes.emplace_back(opentsdbwriter->GetName(), 1); //add more stats + nodes.emplace_back(opentsdbwriter->GetName(), new Dictionary({ + { "connected", opentsdbwriter->GetConnected() } + })); } status->Set("opentsdbwriter", new Dictionary(std::move(nodes))); } +/** + * Resume is equivalent to Start, but with HA capabilities to resume at runtime. + */ void OpenTsdbWriter::Resume() { ObjectImpl::Resume(); @@ -69,7 +82,9 @@ void OpenTsdbWriter::Resume() Service::OnNewCheckResult.connect(std::bind(&OpenTsdbWriter::CheckResultHandler, this, _1, _2)); } -/* Pause is equivalent to Stop, but with HA capabilities to resume at runtime. */ +/** + * Pause is equivalent to Stop, but with HA capabilities to resume at runtime. + */ void OpenTsdbWriter::Pause() { m_ReconnectTimer.reset(); @@ -77,33 +92,54 @@ void OpenTsdbWriter::Pause() Log(LogInformation, "OpentsdbWriter") << "'" << GetName() << "' paused."; + m_Stream->close(); + + SetConnected(false); + ObjectImpl::Pause(); } +/** + * Reconnect handler called by the timer. + * Handles TLS + */ void OpenTsdbWriter::ReconnectTimerHandler() { if (IsPaused()) return; - if (m_Stream) - return; + SetShouldConnect(true); - TcpSocket::Ptr socket = new TcpSocket(); + if (GetConnected()) + return; Log(LogNotice, "OpenTsdbWriter") << "Reconnect to OpenTSDB TSD on host '" << GetHost() << "' port '" << GetPort() << "'."; + /* + * We're using telnet as input method. Future PRs may change this into using the HTTP API. + * http://opentsdb.net/docs/build/html/user_guide/writing/index.html#telnet + */ + + m_Stream = std::make_shared(IoEngine::Get().GetIoService()); + try { - socket->Connect(GetHost(), GetPort()); - } catch (std::exception&) { - Log(LogCritical, "OpenTsdbWriter") - << "Can't connect to OpenTSDB TSD on host '" << GetHost() << "' port '" << GetPort() << "'."; - return; + icinga::Connect(m_Stream->lowest_layer(), GetHost(), GetPort()); + } catch (const std::exception& ex) { + Log(LogWarning, "OpenTsdbWriter") + << "Can't connect to OpenTSDB on host '" << GetHost() << "' port '" << GetPort() << ".'"; } - m_Stream = new NetworkStream(socket); + SetConnected(true); } +/** + * Registered check result handler processing data. + * Calculates tags from the config. + * + * @param checkable Host/service object + * @param cr Check result + */ void OpenTsdbWriter::CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) { if (IsPaused()) @@ -165,6 +201,15 @@ void OpenTsdbWriter::CheckResultHandler(const Checkable::Ptr& checkable, const C SendMetric(checkable, metric + ".execution_time", tags, cr->CalculateExecutionTime(), ts); } +/** + * Parse and send performance data metrics to OpenTSDB + * + * @param checkable Host/service object + * @param metric Full metric name + * @param tags Tag key pairs + * @param cr Check result containing performance data + * @param ts Timestamp when the check result was received + */ void OpenTsdbWriter::SendPerfdata(const Checkable::Ptr& checkable, const String& metric, const std::map& tags, const CheckResult::Ptr& cr, double ts) { @@ -209,6 +254,15 @@ void OpenTsdbWriter::SendPerfdata(const Checkable::Ptr& checkable, const String& } } +/** + * Send given metric to OpenTSDB + * + * @param checkable Host/service object + * @param metric Full metric name + * @param tags Tag key pairs + * @param value Floating point metric value + * @param ts Timestamp where the metric was received from the check result + */ void OpenTsdbWriter::SendMetric(const Checkable::Ptr& checkable, const String& metric, const std::map& tags, double value, double ts) { @@ -220,7 +274,7 @@ void OpenTsdbWriter::SendMetric(const Checkable::Ptr& checkable, const String& m std::ostringstream msgbuf; /* - * must be (http://opentsdb.net/docs/build/html/user_guide/writing.html) + * must be (http://opentsdb.net/docs/build/html/user_guide/query/timeseries.html) * put * "tags" must include at least one tag, we use "host=HOSTNAME" */ @@ -235,21 +289,27 @@ void OpenTsdbWriter::SendMetric(const Checkable::Ptr& checkable, const String& m ObjectLock olock(this); - if (!m_Stream) + if (!GetConnected()) return; try { - m_Stream->Write(put.CStr(), put.GetLength()); + Log(LogDebug, "OpenTsdbWriter") + << "Checkable '" << checkable->GetName() << "' sending message '" << put << "'."; + + boost::asio::write(*m_Stream, boost::asio::buffer(msgbuf.str())); + m_Stream->flush(); } catch (const std::exception& ex) { Log(LogCritical, "OpenTsdbWriter") - << "Cannot write to OpenTSDB TSD on host '" << GetHost() << "' port '" << GetPort() + "'."; - - m_Stream.reset(); + << "Cannot write to TCP socket on host '" << GetHost() << "' port '" << GetPort() << "'."; } } -/* for metric and tag name rules, see - * http://opentsdb.net/docs/build/html/user_guide/writing.html#metrics-and-tags +/** + * Escape tags for OpenTSDB + * http://opentsdb.net/docs/build/html/user_guide/query/timeseries.html#precisions-on-metrics-and-tags + * + * @param str Tag name + * @return Escaped tag */ String OpenTsdbWriter::EscapeTag(const String& str) { @@ -261,6 +321,13 @@ String OpenTsdbWriter::EscapeTag(const String& str) return result; } +/** + * Escape metric name for OpenTSDB + * http://opentsdb.net/docs/build/html/user_guide/query/timeseries.html#precisions-on-metrics-and-tags + * + * @param str Metric name + * @return Escaped metric + */ String OpenTsdbWriter::EscapeMetric(const String& str) { String result = str; @@ -271,4 +338,4 @@ String OpenTsdbWriter::EscapeMetric(const String& str) boost::replace_all(result, ":", "_"); return result; -} +} \ No newline at end of file diff --git a/lib/perfdata/opentsdbwriter.hpp b/lib/perfdata/opentsdbwriter.hpp index 792377556..bf0fc8eb1 100644 --- a/lib/perfdata/opentsdbwriter.hpp +++ b/lib/perfdata/opentsdbwriter.hpp @@ -32,7 +32,7 @@ protected: void Pause() override; private: - Stream::Ptr m_Stream; + std::shared_ptr m_Stream; Timer::Ptr m_ReconnectTimer; diff --git a/lib/perfdata/opentsdbwriter.ti b/lib/perfdata/opentsdbwriter.ti index bce6a11b5..de19a1eac 100644 --- a/lib/perfdata/opentsdbwriter.ti +++ b/lib/perfdata/opentsdbwriter.ti @@ -20,6 +20,11 @@ class OpenTsdbWriter : ConfigObject [config] bool enable_ha { default {{{ return false; }}} }; + + [no_user_modify] bool connected; + [no_user_modify] bool should_connect { + default {{{ return true; }}} + }; }; } From efd4e8ad408254b1b5b51a1656e9dcf8a5e3dd7e Mon Sep 17 00:00:00 2001 From: Michael Friedrich Date: Mon, 27 May 2019 16:49:51 +0200 Subject: [PATCH 07/10] Quality: Use Boost ASIO/IO engine in Graphite feature This commit changes the reconnect priority to high. Also add function docs. --- lib/perfdata/graphitewriter.cpp | 143 ++++++++++++++++++++++++++++---- lib/perfdata/graphitewriter.hpp | 2 +- 2 files changed, 128 insertions(+), 17 deletions(-) diff --git a/lib/perfdata/graphitewriter.cpp b/lib/perfdata/graphitewriter.cpp index aee142e5a..9eb2e84a6 100644 --- a/lib/perfdata/graphitewriter.cpp +++ b/lib/perfdata/graphitewriter.cpp @@ -28,6 +28,9 @@ REGISTER_TYPE(GraphiteWriter); REGISTER_STATSFUNCTION(GraphiteWriter, &GraphiteWriter::StatsFunc); +/* + * Enable HA capabilities once the config object is loaded. + */ void GraphiteWriter::OnConfigLoaded() { ObjectImpl::OnConfigLoaded(); @@ -44,6 +47,12 @@ void GraphiteWriter::OnConfigLoaded() } } +/** + * Feature stats interface + * + * @param status Key value pairs for feature stats + * @param perfdata Array of PerfdataValue objects + */ void GraphiteWriter::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata) { DictionaryData nodes; @@ -65,6 +74,9 @@ void GraphiteWriter::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& status->Set("graphitewriter", new Dictionary(std::move(nodes))); } +/** + * Resume is equivalent to Start, but with HA capabilities to resume at runtime. + */ void GraphiteWriter::Resume() { ObjectImpl::Resume(); @@ -86,7 +98,9 @@ void GraphiteWriter::Resume() Checkable::OnNewCheckResult.connect(std::bind(&GraphiteWriter::CheckResultHandler, this, _1, _2)); } -/* Pause is equivalent to Stop, but with HA capabilities to resume at runtime. */ +/** + * Pause is equivalent to Stop, but with HA capabilities to resume at runtime. + */ void GraphiteWriter::Pause() { m_ReconnectTimer.reset(); @@ -110,11 +124,21 @@ void GraphiteWriter::Pause() ObjectImpl::Pause(); } +/** + * Check if method is called inside the WQ thread. + */ void GraphiteWriter::AssertOnWorkQueue() { ASSERT(m_WorkQueue.IsWorkerThread()); } +/** + * Exception handler for the WQ. + * + * Closes the connection if connected. + * + * @param exp Exception pointer + */ void GraphiteWriter::ExceptionHandler(boost::exception_ptr exp) { Log(LogCritical, "GraphiteWriter", "Exception during Graphite operation: Verify that your backend is operational!"); @@ -123,12 +147,17 @@ void GraphiteWriter::ExceptionHandler(boost::exception_ptr exp) << "Exception during Graphite operation: " << DiagnosticInformation(std::move(exp)); if (GetConnected()) { - m_Stream->Close(); + m_Stream->close(); SetConnected(false); } } +/** + * Reconnect method, stops when the feature is paused in HA zones. + * + * Called inside the WQ. + */ void GraphiteWriter::Reconnect() { AssertOnWorkQueue(); @@ -141,6 +170,9 @@ void GraphiteWriter::Reconnect() ReconnectInternal(); } +/** + * Reconnect method, connects to a TCP Stream + */ void GraphiteWriter::ReconnectInternal() { double startTime = Utility::GetTime(); @@ -152,20 +184,17 @@ void GraphiteWriter::ReconnectInternal() if (GetConnected()) return; - TcpSocket::Ptr socket = new TcpSocket(); - Log(LogNotice, "GraphiteWriter") << "Reconnecting to Graphite on host '" << GetHost() << "' port '" << GetPort() << "'."; - try { - socket->Connect(GetHost(), GetPort()); - } catch (const std::exception& ex) { - Log(LogCritical, "GraphiteWriter") - << "Can't connect to Graphite on host '" << GetHost() << "' port '" << GetPort() << "'."; - throw ex; - } + m_Stream = std::make_shared(IoEngine::Get().GetIoService()); - m_Stream = new NetworkStream(socket); + try { + icinga::Connect(m_Stream->lowest_layer(), GetHost(), GetPort()); + } catch (const std::exception& ex) { + Log(LogWarning, "GraphiteWriter") + << "Can't connect to Graphite on host '" << GetHost() << "' port '" << GetPort() << ".'"; + } SetConnected(true); @@ -173,14 +202,24 @@ void GraphiteWriter::ReconnectInternal() << "Finished reconnecting to Graphite in " << std::setw(2) << Utility::GetTime() - startTime << " second(s)."; } +/** + * Reconnect handler called by the timer. + * + * Enqueues a reconnect task into the WQ. + */ void GraphiteWriter::ReconnectTimerHandler() { if (IsPaused()) return; - m_WorkQueue.Enqueue(std::bind(&GraphiteWriter::Reconnect, this), PriorityNormal); + m_WorkQueue.Enqueue(std::bind(&GraphiteWriter::Reconnect, this), PriorityHigh); } +/** + * Disconnect the stream. + * + * Called inside the WQ. + */ void GraphiteWriter::Disconnect() { AssertOnWorkQueue(); @@ -188,16 +227,27 @@ void GraphiteWriter::Disconnect() DisconnectInternal(); } +/** + * Disconnect the stream. + * + * Called outside the WQ. + */ void GraphiteWriter::DisconnectInternal() { if (!GetConnected()) return; - m_Stream->Close(); + m_Stream->close(); SetConnected(false); } +/** + * Check result event handler, checks whether feature is not paused in HA setups. + * + * @param checkable Host/Service object + * @param cr Check result including performance data + */ void GraphiteWriter::CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) { if (IsPaused()) @@ -206,6 +256,14 @@ void GraphiteWriter::CheckResultHandler(const Checkable::Ptr& checkable, const C m_WorkQueue.Enqueue(std::bind(&GraphiteWriter::CheckResultHandlerInternal, this, checkable, cr)); } +/** + * Check result event handler, prepares metadata and perfdata values and calls Send*() + * + * Called inside the WQ. + * + * @param checkable Host/Service object + * @param cr Check result including performance data + */ void GraphiteWriter::CheckResultHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) { AssertOnWorkQueue(); @@ -262,6 +320,14 @@ void GraphiteWriter::CheckResultHandlerInternal(const Checkable::Ptr& checkable, SendPerfdata(checkable, prefixPerfdata, cr, ts); } +/** + * Parse performance data from check result and call SendMetric() + * + * @param checkable Host/service object + * @param prefix Metric prefix string + * @param cr Check result including performance data + * @param ts Timestamp when the check result was created + */ void GraphiteWriter::SendPerfdata(const Checkable::Ptr& checkable, const String& prefix, const CheckResult::Ptr& cr, double ts) { Array::Ptr perfdata = cr->GetPerformanceData(); @@ -306,8 +372,19 @@ void GraphiteWriter::SendPerfdata(const Checkable::Ptr& checkable, const String& } } +/** + * Computes metric data and sends to Graphite + * + * @param checkable Host/service object + * @param prefix Computed metric prefix string + * @param name Metric name + * @param value Metric value + * @param ts Timestamp when the check result was created + */ void GraphiteWriter::SendMetric(const Checkable::Ptr& checkable, const String& prefix, const String& name, double value, double ts) { + namespace asio = boost::asio; + std::ostringstream msgbuf; msgbuf << prefix << "." << name << " " << Convert::ToString(value) << " " << static_cast(ts); @@ -316,7 +393,6 @@ void GraphiteWriter::SendMetric(const Checkable::Ptr& checkable, const String& p // do not send \n to debug log msgbuf << "\n"; - String metric = msgbuf.str(); boost::mutex::scoped_lock lock(m_StreamMutex); @@ -324,7 +400,8 @@ void GraphiteWriter::SendMetric(const Checkable::Ptr& checkable, const String& p return; try { - m_Stream->Write(metric.CStr(), metric.GetLength()); + asio::write(*m_Stream, asio::buffer(msgbuf.str())); + m_Stream->flush(); } catch (const std::exception& ex) { Log(LogCritical, "GraphiteWriter") << "Cannot write to TCP socket on host '" << GetHost() << "' port '" << GetPort() << "'."; @@ -333,6 +410,14 @@ void GraphiteWriter::SendMetric(const Checkable::Ptr& checkable, const String& p } } +/** + * Escape metric tree elements + * + * Dots are not allowed, e.g. in host names + * + * @param str Metric part name + * @return Escape string + */ String GraphiteWriter::EscapeMetric(const String& str) { String result = str; @@ -346,6 +431,14 @@ String GraphiteWriter::EscapeMetric(const String& str) return result; } +/** + * Escape metric label + * + * Dots are allowed - users can create trees from perfdata labels + * + * @param str Metric label name + * @return Escaped string + */ String GraphiteWriter::EscapeMetricLabel(const String& str) { String result = str; @@ -359,6 +452,12 @@ String GraphiteWriter::EscapeMetricLabel(const String& str) return result; } +/** + * Escape macro metrics found via host/service name templates + * + * @param value Array or string with macro metric names + * @return Escaped string. Arrays are joined with dots. + */ Value GraphiteWriter::EscapeMacroMetric(const Value& value) { if (value.IsObjectType()) { @@ -375,6 +474,12 @@ Value GraphiteWriter::EscapeMacroMetric(const Value& value) return EscapeMetric(value); } +/** + * Validate the configuration setting 'host_name_template' + * + * @param lvalue String containing runtime macros. + * @param utils Helper, unused + */ void GraphiteWriter::ValidateHostNameTemplate(const Lazy& lvalue, const ValidationUtils& utils) { ObjectImpl::ValidateHostNameTemplate(lvalue, utils); @@ -383,6 +488,12 @@ void GraphiteWriter::ValidateHostNameTemplate(const Lazy& lvalue, const BOOST_THROW_EXCEPTION(ValidationError(this, { "host_name_template" }, "Closing $ not found in macro format string '" + lvalue() + "'.")); } +/** + * Validate the configuration setting 'service_name_template' + * + * @param lvalue String containing runtime macros. + * @param utils Helper, unused + */ void GraphiteWriter::ValidateServiceNameTemplate(const Lazy& lvalue, const ValidationUtils& utils) { ObjectImpl::ValidateServiceNameTemplate(lvalue, utils); diff --git a/lib/perfdata/graphitewriter.hpp b/lib/perfdata/graphitewriter.hpp index 2756c0fd4..42f741cee 100644 --- a/lib/perfdata/graphitewriter.hpp +++ b/lib/perfdata/graphitewriter.hpp @@ -37,7 +37,7 @@ protected: void Pause() override; private: - Stream::Ptr m_Stream; + std::shared_ptr m_Stream; boost::mutex m_StreamMutex; WorkQueue m_WorkQueue{10000000, 1}; From ba44c3921c205b2393b7fe5cdbbbef3c1d3c724a Mon Sep 17 00:00:00 2001 From: Michael Friedrich Date: Tue, 28 May 2019 13:03:34 +0200 Subject: [PATCH 08/10] Quality: Remove old MakeSSLContext() interface --- lib/base/tlsutility.cpp | 19 ------------------- lib/base/tlsutility.hpp | 7 ++++++- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/lib/base/tlsutility.cpp b/lib/base/tlsutility.cpp index 59bf54bf0..cc6fe8c17 100644 --- a/lib/base/tlsutility.cpp +++ b/lib/base/tlsutility.cpp @@ -129,25 +129,6 @@ static void SetupSslContext(SSL_CTX *sslContext, const String& pubkey, const Str } } -/** - * Initializes an SSL context using the specified certificates. - * - * @param pubkey The public key. - * @param privkey The matching private key. - * @param cakey CA certificate chain file. - * @returns An SSL context. - */ -std::shared_ptr MakeSSLContext(const String& pubkey, const String& privkey, const String& cakey) -{ - InitializeOpenSSL(); - - std::shared_ptr sslContext = std::shared_ptr(SSL_CTX_new(SSLv23_method()), SSL_CTX_free); - - SetupSslContext(sslContext.get(), pubkey, privkey, cakey); - - return sslContext; -} - /** * Initializes an SSL context using the specified certificates. * diff --git a/lib/base/tlsutility.hpp b/lib/base/tlsutility.hpp index 69b10786c..de7033311 100644 --- a/lib/base/tlsutility.hpp +++ b/lib/base/tlsutility.hpp @@ -21,25 +21,30 @@ namespace icinga { void InitializeOpenSSL(); -std::shared_ptr MakeSSLContext(const String& pubkey = String(), const String& privkey = String(), const String& cakey = String()); + std::shared_ptr MakeAsioSslContext(const String& pubkey = String(), const String& privkey = String(), const String& cakey = String()); void AddCRLToSSLContext(const std::shared_ptr& context, const String& crlPath); void SetCipherListToSSLContext(const std::shared_ptr& context, const String& cipherList); void SetTlsProtocolminToSSLContext(const std::shared_ptr& context, const String& tlsProtocolmin); + String GetCertificateCN(const std::shared_ptr& certificate); std::shared_ptr GetX509Certificate(const String& pemfile); int MakeX509CSR(const String& cn, const String& keyfile, const String& csrfile = String(), const String& certfile = String(), bool ca = false); std::shared_ptr CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PKEY *cakey, bool ca); + String GetIcingaCADir(); String CertificateToString(const std::shared_ptr& cert); + std::shared_ptr StringToCertificate(const String& cert); std::shared_ptr CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject); std::shared_ptr CreateCertIcingaCA(const std::shared_ptr& cert); + String PBKDF2_SHA1(const String& password, const String& salt, int iterations); String PBKDF2_SHA256(const String& password, const String& salt, int iterations); String SHA1(const String& s, bool binary = false); String SHA256(const String& s); String RandomString(int length); + bool VerifyCertificate(const std::shared_ptr& caCertificate, const std::shared_ptr& certificate); class openssl_error : virtual public std::exception, virtual public boost::exception { }; From 120aba3919cd733d931d39a2532669e4dbd6e938 Mon Sep 17 00:00:00 2001 From: Michael Friedrich Date: Tue, 28 May 2019 13:46:19 +0200 Subject: [PATCH 09/10] Quality: Removed unused HttpChunkedEncoding class --- lib/remote/CMakeLists.txt | 1 - lib/remote/httpchunkedencoding.cpp | 66 ------------------------------ lib/remote/httpchunkedencoding.hpp | 37 ----------------- 3 files changed, 104 deletions(-) delete mode 100644 lib/remote/httpchunkedencoding.cpp delete mode 100644 lib/remote/httpchunkedencoding.hpp diff --git a/lib/remote/CMakeLists.txt b/lib/remote/CMakeLists.txt index bd0884876..da0006aa1 100644 --- a/lib/remote/CMakeLists.txt +++ b/lib/remote/CMakeLists.txt @@ -25,7 +25,6 @@ set(remote_SOURCES eventqueue.cpp eventqueue.hpp eventshandler.cpp eventshandler.hpp filterutility.cpp filterutility.hpp - httpchunkedencoding.cpp httpchunkedencoding.hpp httphandler.cpp httphandler.hpp httpserverconnection.cpp httpserverconnection.hpp httputility.cpp httputility.hpp diff --git a/lib/remote/httpchunkedencoding.cpp b/lib/remote/httpchunkedencoding.cpp deleted file mode 100644 index 2b52636aa..000000000 --- a/lib/remote/httpchunkedencoding.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ - -#include "remote/httpchunkedencoding.hpp" -#include - -using namespace icinga; - -StreamReadStatus HttpChunkedEncoding::ReadChunkFromStream(const Stream::Ptr& stream, - char **data, size_t *size, ChunkReadContext& context, bool may_wait) -{ - if (context.LengthIndicator == -1) { - String line; - StreamReadStatus status = stream->ReadLine(&line, context.StreamContext, may_wait); - may_wait = false; - - if (status != StatusNewItem) - return status; - - std::stringstream msgbuf; - msgbuf << std::hex << line; - msgbuf >> context.LengthIndicator; - - if (context.LengthIndicator < 0) - BOOST_THROW_EXCEPTION(std::invalid_argument("HTTP chunk length must not be negative.")); - } - - StreamReadContext& scontext = context.StreamContext; - if (scontext.Eof) - return StatusEof; - - if (scontext.MustRead) { - if (!scontext.FillFromStream(stream, may_wait)) { - scontext.Eof = true; - return StatusEof; - } - - scontext.MustRead = false; - } - - size_t NewlineLength = context.LengthIndicator ? 2 : 0; - - if (scontext.Size < (size_t)context.LengthIndicator + NewlineLength) { - scontext.MustRead = true; - return StatusNeedData; - } - - *data = new char[context.LengthIndicator]; - *size = context.LengthIndicator; - memcpy(*data, scontext.Buffer, context.LengthIndicator); - - scontext.DropData(context.LengthIndicator + NewlineLength); - context.LengthIndicator = -1; - - return StatusNewItem; -} - -void HttpChunkedEncoding::WriteChunkToStream(const Stream::Ptr& stream, const char *data, size_t count) -{ - std::ostringstream msgbuf; - msgbuf << std::hex << count << "\r\n"; - String lengthIndicator = msgbuf.str(); - stream->Write(lengthIndicator.CStr(), lengthIndicator.GetLength()); - stream->Write(data, count); - if (count > 0) - stream->Write("\r\n", 2); -} diff --git a/lib/remote/httpchunkedencoding.hpp b/lib/remote/httpchunkedencoding.hpp deleted file mode 100644 index 70cd58ca4..000000000 --- a/lib/remote/httpchunkedencoding.hpp +++ /dev/null @@ -1,37 +0,0 @@ -/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ - -#ifndef HTTPCHUNKEDENCODING_H -#define HTTPCHUNKEDENCODING_H - -#include "remote/i2-remote.hpp" -#include "base/stream.hpp" - -namespace icinga -{ - -struct ChunkReadContext -{ - StreamReadContext& StreamContext; - int LengthIndicator; - - ChunkReadContext(StreamReadContext& scontext) - : StreamContext(scontext), LengthIndicator(-1) - { } -}; - -/** - * HTTP chunked encoding. - * - * @ingroup remote - */ -struct HttpChunkedEncoding -{ - static StreamReadStatus ReadChunkFromStream(const Stream::Ptr& stream, - char **data, size_t *size, ChunkReadContext& ccontext, bool may_wait = false); - static void WriteChunkToStream(const Stream::Ptr& stream, const char *data, size_t count); - -}; - -} - -#endif /* HTTPCHUNKEDENCODING_H */ From f5bc9b469c2796bc4948ac7287f51fa2caded5b2 Mon Sep 17 00:00:00 2001 From: Michael Friedrich Date: Wed, 29 May 2019 14:17:36 +0200 Subject: [PATCH 10/10] Quality: Mark NetworkStream, TcpSocket & UnixSocket classes as deprecated They're used inside the Livestatus feature which needs rework. --- lib/base/networkstream.hpp | 2 +- lib/base/tcpsocket.hpp | 7 ++++++- lib/base/unixsocket.hpp | 5 +++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/base/networkstream.hpp b/lib/base/networkstream.hpp index 06eb9501e..453d7ad0b 100644 --- a/lib/base/networkstream.hpp +++ b/lib/base/networkstream.hpp @@ -11,7 +11,7 @@ namespace icinga { /** - * A network stream. + * A network stream. DEPRECATED - Use Boost ASIO instead. * * @ingroup base */ diff --git a/lib/base/tcpsocket.hpp b/lib/base/tcpsocket.hpp index 0f8334f01..668e7882b 100644 --- a/lib/base/tcpsocket.hpp +++ b/lib/base/tcpsocket.hpp @@ -12,7 +12,7 @@ namespace icinga { /** - * A TCP socket. + * A TCP socket. DEPRECATED - Use Boost ASIO instead. * * @ingroup base */ @@ -27,6 +27,11 @@ public: void Connect(const String& node, const String& service); }; +/** + * TCP Connect based on Boost ASIO. + * + * @ingroup base + */ template void Connect(Socket& socket, const String& node, const String& service) { diff --git a/lib/base/unixsocket.hpp b/lib/base/unixsocket.hpp index 5d6df8fa8..80a9f255e 100644 --- a/lib/base/unixsocket.hpp +++ b/lib/base/unixsocket.hpp @@ -9,6 +9,11 @@ namespace icinga { +/** + * A TCP socket. DEPRECATED - Use Boost ASIO instead. + * + * @ingroup base + */ class UnixSocket final : public Socket { public: