diff --git a/userspace/libsinsp/dumper.cpp b/userspace/libsinsp/dumper.cpp index 4bf467b4a9..bb76cab7de 100644 --- a/userspace/libsinsp/dumper.cpp +++ b/userspace/libsinsp/dumper.cpp @@ -20,6 +20,8 @@ limitations under the License. #include #include #include +#include +#include sinsp_dumper::sinsp_dumper() { m_dumper = NULL; @@ -67,6 +69,13 @@ void sinsp_dumper::open(sinsp* inspector, const std::string& filename, bool comp inspector->m_container_manager.dump_containers(*this); inspector->m_usergroup_manager.dump_users_groups(*this); + // ask registered ASYNC plugins for a dump of their state + for(auto& p : inspector->m_plugin_manager->plugins()) { + if(p->caps() & CAP_ASYNC) { + p->dump(*this); + } + } + m_nevts = 0; } @@ -87,6 +96,13 @@ void sinsp_dumper::fdopen(sinsp* inspector, int fd, bool compress) { inspector->m_container_manager.dump_containers(*this); inspector->m_usergroup_manager.dump_users_groups(*this); + // ask registered ASYNC plugins for a dump of their state + for(auto& p : inspector->m_plugin_manager->plugins()) { + if(p->caps() & CAP_ASYNC) { + p->dump(*this); + } + } + m_nevts = 0; } diff --git a/userspace/libsinsp/plugin.cpp b/userspace/libsinsp/plugin.cpp index 20d55ece07..25b5ec71fd 100644 --- a/userspace/libsinsp/plugin.cpp +++ b/userspace/libsinsp/plugin.cpp @@ -1175,3 +1175,72 @@ bool sinsp_plugin::set_async_event_handler(async_event_handler_t handler) { return rc == SS_PLUGIN_SUCCESS; } + +bool sinsp_plugin::dump(sinsp_dumper& dumper) { + if(!m_inited) { + throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name); + } + + if(!(m_caps & CAP_ASYNC)) { + throw sinsp_exception("plugin " + m_name + "without async events cap used as dumper"); + } + + if(!m_handle->api.dump) { + return false; + } + + m_async_dump_handler = [&dumper](auto e) { dumper.dump(e.get()); }; + + const auto callback = + [](ss_plugin_owner_t* o, const ss_plugin_event* e, char* err) -> ss_plugin_rc { + auto p = static_cast(o); + // We only support dumping of PPME_ASYNCEVENT_E events + if(e->type != PPME_ASYNCEVENT_E || e->nparams != 3) { + if(err) { + auto e = "malformed async event produced by plugin: " + p->name(); + strlcpy(err, e.c_str(), PLUGIN_MAX_ERRLEN); + } + return SS_PLUGIN_FAILURE; + } + + // Event name must be one of the async event names + auto name = (const char*)((uint8_t*)e + sizeof(ss_plugin_event) + 4 + 4 + 4 + 4); + if(p->async_event_names().find(name) == p->async_event_names().end()) { + if(err) { + auto e = "incompatible async event '" + std::string(name) + + "' produced by plugin: " + p->name(); + strlcpy(err, e.c_str(), PLUGIN_MAX_ERRLEN); + } + return SS_PLUGIN_FAILURE; + } + + try { + auto evt = std::make_unique(); + ASSERT(evt->get_scap_evt_storage() == nullptr); + evt->set_scap_evt_storage(new char[e->len]); + memcpy(evt->get_scap_evt_storage(), e, e->len); + evt->set_cpuid(0); + evt->set_num(0); + evt->set_scap_evt((scap_evt*)evt->get_scap_evt_storage()); + evt->init(); + // note: plugin ID and timestamp will be set by the inspector + p->m_async_dump_handler(std::move(evt)); + } catch(const std::exception& _e) { + if(err) { + strlcpy(err, _e.what(), PLUGIN_MAX_ERRLEN); + } + return SS_PLUGIN_FAILURE; + } catch(...) { + if(err) { + strlcpy(err, "unknwon error in dumping async event", PLUGIN_MAX_ERRLEN); + } + return SS_PLUGIN_FAILURE; + } + return SS_PLUGIN_SUCCESS; + }; + + if(m_handle->api.dump(m_state, this, callback) != SS_PLUGIN_SUCCESS) { + throw sinsp_exception("dump error for plugin '" + m_name + "' : " + m_last_owner_err); + } + return true; +} diff --git a/userspace/libsinsp/plugin.h b/userspace/libsinsp/plugin.h index 155860e096..6d2d02225e 100644 --- a/userspace/libsinsp/plugin.h +++ b/userspace/libsinsp/plugin.h @@ -25,6 +25,7 @@ limitations under the License. #include #include #include +#include #include #include #include @@ -161,6 +162,7 @@ class sinsp_plugin { sinsp_thread_pool::routine_id_t subscribe_routine(ss_plugin_routine_fn_t routine_fn, ss_plugin_routine_state_t* routine_state); bool unsubscribe_routine(sinsp_thread_pool::routine_id_t routine_id); + bool dump(sinsp_dumper& dumper); /** Event Sourcing **/ inline uint32_t id() const { return m_id; } @@ -206,6 +208,8 @@ class sinsp_plugin { using async_event_handler_t = std::function)>; + using async_dump_handler_t = std::function)>; + bool set_async_event_handler(async_event_handler_t handler); // note(jasondellaluce): we set these as protected in order to allow unit @@ -248,9 +252,14 @@ class sinsp_plugin { std::unordered_set m_async_event_names; std::atomic m_async_evt_handler; // note: we don't have thread-safe smart pointers + async_dump_handler_t m_async_dump_handler; + static ss_plugin_rc handle_plugin_async_event(ss_plugin_owner_t* o, const ss_plugin_event* evt, char* err); + static ss_plugin_rc handle_plugin_async_dump(ss_plugin_owner_t* o, + const ss_plugin_event* evt, + char* err); /** Generic helpers **/ void validate_config(std::string& config); diff --git a/userspace/libsinsp/sinsp.cpp b/userspace/libsinsp/sinsp.cpp index a578431bf0..4d9d9bf4c7 100644 --- a/userspace/libsinsp/sinsp.cpp +++ b/userspace/libsinsp/sinsp.cpp @@ -159,7 +159,7 @@ bool sinsp::is_initialstate_event(scap_evt* pevent) const { return pevent->type == PPME_CONTAINER_E || pevent->type == PPME_CONTAINER_JSON_E || pevent->type == PPME_CONTAINER_JSON_2_E || pevent->type == PPME_USER_ADDED_E || pevent->type == PPME_USER_DELETED_E || pevent->type == PPME_GROUP_ADDED_E || - pevent->type == PPME_GROUP_DELETED_E; + pevent->type == PPME_GROUP_DELETED_E || pevent->type == PPME_ASYNCEVENT_E; } void sinsp::consume_initialstate_events() { @@ -181,7 +181,7 @@ void sinsp::consume_initialstate_events() { if(res == SCAP_SUCCESS) { // Setting these to non-null will make sinsp::next use them as a scap event // to avoid a call to scap_next. In this way, we can avoid the state parsing phase - // once we reach a container-unrelated event. + // once we reach a non-initialstate event. m_replay_scap_evt = pevent; m_replay_scap_cpuid = pcpuid; m_replay_scap_flags = flags; @@ -228,9 +228,9 @@ void sinsp::init() { m_fds_to_remove.clear(); // - // If we're reading from file, we try to pre-parse the container events before + // If we're reading from file, we try to pre-parse all initial state-building events before // importing the thread table, so that thread table filtering will work with - // container filters + // full information. // if(is_capture()) { consume_initialstate_events(); diff --git a/userspace/libsinsp/sinsp_cycledumper.cpp b/userspace/libsinsp/sinsp_cycledumper.cpp index 158817e60c..566b359ab2 100644 --- a/userspace/libsinsp/sinsp_cycledumper.cpp +++ b/userspace/libsinsp/sinsp_cycledumper.cpp @@ -110,7 +110,7 @@ void sinsp_cycledumper::autodump_start(const std::string& dump_filename) { std::for_each(m_open_file_callbacks.begin(), m_open_file_callbacks.end(), std::ref(*this)); m_dumper->open(m_inspector, - dump_filename.c_str(), + dump_filename, m_compress ? SCAP_COMPRESSION_GZIP : SCAP_COMPRESSION_NONE); m_inspector->set_dumping(true); diff --git a/userspace/libsinsp/test/plugins.ut.cpp b/userspace/libsinsp/test/plugins.ut.cpp index ed8173a4ab..e0de0a3b16 100644 --- a/userspace/libsinsp/test/plugins.ut.cpp +++ b/userspace/libsinsp/test/plugins.ut.cpp @@ -378,6 +378,68 @@ TEST_F(sinsp_with_test_input, plugin_custom_source) { ASSERT_EQ(next_event(), nullptr); // EOF is expected } +class plugin_test_event_processor : public libsinsp::event_processor { +public: + explicit plugin_test_event_processor(const char* ev_name) { + num_async_evts = 0; + event_name = ev_name; + } + + void on_capture_start() override {} + + void process_event(sinsp_evt* evt, libsinsp::event_return rc) override { + if(evt->get_type() == PPME_ASYNCEVENT_E) { + // Retrieve internal event name + auto ev_name = evt->get_param(1)->as(); + if(ev_name == event_name) { + num_async_evts++; + } + } + } + + int num_async_evts; + +private: + std::string event_name; +}; + +// scenario: a plugin with dump capability is requested a dump and then the capture file is read. +// note: emscripten has trouble with the nodriver engine and async events +#if !defined(__EMSCRIPTEN__) +TEST_F(sinsp_with_test_input, plugin_dump) { + uint64_t max_count = 1; + uint64_t period_ns = 1000000; // 1ms + std::string async_pl_cfg = std::to_string(max_count) + ":" + std::to_string(period_ns); + register_plugin(&m_inspector, get_plugin_api_sample_syscall_async); + + // we will not use the test scap engine here, but open the src plugin instead + // note: we configure the plugin to just emit 1 event through its open params + m_inspector.open_nodriver(); + + auto evt = next_event(); + ASSERT_NE(evt, nullptr); + ASSERT_EQ(evt->get_type(), PPME_ASYNCEVENT_E); + + auto sinspdumper = sinsp_dumper(); + sinspdumper.open(&m_inspector, "test.scap", false); + sinspdumper.close(); + + m_inspector.close(); + + // Here we open a replay inspector just to trigger the initstate events parsing + auto replay_inspector = sinsp(); + // + auto processor = plugin_test_event_processor("sampleticker"); + replay_inspector.register_external_event_processor(processor); + ASSERT_NO_THROW(replay_inspector.open_savefile("test.scap")); + + ASSERT_EQ(processor.num_async_evts, 10); + + replay_inspector.close(); + remove("test.scap"); +} +#endif + TEST(sinsp_plugin, plugin_extract_compatibility) { std::string tmp; sinsp i; diff --git a/userspace/libsinsp/test/plugins/syscall_async.cpp b/userspace/libsinsp/test/plugins/syscall_async.cpp index b247b12ac7..7fc5092021 100644 --- a/userspace/libsinsp/test/plugins/syscall_async.cpp +++ b/userspace/libsinsp/test/plugins/syscall_async.cpp @@ -141,7 +141,7 @@ ss_plugin_rc plugin_set_async_event_handler(ss_plugin_t* s, err, PPME_ASYNCEVENT_E, 3, - 0, + (uint32_t)0, "unsupportedname", scap_const_sized_buffer{data, strlen(data) + 1}); ps->async_evt->tid = 1; @@ -161,7 +161,7 @@ ss_plugin_rc plugin_set_async_event_handler(ss_plugin_t* s, err, PPME_ASYNCEVENT_E, 3, - 0, + (uint32_t)0, name, scap_const_sized_buffer{data, strlen(data) + 1}); ps->async_evt->tid = 1; @@ -188,6 +188,43 @@ ss_plugin_rc plugin_set_async_event_handler(ss_plugin_t* s, return SS_PLUGIN_SUCCESS; } +ss_plugin_rc plugin_dump(ss_plugin_t* s, + ss_plugin_owner_t* owner, + const ss_plugin_async_event_handler_t handler) { + static uint8_t evt_buf[256]; + static ss_plugin_event* evt; + char err[PLUGIN_MAX_ERRLEN]; + const char* name = "sampleticker"; + + for(int i = 0; i < 10; i++) { + evt = (ss_plugin_event*)evt_buf; + + std::string data = "hello world #" + std::to_string(i); + + char error[SCAP_LASTERR_SIZE]; + int32_t encode_res = scap_event_encode_params( + scap_sized_buffer{evt, sizeof(evt_buf)}, + nullptr, + error, + PPME_ASYNCEVENT_E, + 3, + (uint32_t)0, + name, + scap_const_sized_buffer{(void*)data.c_str(), data.length() + 1}); + + if(encode_res == SCAP_FAILURE) { + return SS_PLUGIN_FAILURE; + } + if(SS_PLUGIN_SUCCESS != handler(owner, evt, err)) { + printf("sample_syscall_async: unexpected failure in sending asynchronous event " + "from plugin: %s\n", + err); + exit(1); + } + } + return SS_PLUGIN_SUCCESS; +} + } // anonymous namespace void get_plugin_api_sample_syscall_async(plugin_api& out) { @@ -203,4 +240,5 @@ void get_plugin_api_sample_syscall_async(plugin_api& out) { out.get_async_event_sources = plugin_get_async_event_sources; out.get_async_events = plugin_get_async_events; out.set_async_event_handler = plugin_set_async_event_handler; + out.dump = plugin_dump; } diff --git a/userspace/plugin/plugin_api.h b/userspace/plugin/plugin_api.h index d2e6f9e4a4..eb645d2887 100644 --- a/userspace/plugin/plugin_api.h +++ b/userspace/plugin/plugin_api.h @@ -29,7 +29,7 @@ extern "C" { // // todo(jasondellaluce): when/if major changes to v4, check and solve all todos #define PLUGIN_API_VERSION_MAJOR 3 -#define PLUGIN_API_VERSION_MINOR 9 +#define PLUGIN_API_VERSION_MINOR 10 #define PLUGIN_API_VERSION_PATCH 0 // @@ -1053,6 +1053,25 @@ typedef struct { ss_plugin_rc (*set_async_event_handler)(ss_plugin_t* s, ss_plugin_owner_t* owner, const ss_plugin_async_event_handler_t handler); + + // + // Called by the framework when a capture file dump is requested. + // + // Required: no + // Arguments: + // - s: the plugin state, returned by init(). Can be NULL. + // - owner: Opaque pointer to the plugin's owner. Must be passed + // as an argument to the async event function handler. + // - handler: Function handler to be used for sending events to be dumped + // to the plugin's owner. The handler must be invoked with + // the same owner opaque pointer passed to this function, and with + // an event pointer owned and controlled by the plugin. The event + // pointer is not retained by the handler after it returns. + // + // Return value: A ss_plugin_rc with values SS_PLUGIN_SUCCESS or SS_PLUGIN_FAILURE. + ss_plugin_rc (*dump)(ss_plugin_t* s, + ss_plugin_owner_t* owner, + const ss_plugin_async_event_handler_t handler); }; // Sets a new plugin configuration when provided by the framework. diff --git a/userspace/plugin/plugin_loader.c b/userspace/plugin/plugin_loader.c index 0de84ee864..5b312e2903 100644 --- a/userspace/plugin/plugin_loader.c +++ b/userspace/plugin/plugin_loader.c @@ -122,6 +122,7 @@ plugin_handle_t* plugin_load(const char* path, char* err) { SYM_RESOLVE(ret, get_async_event_sources); SYM_RESOLVE(ret, get_async_events); SYM_RESOLVE(ret, set_async_event_handler); + SYM_RESOLVE(ret, dump); SYM_RESOLVE(ret, set_config); SYM_RESOLVE(ret, get_metrics); SYM_RESOLVE(ret, capture_open);