Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1009,7 +1009,6 @@ set(NSCP_DEF_PLUGIN_LIB
${PLUGIN_API_TARGET}
)

set(ALL_LIB_NAMES)
message(STATUS "Adding libraries")
file(
GLOB ALL_LIB_PROJECTS
Expand All @@ -1035,10 +1034,6 @@ foreach(CURRENT_LIB ${ALL_LIB_PROJECTS})
endif()
message(STATUS " + Library: ${CURRENT_LIB_PATH} (${CURRENT_LIB_NAME})")
add_subdirectory("${CURRENT_LIB_PATH}")
set(ALL_LIB_NAMES
${ALL_LIB_NAMES}
${CURRENT_LIB_NAME}
)
endforeach(
CURRENT_LIB
${ALL_LIB_PROJECTS}
Expand Down
13 changes: 11 additions & 2 deletions build.md
Original file line number Diff line number Diff line change
Expand Up @@ -736,8 +736,17 @@ be swallowed by the capture buffer and never shown.
Build a sanitizer-instrumented tree with `tools/sanitizers/run.sh`. It
configures `build-<sanitizers>/` with `-DNSCP_SANITIZE` and
`-DCMAKE_BUILD_TYPE=RelWithDebInfo`, builds the whole daemon **and all its
modules** (not just the `*_test` binaries), then runs the C++ unit tests under
the sanitizer:
modules** (not just the `*_test` binaries), then runs every `*_test` ctest
target under the sanitizer. That includes the C++ unit tests **and** a set of
Lua acceptance tests (`lua_*_test`) that drive the built `nscp` over the Lua
scripting API — exercising real module code paths (check dispatch, the NRPE
client/server round-trip, etc.) so leaks/UB in those paths are caught too. They
get the same baked-in sanitizer `ENVIRONMENT` as the C++ tests:

> Note: the Python acceptance tests are intentionally **not** part of this run.
> The embedded `boost::python` build SEGVs under AddressSanitizer (a
> toolchain-level incompatibility, not a real NSClient++ bug), so the Lua
> acceptance tests cover the scripting-driven paths instead.

```bash
# From the repo root. ASan + UBSan by default -> build-address+undefined/
Expand Down
56 changes: 35 additions & 21 deletions build/cmake/dependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -86,38 +86,46 @@ set(NSCP_WEB_BACKEND
)
set_property(CACHE NSCP_WEB_BACKEND PROPERTY STRINGS mongoose beast)

# Decide whether the nscp_mongoose web library can be built. It is only
# built when the selected backend's dependencies are satisfied:
# mongoose -> the vendored mongoose source is found
# beast -> OpenSSL is available (ServerBeastImpl includes Boost.Asio
# SSL headers, which need OpenSSL headers/libs at compile time
# even when TLS is not enabled at runtime)
# When they are not, we skip building nscp_mongoose rather than failing the
# whole configure; modules/WEBServer/CMakeLists.txt raises a targeted error
# if it is asked to build without the library.
set(NSCP_MONGOOSE_AVAILABLE FALSE)
if(NSCP_WEB_BACKEND STREQUAL "mongoose")
find_package(Mongoose)
# Surface a clear error here instead of letting MONGOOSE_INCLUDE_DIR-NOTFOUND
# propagate into source lists (`${MONGOOSE_INCLUDE_DIR}/mongoose.c`), which
# otherwise fails much later with a cryptic "Cannot find source file".
if(NOT MONGOOSE_FOUND)
if(MONGOOSE_FOUND)
set(NSCP_MONGOOSE_AVAILABLE TRUE)
else()
message(
FATAL_ERROR
"NSCP_WEB_BACKEND=mongoose requires the vendored mongoose source.\n"
"Either set MONGOOSE_SOURCE_DIR (see build.md), or switch to the\n"
"Beast backend with -DNSCP_WEB_BACKEND=beast (the default on Linux\n"
"CI builds)."
STATUS
"NSCP_WEB_BACKEND=mongoose but the vendored mongoose source was not "
"found; nscp_mongoose (and the WEBServer module) will not be built. "
"Set MONGOOSE_SOURCE_DIR (see build.md), or switch to the Beast "
"backend with -DNSCP_WEB_BACKEND=beast (the default on Linux CI "
"builds)."
)
endif()
elseif(NSCP_WEB_BACKEND STREQUAL "beast")
# Beast is header-only; the Boost components (coroutine + context)
# needed by ServerBeastImpl are added below.
# ServerBeastImpl also includes Boost.Asio SSL headers, which require
# OpenSSL headers/libs at compile time even when TLS is not enabled at
# runtime. Fail during configure instead of producing a later compile
# error from missing openssl/ssl.h or unresolved SSL symbols.
if(NOT OPENSSL_FOUND)
if(OPENSSL_FOUND)
set(NSCP_MONGOOSE_AVAILABLE TRUE)
message(STATUS "WEB backend: Boost.Beast (mongoose download skipped)")
else()
message(
FATAL_ERROR
"NSCP_WEB_BACKEND=beast requires OpenSSL.\n"
"ServerBeastImpl includes Boost.Asio SSL headers, so OpenSSL must\n"
"be available at configure/build time even if TLS is not enabled\n"
"at runtime.\n"
STATUS
"NSCP_WEB_BACKEND=beast but OpenSSL was not found; nscp_mongoose "
"(and the WEBServer module) will not be built. ServerBeastImpl "
"includes Boost.Asio SSL headers, so OpenSSL must be available at "
"configure/build time even if TLS is not enabled at runtime. "
"Install/configure OpenSSL, or switch to -DNSCP_WEB_BACKEND=mongoose."
)
endif()
message(STATUS "WEB backend: Boost.Beast (mongoose download skipped)")
else()
message(FATAL_ERROR "Unknown NSCP_WEB_BACKEND='${NSCP_WEB_BACKEND}' (expected mongoose | beast)")
endif()
Expand All @@ -132,8 +140,14 @@ endif()
# unused work — and would force the package list (libboost-coroutine,
# libboost-context on Debian; analogous on RPM) on environments that
# only ever build the mongoose backend (typically Windows).
#
# Gate on NSCP_MONGOOSE_AVAILABLE too: when the Beast backend is selected but
# its dependencies are missing (e.g. OpenSSL not found) the web library is
# skipped, so ServerBeastImpl is never compiled. Requesting coroutine/context
# as *required* COMPONENTS below would then turn an optional, disabled web
# build into a hard Boost.Coroutine/Context requirement and fail configure.
set(_nscp_extra_boost_components)
if(NSCP_WEB_BACKEND STREQUAL "beast")
if(NSCP_WEB_BACKEND STREQUAL "beast" AND NSCP_MONGOOSE_AVAILABLE)
list(APPEND _nscp_extra_boost_components coroutine context)
endif()

Expand Down
33 changes: 33 additions & 0 deletions build/cmake/functions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,39 @@ function(NSCP_CREATE_TEST _TARGET)
nscp_add_test(${_TARGET})
endfunction()

# Register a Lua acceptance test that drives the built `nscp` binary:
# nscp unit --language lua --script <script>
# The test name should end in `_test` so `ctest -R '_test$'` (used by
# tools/sanitizers/run.sh) picks it up alongside the C++ unit tests. It runs
# under the same NSCP_SANITIZER_TEST_ENV as those tests, so leaks/UB in the
# Lua-driven code paths are caught. Requires the `nscp` target, the LUAScript
# module and the copy_scripts target, all built as part of the default build.
function(NSCP_ADD_LUA_TEST _NAME _SCRIPT)
if(NOT NSCP_BUILD_TESTS)
return()
endif()
add_test(
NAME ${_NAME}
COMMAND
$<TARGET_FILE:nscp>
unit
--language lua
--script ${_SCRIPT}
--path-override scripts=${BUILD_TARGET_ROOT_PATH}/scripts
# Run from the build root so ${base-path} (the external-scripts working
# dir) makes relative script paths like "scripts/check_test.sh" resolve.
WORKING_DIRECTORY ${BUILD_TARGET_ROOT_PATH}
)
if(NSCP_SANITIZER_TEST_ENV)
set_tests_properties(
${_NAME}
PROPERTIES
ENVIRONMENT
"${NSCP_SANITIZER_TEST_ENV}"
)
endif()
endfunction()

macro(NSCP_MAKE_EXE_SBIN _TARGET _SRCS)
NSCP_MAKE_EXE(${_TARGET} "${_SRCS}" ${SBIN_TARGET_FOLDER})
endmacro()
Expand Down
24 changes: 24 additions & 0 deletions include/lua/lua_core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ extern "C" {
#include <lua.h>
}

#include <boost/thread/recursive_mutex.hpp>

#include <list>
#include <lua/lua_script.hpp>
#include <memory>
Expand All @@ -33,6 +35,28 @@ extern "C" {
namespace lua {
typedef scripts::script_information<lua_traits> script_information;

// A process-wide "GIL" that serialises all Lua execution. The Lua runtime shares
// a single lua_State across threads with no internal locking, so any handler that
// fires on another thread (a Scheduler tick, an NSCA inbox delivery, a relayed
// sub-query arriving on a server thread) would otherwise corrupt the interpreter
// if it ran while the main script is mid-Lua. Every entry into Lua holds the GIL
// for the duration of its pcall (guard) and releases it only around blocking /
// re-entrant core calls and nscp.sleep (release) - the same model as CPython's
// GIL. A recursive mutex keeps a missed release on a same-thread re-entry from
// self-deadlocking; the genuine cross-thread re-entry points (the query/submit
// family) always release explicitly.
struct lua_gil {
static boost::recursive_mutex &mutex();
struct guard {
guard() { mutex().lock(); }
~guard() { mutex().unlock(); }
};
struct release {
release() { mutex().unlock(); }
~release() { mutex().lock(); }
};
};

struct lua_runtime_plugin {
virtual void load(lua::lua_wrapper &instance) = 0;
virtual void unload(lua::lua_wrapper &instance) = 0;
Expand Down
2 changes: 2 additions & 0 deletions include/lua/lua_script.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ namespace core_wrapper {
int create_core(lua_State *L);
int create_pb_query(lua_State *L);
int simple_query(lua_State *L);
int query_target(lua_State *L);
int query_forward(lua_State *L);
int query(lua_State *L);
int simple_exec(lua_State *L);
int exec(lua_State *L);
Expand Down
10 changes: 10 additions & 0 deletions include/scripts/script_interface.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ struct core_provider {
virtual bool submit_simple_message(const std::string channel, const std::string command, const NSCAPI::nagiosReturn code, const std::string &message,
const std::string &perf, std::string &response) = 0;
virtual NSCAPI::nagiosReturn simple_query(const std::string &command, const std::list<std::string> &argument, std::string &msg, std::string &perf) = 0;
// Same as simple_query but routed to a specific target (sets the request header
// recipient/destination), so relay modules (NRPE/NSCA/NSCP client targets) pick it up.
virtual NSCAPI::nagiosReturn simple_query(const std::string &target, const std::string &command, const std::list<std::string> &argument, std::string &msg,
std::string &perf) = 0;
// Forward a command verbatim to a relay target via the header "<protocol>_forward"
// command (e.g. "nrpe_forward"). Unlike simple_query(target, ...) the inner payload
// command + arguments are sent on the wire untouched, so a remote handler actually
// receives them. Returns the flattened (code, msg, perf) of the relayed response.
virtual NSCAPI::nagiosReturn query_forward(const std::string &forward_command, const std::string &target, const std::string &command,
const std::list<std::string> &argument, std::string &msg, std::string &perf) = 0;
virtual bool exec_simple_command(const std::string target, const std::string command, const std::list<std::string> &argument,
std::list<std::string> &result) = 0;
virtual bool exec_command(const std::string target, const std::string &request, std::string &response) = 0;
Expand Down
43 changes: 43 additions & 0 deletions include/scripts/script_nscp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,49 @@ NSCAPI::nagiosReturn scripts::nscp::core_provider_impl::simple_query(const std::
return nscapi::protobuf::functions::parse_simple_query_response(response, msg, perf, nscapi::protobuf::functions::no_truncation);
}

NSCAPI::nagiosReturn scripts::nscp::core_provider_impl::simple_query(const std::string &target, const std::string &command, const std::list<std::string> &argument,
std::string &msg, std::string &perf) {
std::string request, response;
PB::Commands::QueryRequestMessage message;
auto *header = message.mutable_header();
header->set_recipient_id(target);
header->set_destination_id(target);
auto *payload = message.add_payload();
payload->set_command(command);
for (const std::string &s : argument) {
payload->add_arguments(s);
}
message.SerializeToString(&request);
if (!core_->query(request, response)) {
msg = "Command failed.";
return NSCAPI::query_return_codes::returnUNKNOWN;
}
return nscapi::protobuf::functions::parse_simple_query_response(response, msg, perf, nscapi::protobuf::functions::no_truncation);
}

NSCAPI::nagiosReturn scripts::nscp::core_provider_impl::query_forward(const std::string &forward_command, const std::string &target, const std::string &command,
const std::list<std::string> &argument, std::string &msg, std::string &perf) {
std::string request, response;
PB::Commands::QueryRequestMessage message;
auto *header = message.mutable_header();
// The header command selects the relay's "forward as-is" path (e.g. nrpe_forward),
// which ships the payload below verbatim instead of re-parsing it locally.
header->set_command(forward_command);
header->set_recipient_id(target);
header->set_destination_id(target);
auto *payload = message.add_payload();
payload->set_command(command);
for (const std::string &s : argument) {
payload->add_arguments(s);
}
message.SerializeToString(&request);
if (!core_->query(request, response)) {
msg = "Command failed.";
return NSCAPI::query_return_codes::returnUNKNOWN;
}
return nscapi::protobuf::functions::parse_simple_query_response(response, msg, perf, nscapi::protobuf::functions::no_truncation);
}

bool scripts::nscp::core_provider_impl::exec_simple_command(const std::string target, const std::string command, const std::list<std::string> &argument,
std::list<std::string> &result) {
std::string request, response;
Expand Down
4 changes: 4 additions & 0 deletions include/scripts/script_nscp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ struct core_provider_impl : public core_provider {
virtual bool submit_simple_message(const std::string channel, const std::string command, const NSCAPI::nagiosReturn code, const std::string &message,
const std::string &perf, std::string &response);
virtual NSCAPI::nagiosReturn simple_query(const std::string &command, const std::list<std::string> &argument, std::string &msg, std::string &perf);
virtual NSCAPI::nagiosReturn simple_query(const std::string &target, const std::string &command, const std::list<std::string> &argument, std::string &msg,
std::string &perf);
virtual NSCAPI::nagiosReturn query_forward(const std::string &forward_command, const std::string &target, const std::string &command,
const std::list<std::string> &argument, std::string &msg, std::string &perf);
virtual bool exec_simple_command(const std::string target, const std::string command, const std::list<std::string> &argument, std::list<std::string> &result);
virtual bool exec_command(const std::string target, const std::string &request, std::string &response);
virtual bool query(const std::string &request, std::string &response);
Expand Down
15 changes: 13 additions & 2 deletions libs/lua_nscp/lua_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
#include <nscapi/protobuf/functions_status.hpp>
#include <nscapi/protobuf/functions_submit.hpp>

boost::recursive_mutex &lua::lua_gil::mutex() {
static boost::recursive_mutex m;
return m;
}

void lua::lua_runtime::register_query(const std::string &command, const std::string &description) {
throw lua_exception("The method or operation is not implemented(reg_query).");
}
Expand All @@ -24,6 +29,7 @@ void lua::lua_runtime::register_subscription(const std::string &channel, const s
void lua::lua_runtime::on_query(std::string command, script_information *information, lua::lua_traits::function_type function, bool simple,
const PB::Commands::QueryRequestMessage::Request &request, PB::Commands::QueryResponseMessage::Response *response,
const PB::Commands::QueryRequestMessage &request_message) {
lua_gil::guard gil;
lua_wrapper lua(prep_function(information, function));
int args = 2;
if (function.object_ref != 0) args = 3;
Expand Down Expand Up @@ -68,6 +74,7 @@ void lua::lua_runtime::on_query(std::string command, script_information *informa

void lua::lua_runtime::exec_main(script_information *information, const std::vector<std::string> &opts,
PB::Commands::ExecuteResponseMessage::Response *response) {
lua_gil::guard gil;
lua_wrapper lua(prep_function(information, "main"));
lua.push_array(opts);
if (lua.pcall(1, 2, 0) != 0) return nscapi::protobuf::functions::set_response_bad(*response, "Failed to handle command main: " + lua.pop_string());
Expand All @@ -86,6 +93,7 @@ void lua::lua_runtime::exec_main(script_information *information, const std::vec
void lua::lua_runtime::on_exec(std::string command, script_information *information, lua::lua_traits::function_type function, bool simple,
const PB::Commands::ExecuteRequestMessage::Request &request, PB::Commands::ExecuteResponseMessage::Response *response,
const PB::Commands::ExecuteRequestMessage &request_message) {
lua_gil::guard gil;
lua_wrapper lua(prep_function(information, function));
int args = 2;
if (function.object_ref != 0) args = 3;
Expand Down Expand Up @@ -128,9 +136,12 @@ void lua::lua_runtime::on_exec(std::string command, script_information *informat

void lua::lua_runtime::on_submit(std::string channel, script_information *information, lua::lua_traits::function_type function, bool simple,
const PB::Commands::QueryResponseMessage::Response &request, PB::Commands::SubmitResponseMessage::Response *response) {
lua_gil::guard gil;
lua_wrapper lua(prep_function(information, function));
int cmd_args = 1;
if (function.object_ref != 0) cmd_args = 2;
// cmd_args is the leading self argument (1 when the handler is a bound method,
// 0 otherwise); the fixed channel/command/... args are added on at each pcall.
int cmd_args = 0;
if (function.object_ref != 0) cmd_args = 1;
if (simple) {
lua.push_string(channel);
lua.push_string(request.command());
Expand Down
Loading
Loading