-
Notifications
You must be signed in to change notification settings - Fork 155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WAF telemetry #2735
base: master
Are you sure you want to change the base?
WAF telemetry #2735
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,8 @@ | |
#include "request_abort.h" | ||
#include "tags.h" | ||
#include <ext/standard/base64.h> | ||
#include <mpack.h> | ||
#include <stdatomic.h> | ||
|
||
typedef struct _dd_omsg { | ||
zend_llist iovecs; | ||
|
@@ -437,7 +439,71 @@ static void _command_process_stack_trace_parameters(mpack_node_t root) | |
} | ||
} | ||
|
||
dd_result _command_process_actions(mpack_node_t root, struct req_info *ctx) | ||
static dd_result _command_process_actions( | ||
mpack_node_t root, struct req_info *ctx); | ||
|
||
/* | ||
* array( | ||
* 0: [<"ok" / "record" / "block" / "redirect">, | ||
* [if block/redirect parameters: (map)]] | ||
* 1: [if block/redirect/record: appsec span data (array of strings: json | ||
* fragments)], | ||
* 2: [force keep: bool] | ||
* 3: [meta: map] | ||
* 4: [metrics: map] | ||
* 5: [telemetry metrics: map string -> | ||
* array(array(value: double, tags: string)]) | ||
* ) | ||
*/ | ||
#define RESP_INDEX_ACTION_PARAMS 0 | ||
#define RESP_INDEX_APPSEC_SPAN_DATA 1 | ||
#define RESP_INDEX_FORCE_KEEP 2 | ||
#define RESP_INDEX_SPAN_META 3 | ||
#define RESP_INDEX_SPAN_METRICS 4 | ||
#define RESP_INDEX_TELEMETRY_METRICS 5 | ||
|
||
dd_result dd_command_proc_resp_verd_span_data( | ||
mpack_node_t root, void *unspecnull _ctx) | ||
{ | ||
struct req_info *ctx = _ctx; | ||
assert(ctx != NULL); | ||
|
||
mpack_node_t actions = mpack_node_array_at(root, RESP_INDEX_ACTION_PARAMS); | ||
dd_result res = _command_process_actions(actions, ctx); | ||
|
||
if (res == dd_should_block || res == dd_should_redirect || | ||
res == dd_should_record) { | ||
_set_appsec_span_data( | ||
mpack_node_array_at(root, RESP_INDEX_APPSEC_SPAN_DATA)); | ||
} | ||
|
||
mpack_node_t force_keep = mpack_node_array_at(root, RESP_INDEX_FORCE_KEEP); | ||
if (mpack_node_type(force_keep) == mpack_type_bool && | ||
mpack_node_bool(force_keep)) { | ||
dd_tags_set_sampling_priority(); | ||
} | ||
|
||
if (mpack_node_array_length(root) >= RESP_INDEX_SPAN_METRICS + 1 && | ||
ctx->root_span) { | ||
zend_object *span = ctx->root_span; | ||
|
||
mpack_node_t meta = mpack_node_array_at(root, RESP_INDEX_SPAN_META); | ||
dd_command_process_meta(meta, span); | ||
mpack_node_t metrics = | ||
mpack_node_array_at(root, RESP_INDEX_SPAN_METRICS); | ||
dd_command_process_metrics(metrics, span); | ||
} | ||
|
||
if (mpack_node_array_length(root) >= RESP_INDEX_TELEMETRY_METRICS + 1) { | ||
dd_command_process_telemetry_metrics( | ||
mpack_node_array_at(root, RESP_INDEX_TELEMETRY_METRICS)); | ||
} | ||
|
||
return res; | ||
} | ||
|
||
static dd_result _command_process_actions( | ||
mpack_node_t root, struct req_info *ctx) | ||
{ | ||
size_t actions = mpack_node_array_length(root); | ||
dd_result res = dd_success; | ||
|
@@ -482,42 +548,6 @@ dd_result _command_process_actions(mpack_node_t root, struct req_info *ctx) | |
return res; | ||
} | ||
|
||
dd_result dd_command_proc_resp_verd_span_data( | ||
mpack_node_t root, void *unspecnull _ctx) | ||
{ | ||
struct req_info *ctx = _ctx; | ||
assert(ctx != NULL); | ||
|
||
mpack_node_t actions = mpack_node_array_at(root, 0); | ||
|
||
dd_result res = _command_process_actions(actions, ctx); | ||
|
||
if (res == dd_should_block || res == dd_should_redirect || | ||
res == dd_should_record) { | ||
_set_appsec_span_data(mpack_node_array_at(root, 1)); | ||
} | ||
|
||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) | ||
mpack_node_t force_keep = mpack_node_array_at(root, 2); | ||
if (mpack_node_type(force_keep) == mpack_type_bool && | ||
mpack_node_bool(force_keep)) { | ||
dd_tags_set_sampling_priority(); | ||
} | ||
|
||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) | ||
if (mpack_node_array_length(root) >= 5 && ctx->root_span) { | ||
zend_object *span = ctx->root_span; | ||
|
||
mpack_node_t meta = mpack_node_array_at(root, 3); | ||
dd_command_process_meta(meta, span); | ||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) | ||
mpack_node_t metrics = mpack_node_array_at(root, 4); | ||
dd_command_process_metrics(metrics, span); | ||
} | ||
|
||
return res; | ||
} | ||
|
||
static void _add_appsec_span_data_frag(mpack_node_t node) | ||
{ | ||
const char *data = mpack_node_data(node); | ||
|
@@ -648,6 +678,103 @@ bool dd_command_process_metrics(mpack_node_t root, zend_object *nonnull span) | |
return true; | ||
} | ||
|
||
static void _handle_telemetry_metric(const char *nonnull key_str, | ||
size_t key_len, double value, const char *nonnull tags_str, | ||
size_t tags_len); | ||
|
||
bool dd_command_process_telemetry_metrics(mpack_node_t metrics) | ||
{ | ||
if (mpack_node_type(metrics) != mpack_type_map) { | ||
return false; | ||
} | ||
|
||
if (!ddtrace_metric_register_buffer) { | ||
mlog_g(dd_log_debug, "ddtrace_metric_register_buffer unavailable"); | ||
return true; | ||
} | ||
|
||
for (size_t i = 0; i < mpack_node_map_count(metrics); i++) { | ||
mpack_node_t key = mpack_node_map_key_at(metrics, i); | ||
|
||
const char *key_str = mpack_node_str(key); | ||
if (!key_str) { | ||
continue; | ||
} | ||
|
||
size_t key_len = mpack_node_strlen(key); | ||
mpack_node_t arr_value = mpack_node_map_value_at(metrics, i); | ||
|
||
for (size_t j = 0; j < mpack_node_array_length(arr_value); j++) { | ||
mpack_node_t value = mpack_node_array_at(arr_value, j); | ||
mpack_node_t dval_node = mpack_node_array_at(value, 0); | ||
double dval = mpack_node_double(dval_node); | ||
|
||
const char *tags_str = ""; | ||
size_t tags_len = 0; | ||
if (mpack_node_array_length(value) >= 2) { | ||
mpack_node_t tags = mpack_node_array_at(value, 1); | ||
tags_str = mpack_node_str(tags); | ||
tags_len = mpack_node_strlen(tags); | ||
} | ||
if (mpack_node_error(metrics) != mpack_ok) { | ||
break; | ||
} | ||
|
||
_handle_telemetry_metric( | ||
key_str, key_len, dval, tags_str, tags_len); | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
static void _init_zstr( | ||
zend_string *_Atomic *nonnull zstr, const char *nonnull str, size_t len) | ||
{ | ||
zend_string *zstr_cur = atomic_load_explicit(zstr, memory_order_acquire); | ||
if (zstr_cur != NULL) { | ||
return; | ||
} | ||
zend_string *zstr_new = zend_string_init(str, len, 1); | ||
if (atomic_compare_exchange_strong_explicit(zstr, &(zend_string *){NULL}, | ||
zstr_new, memory_order_release, memory_order_relaxed)) { | ||
return; | ||
} | ||
zend_string_release(zstr_new); | ||
} | ||
|
||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) | ||
void _handle_telemetry_metric(const char *nonnull key_str, size_t key_len, | ||
double value, const char *nonnull tags_str, size_t tags_len) | ||
{ | ||
#define HANDLE_METRIC(name, type) \ | ||
do { \ | ||
if (key_len == LSTRLEN(name) && memcmp(key_str, name, key_len) == 0) { \ | ||
static zend_string *_Atomic key_zstr; \ | ||
Anilm3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
_init_zstr(&key_zstr, name, LSTRLEN(name)); \ | ||
zend_string *tags_zstr = zend_string_init(tags_str, tags_len, 1); \ | ||
ddtrace_metric_register_buffer( \ | ||
key_zstr, type, DDTRACE_METRIC_NAMESPACE_APPSEC); \ | ||
ddtrace_metric_add_point(key_zstr, value, tags_zstr); \ | ||
zend_string_release(tags_zstr); \ | ||
mlog_g(dd_log_debug, \ | ||
"Telemetry metric %.*s added with tags %.*s and value %f", \ | ||
(int)key_len, key_str, (int)tags_len, tags_str, value); \ | ||
return; \ | ||
} \ | ||
} while (0) | ||
|
||
HANDLE_METRIC("waf.requests", DDTRACE_METRIC_TYPE_COUNT); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I saw you added some integration tests checking this metrics but I am missing some PHPT checking the extension. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would also allow you to test unknown telemetry metrics |
||
HANDLE_METRIC("waf.updates", DDTRACE_METRIC_TYPE_COUNT); | ||
HANDLE_METRIC("waf.init", DDTRACE_METRIC_TYPE_COUNT); | ||
HANDLE_METRIC("waf.config_errors", DDTRACE_METRIC_TYPE_COUNT); | ||
|
||
HANDLE_METRIC("remote_config.first_pull", DDTRACE_METRIC_TYPE_GAUGE); | ||
HANDLE_METRIC("remote_config.last_success", DDTRACE_METRIC_TYPE_GAUGE); | ||
|
||
mlog_g(dd_log_info, "Unknown telemetry metric %.*s", (int)key_len, key_str); | ||
} | ||
|
||
static void _dump_in_msg( | ||
dd_log_level_t lvl, const char *nonnull data, size_t data_len) | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,11 @@ | |
#include "zend_object_handlers.h" | ||
#include "zend_types.h" | ||
|
||
void (*nullable ddtrace_metric_register_buffer)( | ||
zend_string *nonnull name, ddtrace_metric_type type, ddtrace_metric_ns ns); | ||
bool (*nullable ddtrace_metric_add_point)( | ||
zend_string *nonnull name, double value, zend_string *nonnull tags); | ||
|
||
static int (*_orig_ddtrace_shutdown)(SHUTDOWN_FUNC_ARGS); | ||
static int _mod_type; | ||
static int _mod_number; | ||
|
@@ -31,6 +36,7 @@ static zend_string *_meta_struct_propname; | |
static THREAD_LOCAL_ON_ZTS bool _suppress_ddtrace_rshutdown; | ||
static uint8_t *_ddtrace_runtime_id = NULL; | ||
|
||
static void _setup_testing_telemetry_functions(void); | ||
static zend_module_entry *_find_ddtrace_module(void); | ||
static int _ddtrace_rshutdown_testing(SHUTDOWN_FUNC_ARGS); | ||
static void _register_testing_objects(void); | ||
|
@@ -48,6 +54,11 @@ static zend_string *(*_ddtrace_ip_extraction_find)(zval *server); | |
|
||
static const char *nullable (*_ddtrace_remote_config_get_path)(void); | ||
|
||
static void _test_ddtrace_metric_register_buffer( | ||
zend_string *nonnull name, ddtrace_metric_type type, ddtrace_metric_ns ns); | ||
static bool _test_ddtrace_metric_add_point( | ||
zend_string *nonnull name, double value, zend_string *nonnull tags); | ||
|
||
static void dd_trace_load_symbols(void) | ||
{ | ||
bool testing = get_global_DD_APPSEC_TESTING(); | ||
|
@@ -109,6 +120,19 @@ static void dd_trace_load_symbols(void) | |
"Failed to load ddtrace_remote_config_get_path: %s", dlerror()); | ||
} | ||
|
||
ddtrace_metric_register_buffer = | ||
dlsym(handle, "ddtrace_metric_register_buffer"); | ||
if (ddtrace_metric_register_buffer == NULL && !testing) { | ||
mlog(dd_log_error, "Failed to load ddtrace_metric_register_buffer: %s", | ||
dlerror()); // NOLINT(concurrency-mt-unsafe) | ||
} | ||
|
||
ddtrace_metric_add_point = dlsym(handle, "ddtrace_metric_add_point"); | ||
if (ddtrace_metric_add_point == NULL && !testing) { | ||
mlog(dd_log_error, "Failed to load ddtrace_metric_add_point: %s", | ||
dlerror()); // NOLINT(concurrency-mt-unsafe) | ||
} | ||
|
||
dlclose(handle); | ||
} | ||
|
||
|
@@ -123,6 +147,7 @@ void dd_trace_startup() | |
|
||
if (get_global_DD_APPSEC_TESTING()) { | ||
_register_testing_objects(); | ||
_setup_testing_telemetry_functions(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I coudln't get why this is within There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok. Makes sense |
||
} | ||
|
||
zend_module_entry *mod = _find_ddtrace_module(); | ||
|
@@ -145,6 +170,16 @@ void dd_trace_startup() | |
} | ||
} | ||
|
||
static void _setup_testing_telemetry_functions() | ||
{ | ||
if (ddtrace_metric_register_buffer == NULL) { | ||
ddtrace_metric_register_buffer = _test_ddtrace_metric_register_buffer; | ||
} | ||
if (ddtrace_metric_add_point == NULL) { | ||
ddtrace_metric_add_point = _test_ddtrace_metric_add_point; | ||
} | ||
} | ||
|
||
static zend_module_entry *_find_ddtrace_module() | ||
{ | ||
zend_string *ddtrace_name = | ||
|
@@ -516,3 +551,22 @@ static const zend_function_entry functions[] = { | |
// clang-format on | ||
|
||
static void _register_testing_objects() { dd_phpobj_reg_funcs(functions); } | ||
|
||
static void _test_ddtrace_metric_register_buffer( | ||
zend_string *nonnull name, ddtrace_metric_type type, ddtrace_metric_ns ns) | ||
{ | ||
php_error_docref(NULL, E_NOTICE, | ||
"Would call ddtrace_metric_register_buffer with name=%.*s " | ||
"type=%d ns=%d", | ||
(int)ZSTR_LEN(name), ZSTR_VAL(name), type, ns); | ||
} | ||
static bool _test_ddtrace_metric_add_point( | ||
zend_string *nonnull name, double value, zend_string *nonnull tags) | ||
{ | ||
php_error_docref(NULL, E_NOTICE, | ||
"Would call to ddtrace_metric_add_point with name=%.*s value=%f " | ||
"tags=%.*s", | ||
(int)ZSTR_LEN(name), ZSTR_VAL(name), value, (int)ZSTR_LEN(tags), | ||
ZSTR_VAL(tags)); | ||
return true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if it is a good practice or I made it up but, should this early declaration be at the top?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You put these at the top if you use these multiple times at various places across a file. For a single use like here, there's no reason to.