From f9a925bb368dd92361f2dd90c10512e8309590ca Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Wed, 6 Nov 2024 11:49:13 +0100 Subject: [PATCH 1/8] new(userspace): added new plugin API capability to dump plugin state. Also, ASYNCEVENT_E will not be correctly pre-parsed at init time while reading from captures. Signed-off-by: Federico Di Pierro --- userspace/libsinsp/dumper.cpp | 12 ++++++++++++ userspace/libsinsp/plugin.cpp | 30 ++++++++++++++++++++++++++++++ userspace/libsinsp/plugin.h | 1 + userspace/libsinsp/sinsp.cpp | 8 ++++---- userspace/plugin/plugin_api.h | 15 +++++++++++++++ userspace/plugin/plugin_loader.c | 5 +++++ userspace/plugin/plugin_loader.h | 1 + 7 files changed, 68 insertions(+), 4 deletions(-) diff --git a/userspace/libsinsp/dumper.cpp b/userspace/libsinsp/dumper.cpp index 4bf467b4a9..8645d8cc4a 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,16 @@ 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); + // notify registered plugins of capture open + for(auto& p : inspector->m_plugin_manager->plugins()) { + if(p->caps() & CAP_DUMPING) { + if(!p->dump(*this)) { + throw sinsp_exception("dump error for plugin '" + p->name() + + "' : " + p->get_last_error()); + } + } + } + m_nevts = 0; } diff --git a/userspace/libsinsp/plugin.cpp b/userspace/libsinsp/plugin.cpp index 20d55ece07..7cc218dd0e 100644 --- a/userspace/libsinsp/plugin.cpp +++ b/userspace/libsinsp/plugin.cpp @@ -856,6 +856,36 @@ bool sinsp_plugin::capture_close() { return m_handle->api.capture_close(m_state, &in) == 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_handle->api.dump) { + return false; + } + + uint32_t num_evts; + ss_plugin_event** evts; + + if(m_handle->api.dump(m_state, &num_evts, &evts) != SS_PLUGIN_SUCCESS) { + return false; + } + for(int i = 0; i < num_evts; i++) { + auto e = evts[i]; + auto evt = sinsp_evt(); + 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(); + dumper.dump(&evt); + } + return true; +} + /** Event Source CAP **/ scap_source_plugin& sinsp_plugin::as_scap_source() { diff --git a/userspace/libsinsp/plugin.h b/userspace/libsinsp/plugin.h index 155860e096..655b2d1933 100644 --- a/userspace/libsinsp/plugin.h +++ b/userspace/libsinsp/plugin.h @@ -161,6 +161,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; } 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/plugin/plugin_api.h b/userspace/plugin/plugin_api.h index d2e6f9e4a4..5534104749 100644 --- a/userspace/plugin/plugin_api.h +++ b/userspace/plugin/plugin_api.h @@ -1104,6 +1104,21 @@ typedef struct { // Return value: A ss_plugin_rc with values SS_PLUGIN_SUCCESS or SS_PLUGIN_FAILURE. ss_plugin_rc (*capture_close)(ss_plugin_t* s, const ss_plugin_capture_listen_input* i); }; + + // Events dumping capability API + struct { + // + // Called by the framework when a capture file dump is requested. + // + // Required: yes + // Arguments: + // - s: the plugin state, returned by init(). Can be NULL. + // - evts: input containing vtables for performing table operations and + // subscribe/unsubscribe async routines + // + // Return value: A ss_plugin_rc with values SS_PLUGIN_SUCCESS or SS_PLUGIN_FAILURE. + ss_plugin_rc (*dump)(ss_plugin_t* s, uint32_t* nevts, ss_plugin_event*** evts); + }; } plugin_api; #ifdef __cplusplus diff --git a/userspace/plugin/plugin_loader.c b/userspace/plugin/plugin_loader.c index 0de84ee864..afb292be57 100644 --- a/userspace/plugin/plugin_loader.c +++ b/userspace/plugin/plugin_loader.c @@ -126,6 +126,7 @@ plugin_handle_t* plugin_load(const char* path, char* err) { SYM_RESOLVE(ret, get_metrics); SYM_RESOLVE(ret, capture_open); SYM_RESOLVE(ret, capture_close); + SYM_RESOLVE(ret, dump); return ret; } @@ -281,6 +282,10 @@ plugin_caps_t plugin_get_capabilities(const plugin_handle_t* h, char* err) { ", "); } + if(h->api.dump != NULL) { + caps = (plugin_caps_t)((uint32_t)caps | (uint32_t)CAP_DUMPING); + } + return caps; } diff --git a/userspace/plugin/plugin_loader.h b/userspace/plugin/plugin_loader.h index e6e833333b..a703fc0384 100644 --- a/userspace/plugin/plugin_loader.h +++ b/userspace/plugin/plugin_loader.h @@ -44,6 +44,7 @@ typedef enum { CAP_PARSING = 1 << 2, CAP_ASYNC = 1 << 3, CAP_CAPTURE_LISTENING = 1 << 4, + CAP_DUMPING = 1 << 5, CAP_BROKEN = 1 << 31, // used to report inconsistencies } plugin_caps_t; From dcc53a4f215e60f0eee99356eacbd363ec754d9d Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Wed, 6 Nov 2024 11:50:07 +0100 Subject: [PATCH 2/8] chore(userspace/plugin): bumped plugin API version to 3.10.0 Signed-off-by: Federico Di Pierro --- userspace/plugin/plugin_api.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userspace/plugin/plugin_api.h b/userspace/plugin/plugin_api.h index 5534104749..adcad46bee 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 // From b425744ca7b77c2a88857a42cff7d2a4048ed4ba Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Thu, 7 Nov 2024 15:27:33 +0100 Subject: [PATCH 3/8] new(userspace): moved `dump` API under async capability. We now expect `PPME_ASYNCEVENT_E` whose `name` matches one of the plugin supported ones (get_async_events() API). The new API is not required for async capability. Added also a test. Signed-off-by: Federico Di Pierro --- userspace/libsinsp/dumper.cpp | 2 +- userspace/libsinsp/plugin.cpp | 82 ++++++++++++------- userspace/libsinsp/plugin.h | 1 + userspace/libsinsp/sinsp_cycledumper.cpp | 2 +- userspace/libsinsp/test/plugins.ut.cpp | 59 +++++++++++++ .../libsinsp/test/plugins/syscall_async.cpp | 32 +++++++- userspace/plugin/plugin_api.h | 27 +++--- userspace/plugin/plugin_loader.c | 6 +- userspace/plugin/plugin_loader.h | 1 - 9 files changed, 157 insertions(+), 55 deletions(-) diff --git a/userspace/libsinsp/dumper.cpp b/userspace/libsinsp/dumper.cpp index 8645d8cc4a..c641365ad2 100644 --- a/userspace/libsinsp/dumper.cpp +++ b/userspace/libsinsp/dumper.cpp @@ -71,7 +71,7 @@ void sinsp_dumper::open(sinsp* inspector, const std::string& filename, bool comp // notify registered plugins of capture open for(auto& p : inspector->m_plugin_manager->plugins()) { - if(p->caps() & CAP_DUMPING) { + if(p->caps() & CAP_ASYNC) { if(!p->dump(*this)) { throw sinsp_exception("dump error for plugin '" + p->name() + "' : " + p->get_last_error()); diff --git a/userspace/libsinsp/plugin.cpp b/userspace/libsinsp/plugin.cpp index 7cc218dd0e..67fdcbf328 100644 --- a/userspace/libsinsp/plugin.cpp +++ b/userspace/libsinsp/plugin.cpp @@ -856,36 +856,6 @@ bool sinsp_plugin::capture_close() { return m_handle->api.capture_close(m_state, &in) == 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_handle->api.dump) { - return false; - } - - uint32_t num_evts; - ss_plugin_event** evts; - - if(m_handle->api.dump(m_state, &num_evts, &evts) != SS_PLUGIN_SUCCESS) { - return false; - } - for(int i = 0; i < num_evts; i++) { - auto e = evts[i]; - auto evt = sinsp_evt(); - 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(); - dumper.dump(&evt); - } - return true; -} - /** Event Source CAP **/ scap_source_plugin& sinsp_plugin::as_scap_source() { @@ -1205,3 +1175,55 @@ 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) { + // Not required. + return true; + } + + uint32_t num_evts; + ss_plugin_event** evts; + + if(m_handle->api.dump(m_state, &num_evts, &evts) != SS_PLUGIN_SUCCESS) { + return false; + } + + for(uint32_t i = 0; i < num_evts; i++) { + auto e = evts[i]; + if(e == nullptr) { + throw sinsp_exception("null async event dumped by plugin: " + m_name); + } + + // We only support dumping of PPME_ASYNCEVENT_E events + if(e->type != PPME_ASYNCEVENT_E || e->nparams != 3) { + throw sinsp_exception("malformed async event dumped by plugin: " + m_name); + } + + // 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(async_event_names().find(name) == async_event_names().end()) { + throw sinsp_exception("incompatible async event '" + std::string(name) + + "' produced by plugin: " + m_name); + } + + // Build the sinsp_evt + sinsp_evt evt; + evt.set_scap_evt_storage(new char[e->len]); + evt.set_scap_evt((scap_evt*)evt.get_scap_evt_storage()); + memcpy(evt.get_scap_evt_storage(), e, e->len); + evt.set_cpuid(0); + evt.set_num(0); + evt.init(); + dumper.dump(&evt); + } + return true; +} diff --git a/userspace/libsinsp/plugin.h b/userspace/libsinsp/plugin.h index 655b2d1933..ad818f81c4 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 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..41002effca 100644 --- a/userspace/libsinsp/test/plugins.ut.cpp +++ b/userspace/libsinsp/test/plugins.ut.cpp @@ -378,6 +378,65 @@ 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. +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"); +} + 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..e0ba9057fe 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,33 @@ 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, uint32_t* nevts, ss_plugin_event*** evts) { + static uint8_t evt_buf[10][256]; + static ss_plugin_event* evt[10]; + const char* name = "sampleticker"; + const char* data = "hello world"; + for(int i = 0; i < 10; i++) { + evt[i] = (ss_plugin_event*)evt_buf[i]; + char error[SCAP_LASTERR_SIZE]; + int32_t encode_res = + scap_event_encode_params(scap_sized_buffer{evt[i], sizeof(evt_buf[i])}, + nullptr, + error, + PPME_ASYNCEVENT_E, + 3, + (uint32_t)0, + name, + scap_const_sized_buffer{(void*)data, strlen(data) + 1}); + + if(encode_res == SCAP_FAILURE) { + return SS_PLUGIN_FAILURE; + } + } + *evts = evt; + *nevts = 10; + return SS_PLUGIN_SUCCESS; +} + } // anonymous namespace void get_plugin_api_sample_syscall_async(plugin_api& out) { @@ -203,4 +230,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 adcad46bee..fb458a79ae 100644 --- a/userspace/plugin/plugin_api.h +++ b/userspace/plugin/plugin_api.h @@ -1053,6 +1053,18 @@ 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. + // - nevts: number of events retrieved. + // - evts: events retrieved. Events MUST be of type PPME_ASYNCEVENT_E. + // + // Return value: A ss_plugin_rc with values SS_PLUGIN_SUCCESS or SS_PLUGIN_FAILURE. + ss_plugin_rc (*dump)(ss_plugin_t* s, uint32_t* nevts, ss_plugin_event*** evts); }; // Sets a new plugin configuration when provided by the framework. @@ -1104,21 +1116,6 @@ typedef struct { // Return value: A ss_plugin_rc with values SS_PLUGIN_SUCCESS or SS_PLUGIN_FAILURE. ss_plugin_rc (*capture_close)(ss_plugin_t* s, const ss_plugin_capture_listen_input* i); }; - - // Events dumping capability API - struct { - // - // Called by the framework when a capture file dump is requested. - // - // Required: yes - // Arguments: - // - s: the plugin state, returned by init(). Can be NULL. - // - evts: input containing vtables for performing table operations and - // subscribe/unsubscribe async routines - // - // Return value: A ss_plugin_rc with values SS_PLUGIN_SUCCESS or SS_PLUGIN_FAILURE. - ss_plugin_rc (*dump)(ss_plugin_t* s, uint32_t* nevts, ss_plugin_event*** evts); - }; } plugin_api; #ifdef __cplusplus diff --git a/userspace/plugin/plugin_loader.c b/userspace/plugin/plugin_loader.c index afb292be57..5b312e2903 100644 --- a/userspace/plugin/plugin_loader.c +++ b/userspace/plugin/plugin_loader.c @@ -122,11 +122,11 @@ 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); SYM_RESOLVE(ret, capture_close); - SYM_RESOLVE(ret, dump); return ret; } @@ -282,10 +282,6 @@ plugin_caps_t plugin_get_capabilities(const plugin_handle_t* h, char* err) { ", "); } - if(h->api.dump != NULL) { - caps = (plugin_caps_t)((uint32_t)caps | (uint32_t)CAP_DUMPING); - } - return caps; } diff --git a/userspace/plugin/plugin_loader.h b/userspace/plugin/plugin_loader.h index a703fc0384..e6e833333b 100644 --- a/userspace/plugin/plugin_loader.h +++ b/userspace/plugin/plugin_loader.h @@ -44,7 +44,6 @@ typedef enum { CAP_PARSING = 1 << 2, CAP_ASYNC = 1 << 3, CAP_CAPTURE_LISTENING = 1 << 4, - CAP_DUMPING = 1 << 5, CAP_BROKEN = 1 << 31, // used to report inconsistencies } plugin_caps_t; From b3601e20979654ad2609d46c3b6f39b59618228e Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Thu, 7 Nov 2024 16:59:15 +0100 Subject: [PATCH 4/8] chore(userspace/libsinsp): disable plugin_dump test on emscripten. Other tests using no_driver engine and async plugin are also disabled on it. Signed-off-by: Federico Di Pierro --- userspace/libsinsp/test/plugins.ut.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/userspace/libsinsp/test/plugins.ut.cpp b/userspace/libsinsp/test/plugins.ut.cpp index 41002effca..e0de0a3b16 100644 --- a/userspace/libsinsp/test/plugins.ut.cpp +++ b/userspace/libsinsp/test/plugins.ut.cpp @@ -404,6 +404,8 @@ class plugin_test_event_processor : public libsinsp::event_processor { }; // 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 @@ -436,6 +438,7 @@ TEST_F(sinsp_with_test_input, plugin_dump) { replay_inspector.close(); remove("test.scap"); } +#endif TEST(sinsp_plugin, plugin_extract_compatibility) { std::string tmp; From 9a8be80ba7252c5bc17c3906b9ed3233f50b5cb7 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Fri, 8 Nov 2024 08:40:17 +0100 Subject: [PATCH 5/8] chore(userspace/libsinsp): fixed comment string. Signed-off-by: Federico Di Pierro --- userspace/libsinsp/dumper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userspace/libsinsp/dumper.cpp b/userspace/libsinsp/dumper.cpp index c641365ad2..206e03f189 100644 --- a/userspace/libsinsp/dumper.cpp +++ b/userspace/libsinsp/dumper.cpp @@ -69,7 +69,7 @@ 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); - // notify registered plugins of capture open + // ask registered ASYNC plugins for a dump of their state for(auto& p : inspector->m_plugin_manager->plugins()) { if(p->caps() & CAP_ASYNC) { if(!p->dump(*this)) { From d01b079939555bce266a07ba666c1257e808916c Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Fri, 8 Nov 2024 11:45:16 +0100 Subject: [PATCH 6/8] new(userspace): `dump` API now takes a `ss_plugin_async_event_handler_t` callback. Signed-off-by: Federico Di Pierro --- userspace/libsinsp/dumper.cpp | 5 +- userspace/libsinsp/plugin.cpp | 69 ++++++++++++------- userspace/libsinsp/plugin.h | 7 ++ .../libsinsp/test/plugins/syscall_async.cpp | 42 ++++++----- userspace/plugin/plugin_api.h | 13 +++- 5 files changed, 87 insertions(+), 49 deletions(-) diff --git a/userspace/libsinsp/dumper.cpp b/userspace/libsinsp/dumper.cpp index 206e03f189..dd7ecd8cc8 100644 --- a/userspace/libsinsp/dumper.cpp +++ b/userspace/libsinsp/dumper.cpp @@ -72,10 +72,7 @@ void sinsp_dumper::open(sinsp* inspector, const std::string& filename, bool comp // ask registered ASYNC plugins for a dump of their state for(auto& p : inspector->m_plugin_manager->plugins()) { if(p->caps() & CAP_ASYNC) { - if(!p->dump(*this)) { - throw sinsp_exception("dump error for plugin '" + p->name() + - "' : " + p->get_last_error()); - } + p->dump(*this); } } diff --git a/userspace/libsinsp/plugin.cpp b/userspace/libsinsp/plugin.cpp index 67fdcbf328..25b5ec71fd 100644 --- a/userspace/libsinsp/plugin.cpp +++ b/userspace/libsinsp/plugin.cpp @@ -1186,44 +1186,61 @@ bool sinsp_plugin::dump(sinsp_dumper& dumper) { } if(!m_handle->api.dump) { - // Not required. - return true; - } - - uint32_t num_evts; - ss_plugin_event** evts; - - if(m_handle->api.dump(m_state, &num_evts, &evts) != SS_PLUGIN_SUCCESS) { return false; } - for(uint32_t i = 0; i < num_evts; i++) { - auto e = evts[i]; - if(e == nullptr) { - throw sinsp_exception("null async event dumped by plugin: " + m_name); - } + 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) { - throw sinsp_exception("malformed async event dumped by plugin: " + m_name); + 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(async_event_names().find(name) == async_event_names().end()) { - throw sinsp_exception("incompatible async event '" + std::string(name) + - "' produced by plugin: " + m_name); + 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; } - // Build the sinsp_evt - sinsp_evt evt; - evt.set_scap_evt_storage(new char[e->len]); - evt.set_scap_evt((scap_evt*)evt.get_scap_evt_storage()); - memcpy(evt.get_scap_evt_storage(), e, e->len); - evt.set_cpuid(0); - evt.set_num(0); - evt.init(); - dumper.dump(&evt); + 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 ad818f81c4..6d2d02225e 100644 --- a/userspace/libsinsp/plugin.h +++ b/userspace/libsinsp/plugin.h @@ -208,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 @@ -250,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/test/plugins/syscall_async.cpp b/userspace/libsinsp/test/plugins/syscall_async.cpp index e0ba9057fe..7fc5092021 100644 --- a/userspace/libsinsp/test/plugins/syscall_async.cpp +++ b/userspace/libsinsp/test/plugins/syscall_async.cpp @@ -188,30 +188,40 @@ 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, uint32_t* nevts, ss_plugin_event*** evts) { - static uint8_t evt_buf[10][256]; - static ss_plugin_event* evt[10]; +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"; - const char* data = "hello world"; + for(int i = 0; i < 10; i++) { - evt[i] = (ss_plugin_event*)evt_buf[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[i], sizeof(evt_buf[i])}, - nullptr, - error, - PPME_ASYNCEVENT_E, - 3, - (uint32_t)0, - name, - scap_const_sized_buffer{(void*)data, strlen(data) + 1}); + 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); + } } - *evts = evt; - *nevts = 10; return SS_PLUGIN_SUCCESS; } diff --git a/userspace/plugin/plugin_api.h b/userspace/plugin/plugin_api.h index fb458a79ae..eb645d2887 100644 --- a/userspace/plugin/plugin_api.h +++ b/userspace/plugin/plugin_api.h @@ -1060,11 +1060,18 @@ typedef struct { // Required: no // Arguments: // - s: the plugin state, returned by init(). Can be NULL. - // - nevts: number of events retrieved. - // - evts: events retrieved. Events MUST be of type PPME_ASYNCEVENT_E. + // - 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, uint32_t* nevts, ss_plugin_event*** evts); + 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. From 6b32c70a6716688fba1f91bb62c4cbccd21714fe Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Tue, 12 Nov 2024 10:44:03 +0100 Subject: [PATCH 7/8] chore(userspace/libsinsp): call plugin dump API from `fdopen` too. Signed-off-by: Federico Di Pierro --- userspace/libsinsp/dumper.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/userspace/libsinsp/dumper.cpp b/userspace/libsinsp/dumper.cpp index dd7ecd8cc8..bb76cab7de 100644 --- a/userspace/libsinsp/dumper.cpp +++ b/userspace/libsinsp/dumper.cpp @@ -96,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; } From 7fcd4dc5f4617c0555a622a7215e7ad25da2ecd6 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Fri, 22 Nov 2024 12:39:11 +0100 Subject: [PATCH 8/8] fix(userspace/libsinsp): address review comments. Signed-off-by: Federico Di Pierro Co-authored-by: Jason Dellaluce --- userspace/libsinsp/dumper.cpp | 4 ++-- userspace/libsinsp/plugin.cpp | 6 +++--- userspace/libsinsp/plugin.h | 2 +- userspace/libsinsp/test/plugins.ut.cpp | 8 ++++++++ userspace/libsinsp/test/plugins/syscall_async.cpp | 8 ++++---- userspace/plugin/plugin_api.h | 6 +++--- userspace/plugin/plugin_loader.c | 2 +- 7 files changed, 22 insertions(+), 14 deletions(-) diff --git a/userspace/libsinsp/dumper.cpp b/userspace/libsinsp/dumper.cpp index bb76cab7de..542ea4f21c 100644 --- a/userspace/libsinsp/dumper.cpp +++ b/userspace/libsinsp/dumper.cpp @@ -72,7 +72,7 @@ void sinsp_dumper::open(sinsp* inspector, const std::string& filename, bool comp // 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); + p->dump_state(*this); } } @@ -99,7 +99,7 @@ void sinsp_dumper::fdopen(sinsp* inspector, int fd, bool compress) { // 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); + p->dump_state(*this); } } diff --git a/userspace/libsinsp/plugin.cpp b/userspace/libsinsp/plugin.cpp index 25b5ec71fd..11338b9a36 100644 --- a/userspace/libsinsp/plugin.cpp +++ b/userspace/libsinsp/plugin.cpp @@ -1176,7 +1176,7 @@ bool sinsp_plugin::set_async_event_handler(async_event_handler_t handler) { return rc == SS_PLUGIN_SUCCESS; } -bool sinsp_plugin::dump(sinsp_dumper& dumper) { +bool sinsp_plugin::dump_state(sinsp_dumper& dumper) { if(!m_inited) { throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name); } @@ -1185,7 +1185,7 @@ bool sinsp_plugin::dump(sinsp_dumper& dumper) { throw sinsp_exception("plugin " + m_name + "without async events cap used as dumper"); } - if(!m_handle->api.dump) { + if(!m_handle->api.dump_state) { return false; } @@ -1239,7 +1239,7 @@ bool sinsp_plugin::dump(sinsp_dumper& dumper) { return SS_PLUGIN_SUCCESS; }; - if(m_handle->api.dump(m_state, this, callback) != SS_PLUGIN_SUCCESS) { + if(m_handle->api.dump_state(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 6d2d02225e..f65468d682 100644 --- a/userspace/libsinsp/plugin.h +++ b/userspace/libsinsp/plugin.h @@ -162,7 +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); + bool dump_state(sinsp_dumper& dumper); /** Event Sourcing **/ inline uint32_t id() const { return m_id; } diff --git a/userspace/libsinsp/test/plugins.ut.cpp b/userspace/libsinsp/test/plugins.ut.cpp index e0de0a3b16..3108057c9b 100644 --- a/userspace/libsinsp/test/plugins.ut.cpp +++ b/userspace/libsinsp/test/plugins.ut.cpp @@ -404,6 +404,14 @@ class plugin_test_event_processor : public libsinsp::event_processor { }; // scenario: a plugin with dump capability is requested a dump and then the capture file is read. +// * register a plugin with async event capability +// * open inspector in no driver mode +// * request a scap file dump to a temporary text file +// * at this stage, the plugin will be requested to dump its state; in our test case, the plugin +// will just dump 10 fake events +// * open a replay inspector to read the generated scap file +// * register our event processor that just counts number of PPME_ASYNC_EVENT_E +// * remove the test scap file // note: emscripten has trouble with the nodriver engine and async events #if !defined(__EMSCRIPTEN__) TEST_F(sinsp_with_test_input, plugin_dump) { diff --git a/userspace/libsinsp/test/plugins/syscall_async.cpp b/userspace/libsinsp/test/plugins/syscall_async.cpp index 7fc5092021..d023b31653 100644 --- a/userspace/libsinsp/test/plugins/syscall_async.cpp +++ b/userspace/libsinsp/test/plugins/syscall_async.cpp @@ -188,9 +188,9 @@ 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) { +ss_plugin_rc plugin_dump_state(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]; @@ -240,5 +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; + out.dump_state = plugin_dump_state; } diff --git a/userspace/plugin/plugin_api.h b/userspace/plugin/plugin_api.h index eb645d2887..3791661ff5 100644 --- a/userspace/plugin/plugin_api.h +++ b/userspace/plugin/plugin_api.h @@ -1069,9 +1069,9 @@ typedef struct { // 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); + ss_plugin_rc (*dump_state)(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 5b312e2903..05870feef0 100644 --- a/userspace/plugin/plugin_loader.c +++ b/userspace/plugin/plugin_loader.c @@ -122,7 +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, dump_state); SYM_RESOLVE(ret, set_config); SYM_RESOLVE(ret, get_metrics); SYM_RESOLVE(ret, capture_open);