@@ -39,10 +39,11 @@ static void _reset_globals(void);
3939const zend_array * nonnull _get_server_equiv (
4040 const zend_array * nonnull superglob_equiv );
4141static uint64_t _calc_sampling_key (zend_object * root_span , int status_code );
42- static void _register_testing_objects (void );
42+ static void _register_functions (void );
4343
4444static bool _enabled_user_req ;
4545static zend_string * _server_zstr ;
46+ static zend_string * _dd_downstream_request_metric ;
4647
4748static THREAD_LOCAL_ON_ZTS zend_object * nullable _cur_req_span ;
4849static THREAD_LOCAL_ON_ZTS zend_array * nullable _superglob_equiv ;
@@ -53,6 +54,9 @@ static THREAD_LOCAL_ON_ZTS bool _empty_service_or_env;
5354#define MAX_LENGTH_OF_REM_CFG_PATH 31
5455static THREAD_LOCAL_ON_ZTS char
5556 _last_rem_cfg_path [MAX_LENGTH_OF_REM_CFG_PATH + 1 ];
57+ static THREAD_LOCAL_ON_ZTS uint64_t _downstream_body_count_this_req ;
58+ static THREAD_LOCAL_ON_ZTS uint64_t _downstream_body_count_total ;
59+
5660#define CLIENT_IP_LOOKUP_FAILED ((zend_string *)-1)
5761
5862bool dd_req_is_user_req (void ) { return _enabled_user_req ; }
@@ -82,8 +86,10 @@ void dd_req_lifecycle_startup(void)
8286 }
8387
8488 _server_zstr = zend_string_init_interned (LSTRARG ("_SERVER" ), 1 );
89+ _dd_downstream_request_metric =
90+ zend_string_init_interned (LSTRARG ("_dd.appsec.downstream_request" ), 1 );
8591
86- _register_testing_objects ();
92+ _register_functions ();
8793}
8894
8995void dd_req_lifecycle_rinit (bool force )
@@ -181,6 +187,8 @@ static zend_array *nullable _do_request_begin(
181187
182188 dd_tags_rinit ();
183189
190+ _downstream_body_count_this_req = 0 ;
191+
184192 zend_string * nullable rbe = NULL ;
185193 if (rbe_zv ) {
186194 rbe = _get_entity_as_string (rbe_zv );
@@ -267,6 +275,21 @@ void dd_req_lifecycle_rshutdown(bool ignore_verdict, bool force)
267275 return ;
268276 }
269277
278+ // RFC-1062: Set downstream request metric if any bodies were analyzed
279+ if (_cur_req_span && _downstream_body_count_this_req > 0 ) {
280+ zval * metrics_zv = dd_trace_span_get_metrics (_cur_req_span );
281+ if (metrics_zv ) {
282+ zval zv ;
283+ ZVAL_DOUBLE (& zv , 1.0 );
284+ zend_hash_update (
285+ Z_ARRVAL_P (metrics_zv ), _dd_downstream_request_metric , & zv );
286+ mlog_g (dd_log_debug ,
287+ "Set _dd.appsec.downstream_request metric (analyzed %" PRIu64
288+ " bodies)" ,
289+ _downstream_body_count_this_req );
290+ }
291+ }
292+
270293 if (_enabled_user_req ) {
271294 if (_cur_req_span ) {
272295 mlog_g (dd_log_info ,
@@ -967,20 +990,74 @@ PHP_FUNCTION(datadog_appsec_testing_dump_req_lifecycle_state)
967990 }
968991}
969992
993+ static PHP_FUNCTION (datadog_appsec_should_send_downstream_bodies )
994+ {
995+ if (zend_parse_parameters_none () == FAILURE ) {
996+ RETURN_FALSE ;
997+ }
998+
999+ if (!DDAPPSEC_G (active )) {
1000+ RETURN_FALSE ;
1001+ }
1002+
1003+ _downstream_body_count_total ++ ;
1004+
1005+ if (_downstream_body_count_this_req >=
1006+ (uint64_t )get_DD_API_SECURITY_MAX_DOWNSTREAM_REQUEST_BODY_ANALYSIS ()) {
1007+ mlog_g (dd_log_debug ,
1008+ "Downstream body count limit reached for this request (did for "
1009+ "%" PRIu64 " requests)" ,
1010+ _downstream_body_count_this_req );
1011+ RETURN_FALSE ;
1012+ }
1013+
1014+ double sample_rate =
1015+ get_DD_API_SECURITY_DOWNSTREAM_BODY_ANALYSIS_SAMPLE_RATE ();
1016+ if (sample_rate >= 1.0 ) {
1017+ mlog_g (dd_log_debug , "Downstream body analysis sample rate is 1.0; "
1018+ "sending all downstream bodies up to the limit" );
1019+ _downstream_body_count_this_req ++ ;
1020+ RETURN_TRUE ;
1021+ }
1022+
1023+ static const uint64_t KNUTH_FACTOR = 11400714819323198549ULL ;
1024+ uint64_t threshold = (uint64_t )(sample_rate * (double )UINT64_MAX );
1025+ uint64_t sample_value = _downstream_body_count_total * KNUTH_FACTOR ;
1026+
1027+ if (sample_value < threshold ) {
1028+ _downstream_body_count_this_req ++ ;
1029+ mlog_g (dd_log_debug ,
1030+ "We're sending the bodies of this request for analysis" );
1031+ RETURN_TRUE ;
1032+ }
1033+
1034+ mlog_g (dd_log_debug ,
1035+ "We're NOT sending the bodies of this request for analysis" );
1036+ RETURN_FALSE ;
1037+ }
1038+
9701039// clang-format off
9711040ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX (dump_arginfo , 0 , 0 , IS_ARRAY , 0 )
9721041ZEND_END_ARG_INFO ()
1042+ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX (dump_downstream_bodies_arginfo , 0 , 0 , _IS_BOOL , 0 )
1043+ ZEND_END_ARG_INFO ()
9731044
9741045static const zend_function_entry functions [] = {
1046+ ZEND_RAW_FENTRY (DD_APPSEC_NS "should_send_downstream_bodies" , PHP_FN (datadog_appsec_should_send_downstream_bodies ), dump_downstream_bodies_arginfo , 0 , NULL , NULL )
1047+ PHP_FE_END
1048+ };
1049+
1050+ static const zend_function_entry test_functions [] = {
9751051 ZEND_RAW_FENTRY (DD_TESTING_NS "dump_req_lifecycle_state" , PHP_FN (datadog_appsec_testing_dump_req_lifecycle_state ), dump_arginfo , 0 , NULL , NULL )
9761052 PHP_FE_END
9771053};
9781054// clang-format on
9791055
980- static void _register_testing_objects (void )
1056+ static void _register_functions (void )
9811057{
982- if (!get_global_DD_APPSEC_TESTING ()) {
983- return ;
984- }
9851058 dd_phpobj_reg_funcs (functions );
1059+
1060+ if (get_global_DD_APPSEC_TESTING ()) {
1061+ dd_phpobj_reg_funcs (test_functions );
1062+ }
9861063}
0 commit comments