From 26e2a387133b9640173c0a26246e65b3298c0d9d Mon Sep 17 00:00:00 2001 From: Ned Wright Date: Mon, 10 Feb 2025 16:27:27 +0000 Subject: [PATCH 1/2] sync code --- .../nginx_attachment_util.cc | 12 +++++ .../nginx/ngx_module/ngx_cp_compression.c | 12 ++++- attachments/nginx/ngx_module/ngx_cp_hooks.c | 47 +++++++++---------- attachments/nginx/ngx_module/ngx_cp_io.c | 5 +- attachments/nginx/ngx_module/ngx_cp_utils.c | 23 ++++++++- attachments/nginx/ngx_module/ngx_cp_utils.h | 2 + .../http_configuration/http_configuration.cc | 4 ++ .../attachments/nginx_attachment_util.h | 3 ++ 8 files changed, 80 insertions(+), 28 deletions(-) diff --git a/attachments/nginx/nginx_attachment_util/nginx_attachment_util.cc b/attachments/nginx/nginx_attachment_util/nginx_attachment_util.cc index ddf3a01..85cd5b6 100644 --- a/attachments/nginx/nginx_attachment_util/nginx_attachment_util.cc +++ b/attachments/nginx/nginx_attachment_util/nginx_attachment_util.cc @@ -95,6 +95,18 @@ getFailOpenHoldTimeout() return conf_data.getNumericalValue("fail_open_hold_timeout"); } +unsigned int +getHoldVerdictPollingTime() +{ + return conf_data.getNumericalValue("hold_verdict_polling_time"); +} + +unsigned int +getHoldVerdictRetries() +{ + return conf_data.getNumericalValue("hold_verdict_retries"); +} + unsigned int getMaxSessionsPerMinute() { diff --git a/attachments/nginx/ngx_module/ngx_cp_compression.c b/attachments/nginx/ngx_module/ngx_cp_compression.c index 5cc39c2..99a91a7 100644 --- a/attachments/nginx/ngx_module/ngx_cp_compression.c +++ b/attachments/nginx/ngx_module/ngx_cp_compression.c @@ -370,7 +370,17 @@ compression_chain_filter( ); if (compression_result != NGX_OK) { // Failed to decompress or compress. - free_chain(pool, *body); + if (curr_original_contents_link != NULL) { + write_dbg( + DBG_LEVEL_WARNING, + "Failed to %s chain: free unused chain link " + "and copy original chain back to body up to current link", + should_compress ? "compress" : "decompress" + ); + ngx_free_chain(pool, curr_original_contents_link); + curr_original_contents_link = NULL; + copy_chain_buffers(*body, *original_body_contents); + } return NGX_ERROR; } diff --git a/attachments/nginx/ngx_module/ngx_cp_hooks.c b/attachments/nginx/ngx_module/ngx_cp_hooks.c index 55a1f84..9141f3d 100644 --- a/attachments/nginx/ngx_module/ngx_cp_hooks.c +++ b/attachments/nginx/ngx_module/ngx_cp_hooks.c @@ -118,15 +118,10 @@ ngx_session_data_cleanup(void *data) { if (data == NULL) return; ngx_http_cp_session_data *session_data = (ngx_http_cp_session_data *)data; - write_dbg(DBG_LEVEL_TRACE, "Cleaning up session data for session ID %d", session_data->session_id); + write_dbg(DBG_LEVEL_DEBUG, "Cleaning up session data for session ID %d", session_data->session_id); if (session_data->response_data.original_compressed_body != NULL) { - ngx_chain_t *current = session_data->response_data.original_compressed_body; - while (current != NULL) { - ngx_chain_t *next = current->next; - ngx_free_chain(session_data->response_data.request_pool, current); - current = next; - } + free_chain(session_data->response_data.request_pool, session_data->response_data.original_compressed_body); session_data->response_data.original_compressed_body = NULL; } @@ -220,8 +215,8 @@ ngx_int_t ngx_http_cp_hold_verdict(struct ngx_http_cp_event_thread_ctx_t *ctx) { ngx_http_cp_session_data *session_data_p = ctx->session_data_p; - for (uint i = 0; i < 3; i++) { - sleep(1); + for (uint i = 0; i < hold_verdict_retries; i++) { + sleep(hold_verdict_polling_time); int res = ngx_cp_run_in_thread_timeout( ngx_http_cp_hold_verdict_thread, (void *)ctx, @@ -436,16 +431,16 @@ ngx_http_cp_req_header_handler(ngx_http_request_t *request) if (is_in_transparent_mode()) { updateMetricField(TRANSPARENTS_COUNT, 1); - return fail_mode_verdict; + return fail_mode_verdict == NGX_OK ? NGX_DECLINED : NGX_ERROR; } if (is_ngx_cp_attachment_disabled(request)) { write_dbg(DBG_LEVEL_TRACE, "Ignoring inspection of request on a disabled location"); - return NGX_OK; + return NGX_DECLINED; } session_data_p = init_cp_session_data(request); - if (session_data_p == NULL) return NGX_OK; + if (session_data_p == NULL) return NGX_DECLINED; set_current_session_id(session_data_p->session_id); write_dbg(DBG_LEVEL_DEBUG, "Request header filter handling session ID: %d", session_data_p->session_id); @@ -455,7 +450,7 @@ ngx_http_cp_req_header_handler(ngx_http_request_t *request) sessions_per_minute_verdict = enforce_sessions_rate(); if (sessions_per_minute_verdict != TRAFFIC_VERDICT_INSPECT) { session_data_p->verdict = sessions_per_minute_verdict; - return sessions_per_minute_verdict == TRAFFIC_VERDICT_ACCEPT ? NGX_OK : NGX_ERROR; + return sessions_per_minute_verdict == TRAFFIC_VERDICT_ACCEPT ? NGX_DECLINED : NGX_ERROR; } if (!get_already_registered() || !isIpcReady()) { @@ -484,7 +479,7 @@ ngx_http_cp_req_header_handler(ngx_http_request_t *request) ); updateMetricField(REG_THREAD_TIMEOUT, 1); - return fail_mode_verdict; + return fail_mode_verdict == NGX_OK ? NGX_DECLINED : fail_mode_verdict; } write_dbg( DBG_LEVEL_DEBUG, @@ -494,7 +489,7 @@ ngx_http_cp_req_header_handler(ngx_http_request_t *request) ); if (ctx.should_return) { session_data_p->verdict = TRAFFIC_VERDICT_ACCEPT; - return ctx.res; + return ctx.res == NGX_OK ? NGX_DECLINED : ctx.res; } } @@ -509,7 +504,7 @@ ngx_http_cp_req_header_handler(ngx_http_request_t *request) session_data_p->session_id, session_data_p->verdict == TRAFFIC_VERDICT_ACCEPT ? "accept" : "drop" ); - return fail_mode_verdict; + return fail_mode_verdict == NGX_OK ? NGX_DECLINED : fail_mode_verdict; } handle_static_resource_result = handle_static_resource_request( @@ -538,7 +533,7 @@ ngx_http_cp_req_header_handler(ngx_http_request_t *request) ); updateMetricField(REQ_HEADER_THREAD_TIMEOUT, 1); - return fail_mode_verdict; + return fail_mode_verdict == NGX_OK ? NGX_DECLINED : fail_mode_verdict; } write_dbg( DBG_LEVEL_DEBUG, @@ -552,22 +547,23 @@ ngx_http_cp_req_header_handler(ngx_http_request_t *request) if (!res) { session_data_p->verdict = fail_mode_hold_verdict == NGX_OK ? TRAFFIC_VERDICT_ACCEPT : TRAFFIC_VERDICT_DROP; updateMetricField(HOLD_THREAD_TIMEOUT, 1); - return fail_mode_verdict == NGX_OK ? TRAFFIC_VERDICT_ACCEPT : TRAFFIC_VERDICT_DROP; + return fail_mode_verdict == NGX_OK ? NGX_DECLINED : fail_mode_verdict; } } calcProcessingTime(session_data_p, &hook_time_begin, 1); if (ctx.should_return) { - return ctx.res; + return ctx.res == NGX_OK ? NGX_DECLINED : ctx.res; } // There's no body for inspection - return ngx_http_cp_finalize_request_headers_hook( + ngx_int_t result = ngx_http_cp_finalize_request_headers_hook( request, session_data_p, ctx.modifications, ctx.res ); + return result == NGX_OK ? NGX_DECLINED : result; } ngx_int_t @@ -686,7 +682,7 @@ ngx_http_cp_req_body_filter(ngx_http_request_t *request, ngx_chain_t *request_bo if (!res) { session_data_p->verdict = fail_mode_hold_verdict == NGX_OK ? TRAFFIC_VERDICT_ACCEPT : TRAFFIC_VERDICT_DROP; updateMetricField(HOLD_THREAD_TIMEOUT, 1); - return fail_mode_verdict == NGX_OK ? ngx_http_next_request_body_filter(request, request_body_chain) : NGX_HTTP_FORBIDDEN; + return fail_mode_hold_verdict == NGX_OK ? ngx_http_next_request_body_filter(request, request_body_chain) : NGX_HTTP_FORBIDDEN; } } @@ -725,7 +721,7 @@ ngx_http_cp_req_body_filter(ngx_http_request_t *request, ngx_chain_t *request_bo } if (ctx.should_return) { - return ctx.res; + return ctx.res == NGX_OK ? NGX_DECLINED : ctx.res; } if (was_transaction_timedout(session_data_p)) { session_data_p->verdict = fail_mode_verdict == NGX_OK ? TRAFFIC_VERDICT_ACCEPT : TRAFFIC_VERDICT_DROP; @@ -1029,6 +1025,7 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain } if (body_chain->buf->pos != NULL && session_data_p->response_data.new_compression_type != NO_COMPRESSION) { + write_dbg(DBG_LEVEL_TRACE, "Decompressing response body"); if (init_cp_session_original_body(session_data_p, request->pool) == NGX_OK) { if (session_data_p->response_data.decompression_stream == NULL) { session_data_p->response_data.decompression_stream = initCompressionStream(); @@ -1045,7 +1042,7 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain } if (compression_result != NGX_OK) { - copy_chain_buffers(body_chain, session_data_p->response_data.original_compressed_body); + write_dbg(DBG_LEVEL_WARNING, "Failed to decompress response body"); handle_inspection_failure(inspection_failure_weight, fail_mode_verdict, session_data_p); fini_cp_session_data(session_data_p); session_data_p->response_data.response_data_status = NGX_ERROR; @@ -1056,6 +1053,7 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain } if (session_data_p->verdict == TRAFFIC_VERDICT_ACCEPT) { + write_dbg(DBG_LEVEL_TRACE, "Compressing response body"); if (session_data_p->response_data.compression_stream == NULL) { session_data_p->response_data.compression_stream = initCompressionStream(); } @@ -1070,6 +1068,7 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain request->pool ); if (compression_result != NGX_OK) { + write_dbg(DBG_LEVEL_WARNING, "Failed to compress response body"); // Failed to compress body. handle_inspection_failure(inspection_failure_weight, fail_mode_verdict, session_data_p); fini_cp_session_data(session_data_p); @@ -1167,7 +1166,7 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain calcProcessingTime(session_data_p, &hook_time_begin, 0); if (ctx.should_return) { - return ctx.res; + return ctx.res == NGX_OK ? NGX_DECLINED : ctx.res; } if (ctx.should_return_next_filter) { diff --git a/attachments/nginx/ngx_module/ngx_cp_io.c b/attachments/nginx/ngx_module/ngx_cp_io.c index 3f049db..cc0bc3b 100644 --- a/attachments/nginx/ngx_module/ngx_cp_io.c +++ b/attachments/nginx/ngx_module/ngx_cp_io.c @@ -1116,8 +1116,9 @@ ngx_http_cp_body_sender( write_dbg( DBG_LEVEL_TRACE, - "Sending %s body chunk for inspection", - body_type == REQUEST_BODY ? "request" : "response" + "Sending %s body chunk from session id %d for inspection", + body_type == REQUEST_BODY ? "request" : "response", + session_data->session_id ); // Sets fragments identifier to the provided body type. diff --git a/attachments/nginx/ngx_module/ngx_cp_utils.c b/attachments/nginx/ngx_module/ngx_cp_utils.c index 8bbe369..ff5a151 100644 --- a/attachments/nginx/ngx_module/ngx_cp_utils.c +++ b/attachments/nginx/ngx_module/ngx_cp_utils.c @@ -102,6 +102,8 @@ ngx_uint_t num_of_nginx_ipc_elements = 200; ///< Number of NGINX IPC elements. ngx_msec_t keep_alive_interval_msec = DEFAULT_KEEP_ALIVE_INTERVAL_MSEC; ngx_uint_t min_retries_for_verdict = 3; ///< Minimum number of retries for verdict. ngx_uint_t max_retries_for_verdict = 15; ///< Maximum number of retries for verdict. +ngx_uint_t hold_verdict_retries = 3; ///< Number of retries for hold verdict. +ngx_uint_t hold_verdict_polling_time = 1; ///< Polling time for hold verdict. ngx_uint_t body_size_trigger = 200000; ///< Request body size in bytes to switch to maximum retries for verdict. ngx_uint_t remove_res_server_header = 0; ///< Remove server header flag. @@ -322,6 +324,15 @@ copy_chain_buffers(ngx_chain_t *dest, ngx_chain_t *src) ngx_chain_t *curr_src = src; ngx_chain_t *curr_dst = dest; while (curr_src != NULL && curr_dst != NULL) { + if (curr_src->buf == NULL || curr_dst->buf == NULL) { + write_dbg( + DBG_LEVEL_WARNING, + "Failed to copy chain buffers: NULL buffer found. src: %p, dst: %p", + curr_src, + curr_dst + ); + return; + } ngx_memcpy(curr_dst->buf, curr_src->buf, sizeof(ngx_buf_t)); curr_src = curr_src->next; curr_dst = curr_dst->next; @@ -459,10 +470,12 @@ free_chain(ngx_pool_t *pool, ngx_chain_t *chain) while (chain) { ngx_pfree(pool, chain->buf->start); + chain->buf->start = NULL; ngx_pfree(pool, chain->buf); + chain->buf = NULL; next_chain = chain->next; - ngx_pfree(pool, chain); + ngx_free_chain(pool, chain); chain = next_chain; } } @@ -940,6 +953,10 @@ init_general_config(const char *conf_path) fail_mode_hold_verdict = isFailOpenHoldMode() == 1 ? NGX_OK : NGX_HTTP_FORBIDDEN; fail_open_hold_timeout = getFailOpenHoldTimeout(); + // Setting hold verdict polling time and retries. + hold_verdict_polling_time = getHoldVerdictPollingTime(); + hold_verdict_retries = getHoldVerdictRetries(); + // Setting attachment's variables. sessions_per_minute_limit_verdict = isFailOpenOnSessionLimit() ? TRAFFIC_VERDICT_ACCEPT : TRAFFIC_VERDICT_DROP; max_sessions_per_minute = getMaxSessionsPerMinute(); @@ -987,6 +1004,8 @@ init_general_config(const char *conf_path) "keep alive interval msec: %u msec" "min retries for verdict: %u" "max retries for verdict: %u" + "num retries for hold verdict: %u" + "polling time for hold verdict: %u" "body size trigger for request: %u", inspection_mode, new_dbg_level, @@ -1009,6 +1028,8 @@ init_general_config(const char *conf_path) keep_alive_interval_msec, min_retries_for_verdict, max_retries_for_verdict, + hold_verdict_retries, + hold_verdict_polling_time, body_size_trigger ); diff --git a/attachments/nginx/ngx_module/ngx_cp_utils.h b/attachments/nginx/ngx_module/ngx_cp_utils.h index 5c944ad..74cd1f8 100644 --- a/attachments/nginx/ngx_module/ngx_cp_utils.h +++ b/attachments/nginx/ngx_module/ngx_cp_utils.h @@ -64,6 +64,8 @@ extern ngx_http_inspection_mode_e inspection_mode; extern ngx_uint_t num_of_nginx_ipc_elements; extern ngx_uint_t min_retries_for_verdict; extern ngx_uint_t max_retries_for_verdict; +extern ngx_uint_t hold_verdict_retries; +extern ngx_uint_t hold_verdict_polling_time; extern ngx_uint_t body_size_trigger; extern ngx_uint_t remove_res_server_header; diff --git a/core/attachments/http_configuration/http_configuration.cc b/core/attachments/http_configuration/http_configuration.cc index 9a1ddf0..18639ea 100644 --- a/core/attachments/http_configuration/http_configuration.cc +++ b/core/attachments/http_configuration/http_configuration.cc @@ -111,6 +111,8 @@ HttpAttachmentConfiguration::save(cereal::JSONOutputArchive &archive) const cereal::make_nvp("keep_alive_interval_msec", getNumericalValue("keep_alive_interval_msec")), cereal::make_nvp("min_retries_for_verdict", getNumericalValue("min_retries_for_verdict")), cereal::make_nvp("max_retries_for_verdict", getNumericalValue("max_retries_for_verdict")), + cereal::make_nvp("hold_verdict_retries", getNumericalValue("hold_verdict_retries")), + cereal::make_nvp("hold_verdict_polling_time", getNumericalValue("hold_verdict_polling_time")), cereal::make_nvp("body_size_trigger", getNumericalValue("body_size_trigger")), cereal::make_nvp("remove_server_header", getNumericalValue("remove_server_header")) ); @@ -167,6 +169,8 @@ HttpAttachmentConfiguration::load(cereal::JSONInputArchive &archive) loadNumericalValue(archive, "keep_alive_interval_msec", DEFAULT_KEEP_ALIVE_INTERVAL_MSEC); loadNumericalValue(archive, "min_retries_for_verdict", 3); loadNumericalValue(archive, "max_retries_for_verdict", 15); + loadNumericalValue(archive, "hold_verdict_retries", 3); + loadNumericalValue(archive, "hold_verdict_polling_time", 1); loadNumericalValue(archive, "body_size_trigger", 200000); loadNumericalValue(archive, "remove_server_header", 0); } diff --git a/core/include/attachments/nginx_attachment_util.h b/core/include/attachments/nginx_attachment_util.h index 33885d4..6a2b5e9 100644 --- a/core/include/attachments/nginx_attachment_util.h +++ b/core/include/attachments/nginx_attachment_util.h @@ -42,6 +42,9 @@ unsigned int getFailOpenTimeout(); int isFailOpenHoldMode(); unsigned int getFailOpenHoldTimeout(); +unsigned int getHoldVerdictPollingTime(); +unsigned int getHoldVerdictRetries(); + unsigned int getMaxSessionsPerMinute(); int isFailOpenOnSessionLimit(); From 23dbf06cd09743fcd210f9b9c7c1cf647a2a1862 Mon Sep 17 00:00:00 2001 From: wiaam96 <142393189+wiaam96@users.noreply.github.com> Date: Tue, 11 Feb 2025 12:01:51 +0200 Subject: [PATCH 2/2] Istio support (#30) * adding istio files * fix the envoy CMakList file * fix the envoy CMakList file * adding the .mod file * adding the webhook injector image * adding istio files * adding istio files * fix the envoy CMakList file * fix the envoy CMakList file * adding the .mod file * adding the webhook injector image * adding istio files * pulling from dev * fix the envoy CMakList file * adding istio files * fix missing header * fix wrong name of library * fix envoy CMakeLists * remove cloud guard names * remove cloud guard names * adding istio files * adding istio files * [JIRA] INXT-44274: test agent image * add Daniel fixes * remove zlib library * remove nano attachment ut --- attachments/CMakeLists.txt | 2 + attachments/envoy/CMakeLists.txt | 31 + attachments/envoy/build_template | 13 + attachments/envoy/config.go | 284 +++ attachments/envoy/filter.go | 489 +++++ attachments/envoy/go.mod | 20 + attachments/envoy/go.sum | 26 + attachments/envoy/utils.go | 108 + attachments/nano_attachment/CMakeLists.txt | 30 + .../include/mock_nano_access.h | 22 + .../include/mock_nano_attachment_io.h | 213 ++ .../include/mock_nano_attachment_sender.h | 72 + .../include/mock_nano_attachment_thread.h | 42 + .../include/mock_nano_compression.h | 72 + .../include/mock_nano_configuration.h | 39 + .../include/mock_nano_initializer.h | 52 + .../nano_attachment/include/mock_nano_poll.h | 22 + .../include/mock_nano_sender_thread.h | 23 + .../include/mock_nano_socket.h | 50 + .../nano_attachment/include/mock_nano_stat.h | 22 + .../nano_attachment/include/mock_shmem_ipc.h | 107 + attachments/nano_attachment/nano_attachment.c | 616 ++++++ .../nano_attachment/nano_attachment_io.c | 1776 +++++++++++++++++ .../nano_attachment/nano_attachment_io.h | 230 +++ .../nano_attachment/nano_attachment_metric.c | 83 + .../nano_attachment/nano_attachment_metric.h | 33 + .../nano_attachment/nano_attachment_sender.c | 795 ++++++++ .../nano_attachment/nano_attachment_sender.h | 137 ++ .../nano_attachment_sender_thread.c | 330 +++ .../nano_attachment_sender_thread.h | 178 ++ .../nano_attachment/nano_attachment_thread.c | 233 +++ .../nano_attachment/nano_attachment_thread.h | 46 + .../nano_attachment_util/CMakeLists.txt | 8 + .../nano_attachment_util.cc | 251 +++ .../nano_attachment_util_ut/CMakeLists.txt | 8 + .../nano_attachment_util_ut.cc | 125 ++ attachments/nano_attachment/nano_blockpage.h | 24 + .../nano_attachment/nano_compression.c | 130 ++ .../nano_attachment/nano_compression.h | 45 + .../nano_attachment/nano_configuration.c | 144 ++ .../nano_attachment/nano_configuration.h | 37 + .../nano_attachment/nano_initializer.c | 859 ++++++++ .../nano_attachment/nano_initializer.h | 218 ++ attachments/nano_attachment/nano_utils.c | 131 ++ attachments/nano_attachment/nano_utils.h | 90 + core/CMakeLists.txt | 1 + core/include/attachments/nano_attachment.h | 258 +++ .../attachments/nano_attachment_common.h | 489 +++++ .../attachments/nano_attachment_util.h | 67 + core/include/attachments/shmem_ipc_2.h | 79 + core/shmem_ipc_2/CMakeLists.txt | 6 + core/shmem_ipc_2/shared_ipc_debug.h | 55 + core/shmem_ipc_2/shared_ring_queue.c | 652 ++++++ core/shmem_ipc_2/shared_ring_queue.h | 114 ++ core/shmem_ipc_2/shmem_ipc.c | 419 ++++ docker/openappsec-waf-webhook/Dockerfile | 26 + docker/openappsec-waf-webhook/keygen.py | 85 + docker/openappsec-waf-webhook/run.sh | 10 + docker/openappsec-waf-webhook/secretgen.py | 65 + .../openappsec-waf-webhook/webhook_server.py | 653 ++++++ 60 files changed, 11245 insertions(+) create mode 100755 attachments/envoy/CMakeLists.txt create mode 100755 attachments/envoy/build_template create mode 100755 attachments/envoy/config.go create mode 100755 attachments/envoy/filter.go create mode 100755 attachments/envoy/go.mod create mode 100755 attachments/envoy/go.sum create mode 100755 attachments/envoy/utils.go create mode 100644 attachments/nano_attachment/CMakeLists.txt create mode 100644 attachments/nano_attachment/include/mock_nano_access.h create mode 100644 attachments/nano_attachment/include/mock_nano_attachment_io.h create mode 100644 attachments/nano_attachment/include/mock_nano_attachment_sender.h create mode 100644 attachments/nano_attachment/include/mock_nano_attachment_thread.h create mode 100755 attachments/nano_attachment/include/mock_nano_compression.h create mode 100644 attachments/nano_attachment/include/mock_nano_configuration.h create mode 100644 attachments/nano_attachment/include/mock_nano_initializer.h create mode 100644 attachments/nano_attachment/include/mock_nano_poll.h create mode 100644 attachments/nano_attachment/include/mock_nano_sender_thread.h create mode 100644 attachments/nano_attachment/include/mock_nano_socket.h create mode 100644 attachments/nano_attachment/include/mock_nano_stat.h create mode 100644 attachments/nano_attachment/include/mock_shmem_ipc.h create mode 100644 attachments/nano_attachment/nano_attachment.c create mode 100644 attachments/nano_attachment/nano_attachment_io.c create mode 100644 attachments/nano_attachment/nano_attachment_io.h create mode 100644 attachments/nano_attachment/nano_attachment_metric.c create mode 100644 attachments/nano_attachment/nano_attachment_metric.h create mode 100644 attachments/nano_attachment/nano_attachment_sender.c create mode 100644 attachments/nano_attachment/nano_attachment_sender.h create mode 100644 attachments/nano_attachment/nano_attachment_sender_thread.c create mode 100644 attachments/nano_attachment/nano_attachment_sender_thread.h create mode 100644 attachments/nano_attachment/nano_attachment_thread.c create mode 100644 attachments/nano_attachment/nano_attachment_thread.h create mode 100644 attachments/nano_attachment/nano_attachment_util/CMakeLists.txt create mode 100644 attachments/nano_attachment/nano_attachment_util/nano_attachment_util.cc create mode 100644 attachments/nano_attachment/nano_attachment_util/nano_attachment_util_ut/CMakeLists.txt create mode 100644 attachments/nano_attachment/nano_attachment_util/nano_attachment_util_ut/nano_attachment_util_ut.cc create mode 100644 attachments/nano_attachment/nano_blockpage.h create mode 100755 attachments/nano_attachment/nano_compression.c create mode 100755 attachments/nano_attachment/nano_compression.h create mode 100644 attachments/nano_attachment/nano_configuration.c create mode 100644 attachments/nano_attachment/nano_configuration.h create mode 100644 attachments/nano_attachment/nano_initializer.c create mode 100644 attachments/nano_attachment/nano_initializer.h create mode 100644 attachments/nano_attachment/nano_utils.c create mode 100644 attachments/nano_attachment/nano_utils.h create mode 100755 core/include/attachments/nano_attachment.h create mode 100644 core/include/attachments/nano_attachment_common.h create mode 100644 core/include/attachments/nano_attachment_util.h create mode 100755 core/include/attachments/shmem_ipc_2.h create mode 100755 core/shmem_ipc_2/CMakeLists.txt create mode 100755 core/shmem_ipc_2/shared_ipc_debug.h create mode 100755 core/shmem_ipc_2/shared_ring_queue.c create mode 100755 core/shmem_ipc_2/shared_ring_queue.h create mode 100755 core/shmem_ipc_2/shmem_ipc.c create mode 100755 docker/openappsec-waf-webhook/Dockerfile create mode 100755 docker/openappsec-waf-webhook/keygen.py create mode 100755 docker/openappsec-waf-webhook/run.sh create mode 100755 docker/openappsec-waf-webhook/secretgen.py create mode 100755 docker/openappsec-waf-webhook/webhook_server.py diff --git a/attachments/CMakeLists.txt b/attachments/CMakeLists.txt index d1aa43b..a790d8f 100644 --- a/attachments/CMakeLists.txt +++ b/attachments/CMakeLists.txt @@ -1 +1,3 @@ +add_subdirectory(envoy) +add_subdirectory(nano_attachment) add_subdirectory(nginx) diff --git a/attachments/envoy/CMakeLists.txt b/attachments/envoy/CMakeLists.txt new file mode 100755 index 0000000..bbce13e --- /dev/null +++ b/attachments/envoy/CMakeLists.txt @@ -0,0 +1,31 @@ +if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND ATTACHMENT_TYPE STREQUAL "envoy") + set(ATTACHMENTS_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/core/include/attachments) + set(NANO_ATTACHMENT_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/attachments/nano_attachment) + set(SHMEM_LIBRARY_DIR ${CMAKE_BINARY_DIR}/core/shmem_ipc_2) + set(NANO_ATTACHMENT_LIBRARY_DIR ${CMAKE_BINARY_DIR}/attachments/nano_attachment) + set(NANO_ATTACHMENT_UTIL_LIBRARY_DIR ${CMAKE_BINARY_DIR}/attachments/nano_attachment/nano_attachment_util) + set(LIBRARIES "-lnano_attachment -lnano_attachment_util -lshmem_ipc_2") + set(ENVOY_ATTACHMENT_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + + # Configure the build.sh script from the template + configure_file( + ${PROJECT_SOURCE_DIR}/attachments/envoy/build_template + ${CMAKE_BINARY_DIR}/attachments/envoy/build.sh + @ONLY + ) + + # Define a custom command to run the bash script + add_custom_target( + envoy_attachment ALL + COMMAND chmod +x ${CMAKE_BINARY_DIR}/attachments/envoy/build.sh + COMMAND ${CMAKE_BINARY_DIR}/attachments/envoy/build.sh + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/attachments/envoy + COMMENT "Building envoy attachment" + ) + + add_dependencies(envoy_attachment shmem_ipc_2 nano_attachment nano_attachment_util) + + install(FILES libenvoy_attachment.so DESTINATION ${CMAKE_BINARY_DIR}/attachments/envoy PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ) + install(FILES libenvoy_attachment.so DESTINATION lib PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ) +endif() + diff --git a/attachments/envoy/build_template b/attachments/envoy/build_template new file mode 100755 index 0000000..d54bb36 --- /dev/null +++ b/attachments/envoy/build_template @@ -0,0 +1,13 @@ +#!/bin/bash + +# Set environment variables +SHMEM_LIBRARY_DIR="@SHMEM_LIBRARY_DIR@" +NANO_ATTACHMENT_LIBRARY_DIR="@NANO_ATTACHMENT_LIBRARY_DIR@" +NANO_ATTACHMENT_UTIL_LIBRARY_DIR="@NANO_ATTACHMENT_UTIL_LIBRARY_DIR@" +LIBRARIES="@LIBRARIES@" +ENVOY_ATTACHMENT_DIR="@ENVOY_ATTACHMENT_DIR@" + +cd $ENVOY_ATTACHMENT_DIR + +# Run the go build command +CGO_CFLAGS="-I@ATTACHMENTS_INCLUDE_DIR@ -I@NANO_ATTACHMENT_INCLUDE_DIR@" go build -o ${ENVOY_ATTACHMENT_DIR}/libenvoy_attachment.so -buildmode=c-shared -ldflags="-extldflags '-L${SHMEM_LIBRARY_DIR} -L${NANO_ATTACHMENT_LIBRARY_DIR} -L${NANO_ATTACHMENT_UTIL_LIBRARY_DIR} ${LIBRARIES}'" diff --git a/attachments/envoy/config.go b/attachments/envoy/config.go new file mode 100755 index 0000000..617975e --- /dev/null +++ b/attachments/envoy/config.go @@ -0,0 +1,284 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "runtime" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/go-chi/chi/v5" + + xds "github.com/cncf/xds/go/xds/type/v3" + "google.golang.org/protobuf/types/known/anypb" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + envoyHttp "github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http" +) + +/* +#include + +unsigned long get_thread_id() { + return (unsigned long)pthread_self(); +} + +#include "nano_attachment_common.h" +#include "nano_initializer.h" +#include "nano_attachment.h" +*/ +import "C" + +const Name = "cp_nano_filter" +const admin_api_server_info = "http://127.0.0.1:%s/server_info" +const keep_alive_interval = 10 * time.Second + +var filter_id atomic.Int64 +var attachments_map map[int]*nano_attachment = nil +var thread_to_attachment_mapping map[int]int = nil +var attachment_to_thread_mapping map[int]int = nil +var attachment_to_filter_request_structs map[int]*filterRequestStructs = nil +var mutex sync.Mutex +var last_keep_alive time.Time + +type nano_attachment C.struct_NanoAttachment + +// EnvoyServerInfo represents the structure of the JSON response from /server_info +type EnvoyServerInfo struct { + Concurrency int `json:"concurrency"` +} + +func getEnvoyConcurrency() int { + concurrency_method := getEnv("CONCURRENCY_CALC", "numOfCores") + + if concurrency_method == "numOfCores" { + api.LogWarnf("using number of CPU cores") + return runtime.NumCPU() + } + + var conc_number string + + switch concurrency_method { + case "istioCpuLimit": + conc_number = getEnv("ISTIO_CPU_LIMIT", "-1") + api.LogWarnf("using istioCpuLimit, conc_number %s", conc_number) + case "custom": + conc_number = getEnv("CONCURRENCY_NUMBER", "-1") + api.LogWarnf("using custom concurrency number, conc_number %s", conc_number) + default: + api.LogWarnf("unknown concurrency method %s, using number of CPU cores", concurrency_method) + return runtime.NumCPU() + } + + if conc_number == "-1" { + api.LogWarnf("concurrency number is not set as an env variable, using number of CPU cores") + return runtime.NumCPU() + } + + conc_num, err := strconv.Atoi(conc_number) + if err != nil || conc_num <= 0 { + api.LogWarnf("error converting concurrency number %s, using number of CPU cores", conc_number) + return runtime.NumCPU() + } + + return conc_num +} + +func configurationServer() { + r := chi.NewRouter() + + r.Get("/load-config", func(w http.ResponseWriter, r *http.Request) { + mutex.Lock() + defer mutex.Unlock() + worker_ids := make([]int, 0) + workersParam := r.URL.Query().Get("workers") + num_of_workers := len(attachments_map) // concurrency + if workersParam == "" { + for i := 0; i < num_of_workers; i++ { + worker_ids = append(worker_ids, i) + } + } else { + workers := strings.Split(workersParam, ",") + for _, worker := range workers { + worker_id, err := strconv.Atoi(worker) + + if worker_id >= num_of_workers { + api.LogWarnf( + "Can not load configuration of invalid worker ID %d. worker ID should be lower than: %d", + worker_id, + num_of_workers) + } + + if err != nil || worker_id >= num_of_workers { + w.WriteHeader(http.StatusBadRequest) + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(fmt.Sprintf(`{"error": "invalid worker ID: %s"}`, worker))) + return + } + worker_ids = append(worker_ids, worker_id) + } + } + + workers_reload_status := make(map[string]string, len(worker_ids)) + res := C.NANO_OK + for _, worker_id := range worker_ids { + worker_reload_res := C.RestartAttachmentConfiguration((*C.NanoAttachment)(attachments_map[worker_id])) + if worker_reload_res == C.NANO_ERROR { + res = C.NANO_ERROR + workers_reload_status[strconv.Itoa(worker_id)] = "Reload Configuraiton Failed" + continue + } + workers_reload_status[strconv.Itoa(worker_id)] = "Reload Configuraiton Succeded" + } + + response, err := json.Marshal(workers_reload_status) + if err != nil { + api.LogWarnf("Error while sending reponse about reload configuration. Err: %s", err.Error()) + response = []byte(`{"error": "Internal Error"}`) + } + + if res == C.NANO_ERROR || err != nil { + w.WriteHeader(http.StatusInternalServerError) + } + + w.Header().Set("Content-Type", "application/json") + w.Write(response) + }) + + http.ListenAndServe(":8119", r) +} + +func init() { + last_keep_alive = time.Time{} + envoyHttp.RegisterHttpFilterConfigFactoryAndParser(Name, ConfigFactory, &parser{}) + go configurationServer() +} + +type config struct {} + +type parser struct {} + +func sendKeepAlive() { + for { + attachment_ptr := (*C.NanoAttachment)(attachments_map[0]) + if attachment_ptr == nil { + return + } + + C.SendKeepAlive(attachment_ptr) + time.Sleep(30 * time.Second) + } +} + +func (p *parser) initFilterStructs() *filterRequestStructs { + return &filterRequestStructs { + http_start_data: (*C.HttpRequestFilterData)(C.malloc(C.sizeof_HttpRequestFilterData)), + http_meta_data: (*C.HttpMetaData)(C.malloc(C.sizeof_HttpMetaData)), + http_headers: (*C.HttpHeaders)(C.malloc(C.sizeof_HttpHeaders)), + http_headers_data: (*C.HttpHeaderData)(C.malloc(10000 * C.sizeof_HttpHeaderData)), + http_res_headers: (*C.ResHttpHeaders)(C.malloc(C.sizeof_ResHttpHeaders)), + http_body_data: (*C.nano_str_t)(C.malloc(10000 * C.sizeof_nano_str_t)), + attachment_data: (*C.AttachmentData)(C.malloc(C.sizeof_AttachmentData)), + } +} + +// Parse the filter configuration. We can call the ConfigCallbackHandler to control the filter's +// behavior +func (p *parser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (interface{}, error) { + conf := &config{} + + if attachments_map != nil { + api.LogInfof("Waf Configuration already loaded") + return conf, nil + } + + num_of_workers := getEnvoyConcurrency() + + configStruct := &xds.TypedStruct{} + if err := any.UnmarshalTo(configStruct); err != nil { + return nil, err + } + + attachments_map = make(map[int]*nano_attachment) + attachment_to_filter_request_structs = make(map[int]*filterRequestStructs) + attachment_to_thread_mapping = make(map[int]int, 0) + thread_to_attachment_mapping = make(map[int]int, 0) + api.LogInfof("Number of worker threds: %d", num_of_workers) + for worker_id := 0; worker_id < num_of_workers; worker_id++ { + + attachment := C.InitNanoAttachment(C.uint8_t(0), C.int(worker_id), C.int(num_of_workers), C.int(C.fileno(C.stdout))) + for attachment == nil { + api.LogWarnf("attachment is nill going to sleep for two seconds and retry") + time.Sleep(2 * time.Second) + attachment = C.InitNanoAttachment(C.uint8_t(0), C.int(worker_id), C.int(num_of_workers), C.int(C.fileno(C.stdout))) + } + + //mutex.Lock() + attachments_map[worker_id] = (*nano_attachment)(attachment) + attachment_to_filter_request_structs[worker_id] = p.initFilterStructs() + //mutex.Unlock() + } + + go func (){ + sendKeepAlive() + }() + + return conf, nil +} + +// Merge configuration from the inherited parent configuration +func (p *parser) Merge(parent interface{}, child interface{}) interface{} { + parentConfig := parent.(*config) + + // copy one, do not update parentConfig directly. + newConfig := *parentConfig + return &newConfig +} + +func ConfigFactory(c interface{}) api.StreamFilterFactory { + conf, ok := c.(*config) + if !ok { + panic("unexpected config type") + } + + return func(callbacks api.FilterCallbackHandler) api.StreamFilter { + worker_thread_id := int(C.get_thread_id()) + api.LogDebugf("worker_thread_id: %d", worker_thread_id) + if _, ok := thread_to_attachment_mapping[int(worker_thread_id)]; !ok { + api.LogDebugf("need to add new thread to the map") + map_size := len(attachment_to_thread_mapping) + if map_size < len(attachments_map) { + attachment_to_thread_mapping[map_size] = worker_thread_id + thread_to_attachment_mapping[worker_thread_id] = map_size + api.LogDebugf("len(attachment_to_thread_mapping): %d", len(attachment_to_thread_mapping)) + api.LogDebugf("thread_to_attachment_mapping: %v", thread_to_attachment_mapping) + api.LogDebugf("attachment_to_thread_mapping: %v", attachment_to_thread_mapping) + } else { + panic("unexpected thread id") + } + } + + worker_id := thread_to_attachment_mapping[int(worker_thread_id)] + api.LogDebugf("worker_id: %d", worker_id) + + filter_id.Add(1) + session_id := filter_id.Load() + attachment_ptr := attachments_map[worker_id] + session_data := C.InitSessionData((*C.NanoAttachment)(attachment_ptr), C.SessionID(session_id)) + + return &filter{ + callbacks: callbacks, + config: conf, + session_id: session_id, + cp_attachment: attachment_ptr, + session_data: session_data, + request_structs: attachment_to_filter_request_structs[worker_id], + } + } +} + +func main() {} diff --git a/attachments/envoy/filter.go b/attachments/envoy/filter.go new file mode 100755 index 0000000..0f0549b --- /dev/null +++ b/attachments/envoy/filter.go @@ -0,0 +1,489 @@ +package main + +/* +#include + +unsigned long get_thread_id_2() { + return (unsigned long)pthread_self(); +} + +#include +#include +#include "nano_attachment_common.h" +#include "nano_attachment.h" + +HttpHeaderData* createHttpHeaderDataArray(int size) { + return (HttpHeaderData*)malloc(size * sizeof(HttpHeaderData)); +} + +HttpMetaData* createHttpMetaData() { + return (HttpMetaData*)malloc(sizeof(HttpMetaData)); +} + +void setHeaderElement(HttpHeaderData* arr, int index, nano_str_t key, nano_str_t value) { + if (arr == NULL) { + return; + } + + arr[index].key = key; + arr[index].value = value; +} +*/ +import "C" +import ( + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + + "strconv" + "strings" + "unsafe" +) + +func convertBlockPageToString(block_page C.BlockPageData) string { + block_page_size := block_page.title_prefix.len + + block_page.title.len + + block_page.body_prefix.len + + block_page.body.len + + block_page.uuid_prefix.len + + block_page.uuid.len + + block_page.uuid_suffix.len + + block_page_bytes := make([]byte, block_page_size) + + location := 0 + location = copyToSlice( + block_page_bytes, + unsafe.Pointer(block_page.title_prefix.data), + C.size_t(block_page.title_prefix.len), + location) + + location = copyToSlice( + block_page_bytes, + unsafe.Pointer(block_page.title.data), + C.size_t(block_page.title.len), + location) + + location = copyToSlice( + block_page_bytes, + unsafe.Pointer(block_page.body_prefix.data), + C.size_t(block_page.body_prefix.len), + location) + + location = copyToSlice( + block_page_bytes, + unsafe.Pointer(block_page.body.data), + C.size_t(block_page.body.len), + location) + + location = copyToSlice( + block_page_bytes, + unsafe.Pointer(block_page.uuid_prefix.data), + C.size_t(block_page.uuid_prefix.len), + location) + + location = copyToSlice( + block_page_bytes, + unsafe.Pointer(block_page.uuid.data), + C.size_t(block_page.uuid.len), + location) + + copyToSlice( + block_page_bytes, + unsafe.Pointer(block_page.uuid_suffix.data), + C.size_t(block_page.uuid_suffix.len), + location) + + return string(block_page_bytes) +} + +// The callbacks in the filter, like `DecodeHeaders`, can be implemented on demand. +// Because api.PassThroughStreamFilter provides a default implementation. +type filter struct { + api.PassThroughStreamFilter + + callbacks api.FilterCallbackHandler + path string + config *config + session_id int64 + session_data *C.HttpSessionData + cp_attachment *nano_attachment + request_structs *filterRequestStructs + body_buffer_chunk int +} + +type filterRequestStructs struct { + http_start_data *C.HttpRequestFilterData + http_meta_data *C.HttpMetaData + http_headers *C.HttpHeaders + http_headers_data *C.HttpHeaderData + http_res_headers *C.ResHttpHeaders + http_body_data *C.nano_str_t + attachment_data *C.AttachmentData +} + +func (f *filterRequestStructs) ZeroInitialize() { + if f.http_start_data != nil { + C.memset(unsafe.Pointer(f.http_start_data), 0, C.size_t(unsafe.Sizeof(*f.http_start_data))) + } + if f.http_meta_data != nil { + C.memset(unsafe.Pointer(f.http_meta_data), 0, C.size_t(unsafe.Sizeof(*f.http_meta_data))) + } + if f.http_headers != nil { + C.memset(unsafe.Pointer(f.http_headers), 0, C.size_t(unsafe.Sizeof(*f.http_headers))) + } + if f.http_headers_data != nil { + C.memset(unsafe.Pointer(f.http_headers_data), 0, C.size_t(unsafe.Sizeof(*f.http_headers_data))) + } + if f.attachment_data != nil { + C.memset(unsafe.Pointer(f.attachment_data), 0, C.size_t(unsafe.Sizeof(*f.attachment_data))) + } +} + +func (f *filter) isSessionFinalized() bool { + return C.IsSessionFinalized((*C.NanoAttachment)(f.cp_attachment), (*C.HttpSessionData)(f.session_data)) == 1 +} + +func (f *filter) sendData(data unsafe.Pointer, chunkType C.HttpChunkType) C.AttachmentVerdictResponse { + + attachment_data := f.request_structs.attachment_data + attachment_data.session_id = C.uint32_t(f.session_id) + attachment_data.chunk_type = chunkType // Adjust type as needed + attachment_data.session_data = f.session_data // Ensure `f.session_data` is compatible + attachment_data.data = C.DataBuffer(data) // Ensure `data` is compatible with `C.DataBuffer` + + return C.SendDataNanoAttachment((*C.NanoAttachment)(f.cp_attachment), attachment_data) +} + +func (f *filter) handleCustomResponse(verdict_response *C.AttachmentVerdictResponse) api.StatusType { + if verdict_response.web_response_data.web_response_type == C.CUSTOM_WEB_RESPONSE { + headers := map[string][]string{ + "Content-Type": []string{"text/html"}, + } + block_page_parts := C.GetBlockPage( + (*C.NanoAttachment)(f.cp_attachment), + (*C.HttpSessionData)(f.session_data), + (*C.AttachmentVerdictResponse)(verdict_response)) + return f.sendLocalReplyInternal(int(block_page_parts.response_code), convertBlockPageToString(block_page_parts), headers) + } + + redirect_data := C.GetRedirectPage( + (*C.NanoAttachment)(f.cp_attachment), + (*C.HttpSessionData)(f.session_data), + (*C.AttachmentVerdictResponse)(verdict_response)) + redirect_location := redirect_data.redirect_location + + redirect_location_slice := unsafe.Slice((*byte)(unsafe.Pointer(redirect_location.data)), redirect_location.len) + headers := map[string][]string{ + "Location": []string{string(redirect_location_slice)}, + } + + return f.sendLocalReplyInternal(307, "", headers) +} + +func (f *filter) finalizeRequest(verdict_response *C.AttachmentVerdictResponse) api.StatusType { + if C.AttachmentVerdict(verdict_response.verdict) == C.ATTACHMENT_VERDICT_DROP { + return f.handleCustomResponse(verdict_response) + } + + return api.Continue +} + +func (f *filter) handleHeaders(header api.HeaderMap) { + const envoy_headers_prefix = "x-envoy" + i := 0 + header.Range(func(key, value string) bool { + if i > 10000 { + return true + } + + api.LogInfof("inserting headers: key %s, value %s", key, value) + + if strings.HasPrefix(key, envoy_headers_prefix) || + key == "x-request-id" || + key == ":method" || + key == ":path" || + key == ":scheme" || + key == "x-forwarded-proto" { + return true + } + + if key == ":authority" { + key = "Host" + } + + key_nano_str := createNanoStrWithoutCopy(key) + value_nano_str := createNanoStrWithoutCopy(value) + C.setHeaderElement((*C.HttpHeaderData)(f.request_structs.http_headers_data), C.int(i), key_nano_str, value_nano_str) + i++ + return true + }) + + http_headers := f.request_structs.http_headers + http_headers.data = f.request_structs.http_headers_data + http_headers.headers_count = C.size_t(i) +} + +func (f *filter) sendBody(buffer api.BufferInstance, is_req bool) C.AttachmentVerdictResponse { + chunk_type := C.HTTP_REQUEST_BODY + if !is_req { + chunk_type = C.HTTP_RESPONSE_BODY + } + + data := buffer.Bytes() + data_len := len(data) + buffer_size := 8 * 1024 + num_of_buffers := ((data_len - 1) / buffer_size) + 1 + + // TO DO: FIX THIS ASAP + if num_of_buffers > 10000 { + num_of_buffers = 10000 + } + + + for i := 0; i < num_of_buffers; i++ { + nanoStrPtr := (*C.nano_str_t)(unsafe.Pointer(uintptr(unsafe.Pointer(f.request_structs.http_body_data)) + uintptr(i)*unsafe.Sizeof(*f.request_structs.http_body_data))) + nanoStrPtr.data = (*C.uchar)(unsafe.Pointer(&data[i * buffer_size])) + + if i + 1 == num_of_buffers { + nanoStrPtr.len = C.size_t(data_len - (i * buffer_size)) + } else { + nanoStrPtr.len = C.size_t(buffer_size) + } + + } + + http_chunks_array := C.HttpBody{ + data: f.request_structs.http_body_data, + bodies_count: C.size_t(num_of_buffers), + } + + api.LogInfof("sending body data: %+v", http_chunks_array) + return f.sendData(unsafe.Pointer(&http_chunks_array), C.HttpChunkType(chunk_type)) + +} + +func (f *filter) sendStartTransaction(start_transaction_data *C.HttpRequestFilterData) C.AttachmentVerdictResponse { + return f.sendData(unsafe.Pointer(&start_transaction_data), C.HTTP_REQUEST_FILTER) +} + +func (f *filter) handleStartTransaction(header api.RequestHeaderMap) { + stream_info := f.callbacks.StreamInfo() + + ip_location := 0 + port_location := 1 + + listening_address := stream_info.DownstreamLocalAddress() + listening_address_arr := strings.Split(listening_address, ":") + listening_port, _ := strconv.Atoi(listening_address_arr[port_location]) + + client_address := stream_info.DownstreamRemoteAddress() + client_addr_arr := strings.Split(client_address, ":") + client_port, _ := strconv.Atoi(client_addr_arr[port_location]) + + host := strings.Split(header.Host(), ":")[0] + + protocol, _ := stream_info.Protocol() + + // init start transaction struct + meta_data := f.request_structs.http_meta_data + meta_data.http_protocol = createNanoStr(protocol) + meta_data.method_name = createNanoStr(header.Method()) + meta_data.host = createNanoStr(host) + meta_data.listening_ip = createNanoStr(listening_address_arr[ip_location]) + meta_data.listening_port = C.uint16_t(listening_port) + meta_data.uri = createNanoStr(header.Path()) + meta_data.client_ip = createNanoStr(client_addr_arr[ip_location]) + meta_data.client_port = C.uint16_t(client_port) +} + +func (f *filter) sendLocalReplyInternal(ret_code int, custom_response string, headers map[string][]string) api.StatusType { + f.callbacks.SendLocalReply(ret_code, custom_response, headers, 0, "") + return api.LocalReply +} + +func (f *filter) endInspectionPart(chunk_type C.HttpChunkType) api.StatusType { + api.LogInfof("Ending inspection for current chunk") + res := f.sendData(nil, chunk_type) + + if C.AttachmentVerdict(res.verdict) != C.ATTACHMENT_VERDICT_INSPECT { + api.LogInfof("got final verict: %v", res.verdict) + return f.finalizeRequest(&res) + } + + return api.Continue +} + +// Callbacks which are called in request path +// The endStream is true if the request doesn't have body +func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api.StatusType { + ret := api.Continue + + defer RecoverPanic(&ret) + + if f.isSessionFinalized() { + api.LogInfof("session has already been inspected, no need for further inspection") + return api.Continue + } + + f.handleStartTransaction(header) + f.handleHeaders(header) + + http_start_data := f.request_structs.http_start_data + http_start_data.meta_data = f.request_structs.http_meta_data + http_start_data.req_headers = f.request_structs.http_headers + http_start_data.contains_body = C.bool(!endStream) + + res := f.sendData(unsafe.Pointer(http_start_data), C.HTTP_REQUEST_FILTER) + if C.AttachmentVerdict(res.verdict) != C.ATTACHMENT_VERDICT_INSPECT { + api.LogInfof("got final verict: %v", res.verdict) + return f.finalizeRequest(&res) + } + + return ret +} + +// DecodeData might be called multiple times during handling the request body. +// The endStream is true when handling the last piece of the body. +func (f *filter) DecodeData(buffer api.BufferInstance, endStream bool) api.StatusType { + ret := api.Continue + + defer RecoverPanic(&ret) + + if f.isSessionFinalized() { + return api.Continue + } + + if endStream && buffer.Len() == 0 { + return f.endInspectionPart(C.HttpChunkType(C.HTTP_REQUEST_END)) + } + + if buffer.Len() == 0 { + return ret + } + + res := f.sendBody(buffer, true) + if C.AttachmentVerdict(res.verdict) != C.ATTACHMENT_VERDICT_INSPECT { + api.LogInfof("got final verict: %v", res.verdict) + return f.finalizeRequest(&res) + } + + if endStream { + return f.endInspectionPart(C.HttpChunkType(C.HTTP_REQUEST_END)) + } + + return ret +} + +// Callbacks which are called in response path +// The endStream is true if the response doesn't have body +func (f *filter) EncodeHeaders(header api.ResponseHeaderMap, endStream bool) api.StatusType { + ret := api.Continue + + defer RecoverPanic(&ret) + + if f.isSessionFinalized() { + return api.Continue + } + + const content_length_key = "content-length" + const status_code_key = ":status" + + + content_length_str, _ := header.Get(content_length_key) + status_code_str, _ := header.Get(status_code_key) + content_length, _ := strconv.Atoi(content_length_str) + status_code, _ := strconv.Atoi(status_code_str) + + f.handleHeaders(header) + res_http_headers := f.request_structs.http_res_headers + res_http_headers.headers = f.request_structs.http_headers + res_http_headers.content_length = C.uint64_t(content_length) + res_http_headers.response_code = C.uint16_t(status_code) + + res := f.sendData(unsafe.Pointer(res_http_headers), C.HTTP_RESPONSE_HEADER) + if C.AttachmentVerdict(res.verdict) != C.ATTACHMENT_VERDICT_INSPECT { + api.LogInfof("got final verict: %v", res.verdict) + return f.finalizeRequest(&res) + } + + if endStream { + return f.endInspectionPart(C.HttpChunkType(C.HTTP_RESPONSE_END)) + } + + return ret +} + +func injectBodyChunk( + curr_modification *C.struct_NanoHttpModificationList, + body_buffer_chunk int, + buffer *api.BufferInstance) { + for curr_modification != nil { + if (int(curr_modification.modification.orig_buff_index) == body_buffer_chunk) { + mod := curr_modification.modification // type: HttpInjectData + modifications := C.GoString(curr_modification.modification_buffer) + new_buffer:= insertAtPosition((*buffer).String(), modifications, int(mod.injection_pos)) + (*buffer).SetString(new_buffer) + } + curr_modification = curr_modification.next + } +} + +// EncodeData might be called multiple times during handling the response body. +// The endStream is true when handling the last piece of the body. +func (f *filter) EncodeData(buffer api.BufferInstance, endStream bool) api.StatusType { + ret := api.Continue + + defer RecoverPanic(&ret) + + if f.isSessionFinalized() { + return api.Continue + } + + if endStream && buffer.Len() == 0 { + return f.endInspectionPart(C.HttpChunkType(C.HTTP_RESPONSE_END)) + } + + if buffer.Len() == 0 { + return ret + } + + res := f.sendBody(buffer, false) + injectBodyChunk(res.modifications, f.body_buffer_chunk, &buffer) + f.body_buffer_chunk++ + if C.AttachmentVerdict(res.verdict) != C.ATTACHMENT_VERDICT_INSPECT { + api.LogInfof("got final verict: %v", res.verdict) + return f.finalizeRequest(&res) + } + + if endStream { + return f.endInspectionPart(C.HttpChunkType(C.HTTP_RESPONSE_END)) + } + + return ret +} + +// ____________NOT IMPLEMENTED AT THE MOMENT____________ +func (f *filter) DecodeTrailers(trailers api.RequestTrailerMap) api.StatusType { + // support suspending & resuming the filter in a background goroutine + return api.Continue +} + +func (f *filter) EncodeTrailers(trailers api.ResponseTrailerMap) api.StatusType { + return api.Continue +} + +// OnLog is called when the HTTP stream is ended on HTTP Connection Manager filter. +func (f *filter) OnLog() {} + +// OnLogDownstreamStart is called when HTTP Connection Manager filter receives a new HTTP request +// (required the corresponding access log type is enabled) +func (f *filter) OnLogDownstreamStart() {} + +// OnLogDownstreamPeriodic is called on any HTTP Connection Manager periodic log record +// (required the corresponding access log type is enabled) +func (f *filter) OnLogDownstreamPeriodic() {} + +func (f *filter) OnDestroy(reason api.DestroyReason) { + freeHttpMetaDataFields(f.request_structs.http_meta_data) + f.request_structs.ZeroInitialize() + C.FiniSessionData((*C.NanoAttachment)(f.cp_attachment), f.session_data) +} diff --git a/attachments/envoy/go.mod b/attachments/envoy/go.mod new file mode 100755 index 0000000..21bb21c --- /dev/null +++ b/attachments/envoy/go.mod @@ -0,0 +1,20 @@ +module gitlab.ngen.checkpoint.com/Ngen/agent-core/attachments/envoy + +// the version should >= 1.18 +go 1.20 + +// NOTICE: these lines could be generated automatically by "go mod tidy" +require ( + github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa + github.com/envoyproxy/envoy v1.29.8-0.20240702140355-f3e7e90ed021 + google.golang.org/protobuf v1.34.2 +) + +require github.com/go-chi/chi/v5 v5.1.0 + +require ( + github.com/envoyproxy/protoc-gen-validate v1.0.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect +) diff --git a/attachments/envoy/go.sum b/attachments/envoy/go.sum new file mode 100755 index 0000000..e2e1e25 --- /dev/null +++ b/attachments/envoy/go.sum @@ -0,0 +1,26 @@ +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= +github.com/envoyproxy/envoy v1.28.1-0.20231218050644-ca5d45d1887a h1:PQveCjvjXZS400K7z+uuouN/Q/dLhLpB5a/6GIC4v34= +github.com/envoyproxy/envoy v1.28.1-0.20231218050644-ca5d45d1887a/go.mod h1:STO/nOGQMw2DNOFTaokM1VY72GJBs0mbXq0I1lJr0XQ= +github.com/envoyproxy/envoy v1.29.8-0.20240702140355-f3e7e90ed021 h1:mTisjPVHGpxlWi7Yj7PnpxD0GeoauHcWvEgch069i+M= +github.com/envoyproxy/envoy v1.29.8-0.20240702140355-f3e7e90ed021/go.mod h1:ujBFxE543X8OePZG+FbeR9LnpBxTLu64IAU7A20EB9A= +github.com/envoyproxy/envoy v1.31.0 h1:NsTo+medzu0bMffXAjl+zKaViLOShKuIZWQnKKYq0/4= +github.com/envoyproxy/envoy v1.31.0/go.mod h1:ujBFxE543X8OePZG+FbeR9LnpBxTLu64IAU7A20EB9A= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= diff --git a/attachments/envoy/utils.go b/attachments/envoy/utils.go new file mode 100755 index 0000000..0d0c90e --- /dev/null +++ b/attachments/envoy/utils.go @@ -0,0 +1,108 @@ +package main + +/* +#include +#include "nano_attachment_common.h" +#include "nano_attachment.h" +#include +*/ +import "C" +import ( + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + + "reflect" + "unsafe" + "os" + "runtime" + "strconv" +) +func getEnv(key, defaultValue string) string { + value, exists := os.LookupEnv(key) + if !exists { + return defaultValue + } + return value +} + +var INSERT_POS_ERR_MSG = "Got invalid insertion position, will not insert." + +func copyToSlice(dest []byte, src unsafe.Pointer, size C.size_t, location int) int { + C.memcpy(unsafe.Pointer(&dest[location]), src, size) + return location + int(size) +} + +func newNanoStr(data []byte) *C.nano_str_t { + nanoStr := (*C.nano_str_t)(C.malloc(C.size_t(unsafe.Sizeof(C.nano_str_t{})))) + if nanoStr == nil { + panic("failed to allocate memory for nano_str_t struct") + } + + nanoStr.len = C.size_t(len(data)) + return nanoStr +} + +func insertAtPosition(buff string, injection string, pos int) string { + if pos < 0 || pos > len(buff) { + api.LogDebugf( + INSERT_POS_ERR_MSG + + " Position: " + + strconv.Itoa(pos) + + ", buffer's lenght: " + + strconv.Itoa(len(buff))) + return buff + } + return_buff := buff[:pos] + injection + buff[pos:] + return return_buff +} + +func createNanoStr(str string) C.nano_str_t { + c_str := C.CString(str) + nanoStr := C.nano_str_t{ + len: C.size_t(len(str)), + data: (*C.uchar)(unsafe.Pointer(c_str)), + } + + return nanoStr +} + +func createNanoStrWithoutCopy(str string) C.nano_str_t { + nanoStr := C.nano_str_t{ + len: C.size_t(len(str)), + data: (*C.uchar)(unsafe.Pointer((*(*reflect.StringHeader)(unsafe.Pointer(&str))).Data)), + } + + return nanoStr +} + +func freeNanoStr(str *C.nano_str_t) { + C.free(unsafe.Pointer(str.data)) +} + +func freeHttpMetaDataFields(meta_data *C.HttpMetaData) { + freeNanoStr(&(*meta_data).http_protocol) + freeNanoStr(&(*meta_data).method_name) + freeNanoStr(&(*meta_data).host) + freeNanoStr(&(*meta_data).listening_ip) + freeNanoStr(&(*meta_data).uri) + freeNanoStr(&(*meta_data).client_ip) +} + +func freeHeaders(header_arr *C.HttpHeaderData, header_slice []C.HttpHeaderData) { + C.free(unsafe.Pointer(header_arr)) + + for _, header := range header_slice { + freeNanoStr(&(header.key)) + freeNanoStr(&(header.value)) + } +} + +func RecoverPanic(ret *api.StatusType) { + if e := recover(); e != nil { + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + api.LogErrorf("http: panic serving: %v\n%s", e, buf) + + *ret = api.Continue + } +} diff --git a/attachments/nano_attachment/CMakeLists.txt b/attachments/nano_attachment/CMakeLists.txt new file mode 100644 index 0000000..fdd41ec --- /dev/null +++ b/attachments/nano_attachment/CMakeLists.txt @@ -0,0 +1,30 @@ +add_subdirectory(nano_attachment_util) + +include_directories(include) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_GNU_SOURCE -lpthread -Wall") + +link_directories(${CMAKE_BINARY_DIR}/core) +link_directories(${CMAKE_BINARY_DIR}/core/shmem_ipc) +include_directories(${PROJECT_SOURCE_DIR}/core/include/attachments) + + +add_library( + nano_attachment + SHARED + nano_attachment.c + nano_configuration.c + nano_initializer.c + nano_utils.c + nano_attachment_io.c + nano_attachment_thread.c + nano_attachment_sender.c + nano_attachment_sender_thread.c + nano_attachment_metric.c + nano_compression.c +) + +target_link_libraries(nano_attachment shmem_ipc_2 nano_attachment_util osrc_compression_utils) + +install(TARGETS nano_attachment DESTINATION lib) +install(TARGETS nano_attachment DESTINATION nginx_attachment/lib PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ) diff --git a/attachments/nano_attachment/include/mock_nano_access.h b/attachments/nano_attachment/include/mock_nano_access.h new file mode 100644 index 0000000..ef501cd --- /dev/null +++ b/attachments/nano_attachment/include/mock_nano_access.h @@ -0,0 +1,22 @@ +#ifndef __MOCK_ACCESS_H__ +#define __MOCK_ACCESS_H__ + +#include "cmock.h" + +extern "C" { +#include // For the access function +} + +class NanoAccessMocker : public CMockMocker +{ +public: + MOCK_METHOD2(access, int(const char *path, int mode)); +}; + +CMOCK_MOCK_FUNCTION2( + NanoAccessMocker, + access, + int(const char *path, int mode) +); + +#endif // __MOCK_ACCESS_H__ diff --git a/attachments/nano_attachment/include/mock_nano_attachment_io.h b/attachments/nano_attachment/include/mock_nano_attachment_io.h new file mode 100644 index 0000000..677f228 --- /dev/null +++ b/attachments/nano_attachment/include/mock_nano_attachment_io.h @@ -0,0 +1,213 @@ +#ifndef __MOCK_NANO_INITIALIZER_IO_H__ +#define __MOCK_NANO_INITIALIZER_IO_H__ + +#include "cmock.h" +#include "nano_attachment_common.h" + +extern "C" { +#include "nano_attachment_io.h" +} + +class NanoAttachmentIoMocker : public CMockMocker +{ +public: + MOCK_METHOD1( + connect_to_comm_socket, + NanoCommunicationResult(NanoAttachment *attachment) + ); + + MOCK_METHOD1( + connect_to_registration_socket, + NanoCommunicationResult(NanoAttachment *attachment) + ); + + MOCK_METHOD6( + nano_metadata_sender, + void( + NanoAttachment *attachment, + HttpMetaData *metadata, + HttpEventThreadCtx *ctx, + uint32_t cur_request_id, + unsigned int *num_of_messages_sent, + bool is_verdict_requested + ) + ); + + MOCK_METHOD7( + nano_header_sender, + void( + NanoAttachment *attachment, + HttpHeaders *headers, + HttpEventThreadCtx *ctx, + AttachmentDataType header_type, + uint32_t cur_request_id, + unsigned int *num_messages_sent, + bool is_verdict_requested + ) + ); + + MOCK_METHOD5( + nano_send_response_code, + void( + NanoAttachment *attachment, + uint16_t response_code, + HttpEventThreadCtx *ctx, + uint32_t cur_request_id, + unsigned int *num_messages_sent + ) + ); + + MOCK_METHOD5( + nano_send_response_content_length, + void( + NanoAttachment *attachment, + uint64_t content_length, + HttpEventThreadCtx *ctx, + uint32_t cur_request_id, + unsigned int *num_messages_sent + ) + ); + + MOCK_METHOD6( + nano_body_sender, + void( + NanoAttachment *attachment, + HttpBody *bodies, + HttpEventThreadCtx *ctx, + AttachmentDataType body_type, + uint32_t cur_request_id, + unsigned int *num_messages_sent + ) + ); + + MOCK_METHOD5( + nano_end_transaction_sender, + void( + NanoAttachment *attachment, + AttachmentDataType end_transaction_type, + HttpEventThreadCtx *ctx, + SessionID cur_request_id, + unsigned int *num_messages_sent + ) + ); + + MOCK_METHOD4( + nano_request_delayed_verdict, + void( + NanoAttachment *attachment, + HttpEventThreadCtx *ctx, + SessionID cur_request_id, + unsigned int *num_messages_sent + ) + ); + + MOCK_METHOD1(nano_send_metric_data_sender, void(NanoAttachment *Attachment)); +}; + +CMOCK_MOCK_FUNCTION1( + NanoAttachmentIoMocker, + connect_to_comm_socket, + NanoCommunicationResult(NanoAttachment *attachment) +); + +CMOCK_MOCK_FUNCTION1( + NanoAttachmentIoMocker, + connect_to_registration_socket, + NanoCommunicationResult(NanoAttachment *attachment) +); + +CMOCK_MOCK_FUNCTION6( + NanoAttachmentIoMocker, + nano_metadata_sender, + void( + NanoAttachment *attachment, + HttpMetaData *metadata, + HttpEventThreadCtx *ctx, + uint32_t cur_request_id, + unsigned int *num_of_messages_sent, + bool is_verdict_requested + ) +); + +CMOCK_MOCK_FUNCTION7( + NanoAttachmentIoMocker, + nano_header_sender, + void( + NanoAttachment *attachment, + HttpHeaders *headers, + HttpEventThreadCtx *ctx, + AttachmentDataType header_type, + uint32_t cur_request_id, + unsigned int *num_messages_sent, + bool is_verdict_requested + ) +); + +CMOCK_MOCK_FUNCTION5( + NanoAttachmentIoMocker, + nano_send_response_code, + void( + NanoAttachment *attachment, + uint16_t response_code, + HttpEventThreadCtx *ctx, + uint32_t cur_request_id, + unsigned int *num_messages_sent + ) +); + +CMOCK_MOCK_FUNCTION5( + NanoAttachmentIoMocker, + nano_send_response_content_length, + void( + NanoAttachment *attachment, + uint64_t content_length, + HttpEventThreadCtx *ctx, + uint32_t cur_request_id, + unsigned int *num_messages_sent + ) +); + + +CMOCK_MOCK_FUNCTION6( + NanoAttachmentIoMocker, + nano_body_sender, + void( + NanoAttachment *attachment, + HttpBody *bodies, + HttpEventThreadCtx *ctx, + AttachmentDataType body_type, + uint32_t cur_request_id, + unsigned int *num_messages_sent + ) +); + +CMOCK_MOCK_FUNCTION5( + NanoAttachmentIoMocker, + nano_end_transaction_sender, + void( + NanoAttachment *attachment, + AttachmentDataType end_transaction_type, + HttpEventThreadCtx *ctx, + SessionID cur_request_id, + unsigned int *num_messages_sent + ) +); + +CMOCK_MOCK_FUNCTION4( + NanoAttachmentIoMocker, + nano_request_delayed_verdict, + void( + NanoAttachment *attachment, + HttpEventThreadCtx *ctx, + SessionID cur_request_id, + unsigned int *num_messages_sent + ) +); + +CMOCK_MOCK_FUNCTION1( + NanoAttachmentIoMocker, + nano_send_metric_data_sender, + void(NanoAttachment *Attachment) +); + +#endif // __MOCK_NANO_INITIALIZER_IO_H__ diff --git a/attachments/nano_attachment/include/mock_nano_attachment_sender.h b/attachments/nano_attachment/include/mock_nano_attachment_sender.h new file mode 100644 index 0000000..2d7eb2b --- /dev/null +++ b/attachments/nano_attachment/include/mock_nano_attachment_sender.h @@ -0,0 +1,72 @@ +#ifndef __MOCK_NANO_ATTACHMENT_SENDER_H__ +#define __MOCK_NANO_ATTACHMENT_SENDER_H__ + +#include "cmock.h" +#include "nano_attachment_common.h" + +extern "C" { +#include "nano_attachment_sender.h" +} + +class NanoAttachmentSenderMocker : public CMockMocker +{ +public: + MOCK_METHOD2(SendMetadata, AttachmentVerdictResponse(NanoAttachment *attachment, AttachmentData *data)); + MOCK_METHOD2(SendRequestHeaders, AttachmentVerdictResponse(NanoAttachment *attachment, AttachmentData *data)); + MOCK_METHOD2(SendResponseHeaders, AttachmentVerdictResponse(NanoAttachment *attachment, AttachmentData *data)); + MOCK_METHOD2(SendRequestBody, AttachmentVerdictResponse(NanoAttachment *attachment, AttachmentData *data)); + MOCK_METHOD2(SendResponseBody, AttachmentVerdictResponse(NanoAttachment *attachment, AttachmentData *data)); + MOCK_METHOD2(SendRequestEnd, AttachmentVerdictResponse(NanoAttachment *attachment, AttachmentData *data)); + MOCK_METHOD2(SendResponseEnd, AttachmentVerdictResponse(NanoAttachment *attachment, AttachmentData *data)); + MOCK_METHOD1(SendMetricData, NanoCommunicationResult(NanoAttachment *attachment)); +}; + +CMOCK_MOCK_FUNCTION2( + NanoAttachmentSenderMocker, + SendMetadata, + AttachmentVerdictResponse(NanoAttachment *attachment, AttachmentData *data) +); + +CMOCK_MOCK_FUNCTION2( + NanoAttachmentSenderMocker, + SendRequestHeaders, + AttachmentVerdictResponse(NanoAttachment *attachment, AttachmentData *data) +); + +CMOCK_MOCK_FUNCTION2( + NanoAttachmentSenderMocker, + SendResponseHeaders, + AttachmentVerdictResponse(NanoAttachment *attachment, AttachmentData *data) +); + +CMOCK_MOCK_FUNCTION2( + NanoAttachmentSenderMocker, + SendRequestBody, + AttachmentVerdictResponse(NanoAttachment *attachment, AttachmentData *data) +); + +CMOCK_MOCK_FUNCTION2( + NanoAttachmentSenderMocker, + SendResponseBody, + AttachmentVerdictResponse(NanoAttachment *attachment, AttachmentData *data) +); + +CMOCK_MOCK_FUNCTION2( + NanoAttachmentSenderMocker, + SendRequestEnd, + AttachmentVerdictResponse(NanoAttachment *attachment, AttachmentData *data) +); + +CMOCK_MOCK_FUNCTION2( + NanoAttachmentSenderMocker, + SendResponseEnd, + AttachmentVerdictResponse(NanoAttachment *attachment, AttachmentData *data) +); + +CMOCK_MOCK_FUNCTION1( + NanoAttachmentSenderMocker, + SendMetricData, + NanoCommunicationResult(NanoAttachment *attachment) +); + +#endif // __MOCK_NANO_ATTACHMENT_SENDER_H__ diff --git a/attachments/nano_attachment/include/mock_nano_attachment_thread.h b/attachments/nano_attachment/include/mock_nano_attachment_thread.h new file mode 100644 index 0000000..dff771c --- /dev/null +++ b/attachments/nano_attachment/include/mock_nano_attachment_thread.h @@ -0,0 +1,42 @@ +#ifndef __MOCK_NANO_ATTACHMENT_THREAD_H__ +#define __MOCK_NANO_ATTACHMENT_THREAD_H__ + +#include "cmock.h" +#include "nano_attachment_common.h" + +extern "C" { +#include "nano_attachment_thread.h" +} + +class NanoAttachmentThreadMocker : public CMockMocker +{ +public: + MOCK_METHOD7( + NanoRunInThreadTimeout, + int( + NanoAttachment *attachment, + AttachmentData *data, + CpThreadRoutine thread_func, + void *arg, + int timeout_msecs, + char *func_name, + TransactionType transaction_type + ) + ); +}; + +CMOCK_MOCK_FUNCTION7( + NanoAttachmentThreadMocker, + NanoRunInThreadTimeout, + int( + NanoAttachment *attachment, + AttachmentData *data, + CpThreadRoutine thread_func, + void *arg, + int timeout_msecs, + char *func_name, + TransactionType transaction_type + ) +); + +#endif // __MOCK_NANO_ATTACHMENT_THREAD_H__ diff --git a/attachments/nano_attachment/include/mock_nano_compression.h b/attachments/nano_attachment/include/mock_nano_compression.h new file mode 100755 index 0000000..3d57f95 --- /dev/null +++ b/attachments/nano_attachment/include/mock_nano_compression.h @@ -0,0 +1,72 @@ +#ifndef __MOCK_NANO_COMPRESSION_H__ +#define __MOCK_NANO_COMPRESSION_H__ + +#include "cmock.h" +#include "nano_attachment_common.h" + +extern "C" { +#include "nano_compression.h" +} + +class NanoCompressionMocker : public CMockMocker +{ +public: + MOCK_METHOD3( + nano_compress_body, + HttpBody*( + NanoAttachment *attachment, + HttpBody *bodies, + HttpSessionData *session_data_p + ) + ); + + MOCK_METHOD3( + nano_decompress_body, + HttpBody*( + NanoAttachment *attachment, + HttpBody *bodies, + HttpSessionData *session_data_p + ) + ); + + MOCK_METHOD3( + nano_free_compressed_body, + void( + NanoAttachment *attachment, + HttpBody *bodies, + HttpSessionData *session_data_p + ) + ); +}; + +CMOCK_MOCK_FUNCTION3( + NanoCompressionMocker, + nano_compress_body, + HttpBody*( + NanoAttachment *attachment, + HttpBody *bodies, + HttpSessionData *session_data_p + ) +); + +CMOCK_MOCK_FUNCTION3( + NanoCompressionMocker, + nano_decompress_body, + HttpBody*( + NanoAttachment *attachment, + HttpBody *bodies, + HttpSessionData *session_data_p + ) +); + +CMOCK_MOCK_FUNCTION3( + NanoCompressionMocker, + nano_free_compressed_body, + void( + NanoAttachment *attachment, + HttpBody *bodies, + HttpSessionData *session_data_p + ) +); + +#endif // __MOCK_NANO_COMPRESSION_H__ diff --git a/attachments/nano_attachment/include/mock_nano_configuration.h b/attachments/nano_attachment/include/mock_nano_configuration.h new file mode 100644 index 0000000..3a34085 --- /dev/null +++ b/attachments/nano_attachment/include/mock_nano_configuration.h @@ -0,0 +1,39 @@ +#ifndef __MOCK_NANO_CONFIGURATION_H__ +#define __MOCK_NANO_CONFIGURATION_H__ + +#include "cmock.h" +#include "nano_attachment_common.h" + +extern "C" { +#include "nano_configuration.h" +} + +class NanoConfigurationMocker : public CMockMocker +{ +public: + MOCK_METHOD2( + init_attachment_config, + NanoCommunicationResult( + NanoAttachment *attachment, + const char *conf_path + ) + ); + MOCK_METHOD1(reset_attachment_config, NanoCommunicationResult(NanoAttachment *attachment)); +}; + +CMOCK_MOCK_FUNCTION2( + NanoConfigurationMocker, + init_attachment_config, + NanoCommunicationResult( + NanoAttachment *attachment, + const char *conf_path + ) +); + +CMOCK_MOCK_FUNCTION1( + NanoConfigurationMocker, + reset_attachment_config, + NanoCommunicationResult(NanoAttachment *attachment) +); + +#endif // __MOCK_NANO_CONFIGURATION_H__ diff --git a/attachments/nano_attachment/include/mock_nano_initializer.h b/attachments/nano_attachment/include/mock_nano_initializer.h new file mode 100644 index 0000000..82cbbac --- /dev/null +++ b/attachments/nano_attachment/include/mock_nano_initializer.h @@ -0,0 +1,52 @@ +#ifndef __MOCK_NANO_INITIALIZER_H__ +#define __MOCK_NANO_INITIALIZER_H__ + +#include "cmock.h" +#include "nano_attachment_common.h" + +extern "C" { +#include "nano_initializer.h" +} + +class NanoInitializerMocker : public CMockMocker +{ +public: + MOCK_METHOD1(nano_attachment_init_process, NanoCommunicationResult(NanoAttachment *attachment)); + MOCK_METHOD5( + write_to_service, + NanoCommunicationResult( + NanoAttachment *attachment, + int *socket, + void *data, + uint32_t size, + struct timeval *absolute_end_time + ) + ); + MOCK_METHOD1(handle_shmem_corruption, NanoCommunicationResult(NanoAttachment *attachment)); +}; + +CMOCK_MOCK_FUNCTION1( + NanoInitializerMocker, + nano_attachment_init_process, + NanoCommunicationResult(NanoAttachment *attachment) +); + +CMOCK_MOCK_FUNCTION5( + NanoInitializerMocker, + write_to_service, + NanoCommunicationResult( + NanoAttachment *attachment, + int *socket, + void *data, + uint32_t size, + struct timeval *absolute_end_time + ) +); + +CMOCK_MOCK_FUNCTION1( + NanoInitializerMocker, + handle_shmem_corruption, + NanoCommunicationResult(NanoAttachment *attachment) +); + +#endif // __MOCK_NANO_INITIALIZER_H__ diff --git a/attachments/nano_attachment/include/mock_nano_poll.h b/attachments/nano_attachment/include/mock_nano_poll.h new file mode 100644 index 0000000..6b5e07b --- /dev/null +++ b/attachments/nano_attachment/include/mock_nano_poll.h @@ -0,0 +1,22 @@ +#ifndef __MOCK_NANO_POLL_H__ +#define __MOCK_NANO_POLL_H__ + +#include "cmock.h" + +extern "C" { +#include +} + +class NanoPollMocker : public CMockMocker +{ +public: + MOCK_METHOD3(poll, int(pollfd *fds, nfds_t nfds, int timeout)); +}; + +CMOCK_MOCK_FUNCTION3( + NanoPollMocker, + poll, + int(pollfd *fds, nfds_t nfds, int timeout) +); + +#endif // __MOCK_NANO_POLL_H__ diff --git a/attachments/nano_attachment/include/mock_nano_sender_thread.h b/attachments/nano_attachment/include/mock_nano_sender_thread.h new file mode 100644 index 0000000..18ab20e --- /dev/null +++ b/attachments/nano_attachment/include/mock_nano_sender_thread.h @@ -0,0 +1,23 @@ +#ifndef __MOCK_NANO_ATTACHMENT_SENDER_THREAD_H__ +#define __MOCK_NANO_ATTACHMENT_SENDER_THREAD_H__ + +#include "cmock.h" +#include "nano_attachment_common.h" + +extern "C" { +#include "nano_attachment_sender_thread.h" +} + +class NanoSenderThreadMocker : public CMockMocker +{ +public: + MOCK_METHOD1(SendRequestEndThread, void *(void *_ctx)); +}; + +CMOCK_MOCK_FUNCTION1( + NanoSenderThreadMocker, + SendRequestEndThread, + void *(void *_ctx) +); + +#endif // __MOCK_NANO_ATTACHMENT_SENDER_THREAD_H__ diff --git a/attachments/nano_attachment/include/mock_nano_socket.h b/attachments/nano_attachment/include/mock_nano_socket.h new file mode 100644 index 0000000..bef42b1 --- /dev/null +++ b/attachments/nano_attachment/include/mock_nano_socket.h @@ -0,0 +1,50 @@ +#ifndef __MOCK_NANO_SOCKET_H__ +#define __MOCK_NANO_SOCKET_H__ + +#include "cmock.h" + +extern "C" { +#include +} + +class NanoSocketMocker : public CMockMocker +{ +public: + MOCK_METHOD3(socket, int(int domain, int type, int protocol)); + MOCK_METHOD3(connect, int(int sockfd, const struct sockaddr *addr, socklen_t addrlen)); + MOCK_METHOD1(close, int(int sockfd)); + MOCK_METHOD3(write, ssize_t(int fd, const void *buf, size_t count)); + MOCK_METHOD3(read, ssize_t(int fd, void *buf, size_t count)); +}; + +CMOCK_MOCK_FUNCTION3( + NanoSocketMocker, + socket, + int(int domain, int type, int protocol) +); + +CMOCK_MOCK_FUNCTION3( + NanoSocketMocker, + connect, + int(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +); + +CMOCK_MOCK_FUNCTION1( + NanoSocketMocker, + close, + int(int sockfd) +); + +CMOCK_MOCK_FUNCTION3( + NanoSocketMocker, + write, + ssize_t(int fd, const void *buf, size_t count) +); + +CMOCK_MOCK_FUNCTION3( + NanoSocketMocker, + read, + ssize_t(int fd, void *buf, size_t count) +); + +#endif // __MOCK_NANO_SOCKET_H__ diff --git a/attachments/nano_attachment/include/mock_nano_stat.h b/attachments/nano_attachment/include/mock_nano_stat.h new file mode 100644 index 0000000..11885f0 --- /dev/null +++ b/attachments/nano_attachment/include/mock_nano_stat.h @@ -0,0 +1,22 @@ +#ifndef __MOCK_NANO_STAT_H__ +#define __MOCK_NANO_STAT_H__ + +#include "cmock.h" + +extern "C" { +#include +} + +class NanoStatMocker : public CMockMocker +{ +public: + MOCK_METHOD2(mkdir, int(const char *pathname, mode_t mode)); +}; + +CMOCK_MOCK_FUNCTION2( + NanoStatMocker, + mkdir, + int(const char *pathname, mode_t mode) +); + +#endif // __MOCK_NANO_STAT_H__ diff --git a/attachments/nano_attachment/include/mock_shmem_ipc.h b/attachments/nano_attachment/include/mock_shmem_ipc.h new file mode 100644 index 0000000..1015da4 --- /dev/null +++ b/attachments/nano_attachment/include/mock_shmem_ipc.h @@ -0,0 +1,107 @@ +#ifndef __MOCK_SHMEM_IPC__ +#define __MOCK_SHMEM_IPC__ + +#include "cmock.h" +#include "cptest.h" + +extern "C" { +#include "shmem_ipc_2.h" +} + +class NanoShmemIPCMocker : public CMockMocker +{ +public: + MOCK_METHOD7( + initIpc, + SharedMemoryIPC *( + const char queue_name[32], + uint32_t user_id, + uint32_t group_id, + int is_owner, + uint16_t num_of_queue_elem, + const LoggingData *logging_data, + void (*debug_func)( + const LoggingData *loggin_data, + uint32_t worker_id, + int is_error, + const char *func, + const char *file, + int line_num, + const char *fmt, + ... + ) + ) + ); + MOCK_METHOD2(destroyIpc, void(SharedMemoryIPC *ipc, int is_owner)); + MOCK_METHOD2(resetIpc, void(SharedMemoryIPC *ipc, uint16_t num_of_data_segments)); + MOCK_METHOD3( + sendData, + int(SharedMemoryIPC *ipc, const uint16_t data_to_send_size, const char *data_to_send) + ); + MOCK_METHOD4( + sendChunkedData, + int( + SharedMemoryIPC *ipc, + const uint16_t *data_to_send_sizes, + const char **data_elem_to_send, + const uint8_t num_of_data_elem + ) + ); + MOCK_METHOD3( + receiveData, + int(SharedMemoryIPC *ipc, uint16_t *received_data_size, const char **received_data) + ); + MOCK_METHOD1(popData, int(SharedMemoryIPC *ipc)); + MOCK_METHOD1(isDataAvailable, int(SharedMemoryIPC *ipc)); + MOCK_METHOD2(isCorruptedShmem, int(SharedMemoryIPC *ipc, int)); +}; + +CMOCK_MOCK_FUNCTION7( + NanoShmemIPCMocker, + initIpc, + SharedMemoryIPC *( + const char queue_name[32], + uint32_t user_id, + uint32_t group_id, + int is_owner, + uint16_t num_of_queue_elem, + const LoggingData *logging_data, + void (*debug_func)( + const LoggingData *loggin_data, + uint32_t worker_id, + int is_error, + const char *func, + const char *file, + int line_num, + const char *fmt, + ... + ) + ) +); +CMOCK_MOCK_FUNCTION2(NanoShmemIPCMocker, destroyIpc, void(SharedMemoryIPC *ipc, int is_owner)); +CMOCK_MOCK_FUNCTION3( + NanoShmemIPCMocker, + sendData, + int(SharedMemoryIPC *ipc, const uint16_t data_to_send_size, const char *data_to_send) +) +CMOCK_MOCK_FUNCTION4( + NanoShmemIPCMocker, + sendChunkedData, + int( + SharedMemoryIPC *ipc, + const uint16_t *data_to_send_sizes, + const char **data_elem_to_send, + const uint8_t num_of_data_elem + ) +); +CMOCK_MOCK_FUNCTION3( + NanoShmemIPCMocker, + receiveData, + int(SharedMemoryIPC *ipc, uint16_t *received_data_size, const char **received_data) +); +CMOCK_MOCK_FUNCTION1(NanoShmemIPCMocker, popData, int(SharedMemoryIPC *ipc)); +CMOCK_MOCK_FUNCTION1(NanoShmemIPCMocker, isDataAvailable, int(SharedMemoryIPC *ipc)); +CMOCK_MOCK_FUNCTION2(NanoShmemIPCMocker, resetIpc, void(SharedMemoryIPC *ipc, uint16_t num_of_data_segments)); +CMOCK_MOCK_FUNCTION2(NanoShmemIPCMocker, isCorruptedShmem, int(SharedMemoryIPC *ipc, int)); + +#endif // __MOCK_SHMEM_IPC__ diff --git a/attachments/nano_attachment/nano_attachment.c b/attachments/nano_attachment/nano_attachment.c new file mode 100644 index 0000000..7c89ad7 --- /dev/null +++ b/attachments/nano_attachment/nano_attachment.c @@ -0,0 +1,616 @@ +#include "nano_attachment.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "nano_attachment_sender.h" +#include "nano_attachment_metric.h" +#include "nano_initializer.h" +#include "nano_configuration.h" +#include "nano_utils.h" +#include "attachment_types.h" +#include "nano_blockpage.h" +#include "compression_utils.h" +#include "nano_compression.h" + +NanoAttachment * +InitNanoAttachment(uint8_t attachment_type, int worker_id, int num_of_workers, int logging_fd) +{ + NanoAttachment *attachment = malloc(sizeof(NanoAttachment)); + if (attachment == NULL) { + return NULL; + } + + memset(attachment, 0, sizeof(NanoAttachment)); + + attachment->shared_verdict_signal_path[0] = '\0'; + attachment->worker_id = worker_id; + attachment->num_of_workers = num_of_workers; + attachment->nano_user_id = getuid(); + attachment->nano_group_id = getgid(); + attachment->registration_socket = -1; + attachment->registration_state = NOT_REGISTERED; + attachment->attachment_type = attachment_type; + attachment->nano_service_ipc = NULL; + attachment->comm_socket = -1; + attachment->logging_data = NULL; + + if (set_docker_id(attachment) == NANO_ERROR) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Could not evaluate container id"); + close_logging_fd(attachment); + free(attachment); + return NULL; + } + + if (set_logging_fd(attachment, logging_fd) == NANO_ERROR) { + free(attachment); + return NULL; + } + + attachment->logging_data = initLoggingData( + attachment->logging_fd, + DBG_LEVEL_INFO, + attachment->worker_id + ); + if (attachment->logging_data == NULL) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Failed to initialize logging data"); + return NULL; + } + + if (set_unique_id(attachment) == NANO_ERROR) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Could not evaluate unique name"); + close_logging_fd(attachment); + free(attachment); + return NULL; + } + + attachment->is_configuration_updated = NANO_ERROR; + attachment->current_config_version = 0; + + attachment->fail_mode_verdict = NANO_OK; + attachment->fail_mode_delayed_verdict = NANO_OK; + attachment->dbg_level = DBG_LEVEL_INFO; + attachment->num_of_connection_attempts = 0; + attachment->fail_open_timeout = 50; + attachment->fail_open_delayed_timeout = 150; + attachment->sessions_per_minute_limit_verdict = ATTACHMENT_VERDICT_ACCEPT; + attachment->max_sessions_per_minute = 0; + attachment->req_max_proccessing_ms_time = 3000; + attachment->res_max_proccessing_ms_time = 3000; + attachment->registration_thread_timeout_msec = 100; + attachment->req_start_thread_timeout_msec = 100; + attachment->req_header_thread_timeout_msec = 100; + attachment->req_body_thread_timeout_msec = 150; + attachment->res_header_thread_timeout_msec = 100; + attachment->res_body_thread_timeout_msec = 150; + attachment->waiting_for_verdict_thread_timeout_msec = 150; + attachment->metric_timeout_timeout = 100; + attachment->inspection_mode = NON_BLOCKING_THREAD; + attachment->num_of_nano_ipc_elements = 200; + attachment->keep_alive_interval_msec = DEFAULT_KEEP_ALIVE_INTERVAL_MSEC; + + if (nano_attachment_init_process(attachment) != NANO_OK) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Could not initialize nano attachment"); + close_logging_fd(attachment); + free(attachment); + return NULL; + } + + reset_metric_data(attachment); + return attachment; +}; + +void +FiniNanoAttachment(NanoAttachment *attachment) +{ + close_logging_fd(attachment); + free(attachment); +}; + +NanoCommunicationResult +RestartAttachmentConfiguration(NanoAttachment *attachment) +{ + return reset_attachment_config(attachment); +}; + +HttpSessionData * +InitSessionData(NanoAttachment *attachment, SessionID session_id) +{ + HttpSessionData *session_data = malloc(sizeof(HttpSessionData)); + if (session_data == NULL) { + return NULL; + } + + write_dbg( + attachment, + session_id, + DBG_LEVEL_TRACE, + "Initiating session data" + ); + + session_data->was_request_fully_inspected = 0; + session_data->verdict = TRAFFIC_VERDICT_INSPECT; + session_data->session_id = session_id; + session_data->remaining_messages_to_reply = 0; + session_data->req_proccesing_time = 0; + session_data->res_proccesing_time = 0; + session_data->processed_req_body_size = 0; + session_data->processed_res_body_size = 0; + + session_data->response_data.compression_type = NO_COMPRESSION; + session_data->response_data.compression_stream = NULL; + session_data->response_data.decompression_stream = NULL; + + return session_data; +}; + +void +FiniSessionData(NanoAttachment *attachment, HttpSessionData *session_data) +{ + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_DEBUG, + "Freeing session data for session_id" + ); + + if (session_data->response_data.compression_stream != NULL) { + finiCompressionStream(session_data->response_data.compression_stream); + session_data->response_data.compression_stream = NULL; + } + + if (session_data->response_data.decompression_stream != NULL) { + finiCompressionStream(session_data->response_data.decompression_stream); + session_data->response_data.decompression_stream = NULL; + } + free(session_data); +}; + +void +UpdateMetric(NanoAttachment *attachment, AttachmentMetricType metric_type, uint64_t value) +{ + updateMetricField(attachment, metric_type, value); +} + +void +SendAccumulatedMetricData(NanoAttachment *attachment) +{ + SendMetricData(attachment); +} + +AttachmentVerdictResponse +SendDataNanoAttachment(NanoAttachment *attachment, AttachmentData *data) +{ + switch (data->chunk_type) { + case HTTP_REQUEST_FILTER: { + return SendRequestFilter(attachment, data); + } + case HTTP_REQUEST_METADATA: { + return SendMetadata(attachment, data); + } + case HTTP_REQUEST_HEADER: { + return SendRequestHeaders(attachment, data); + } + case HTTP_REQUEST_BODY: { + return SendRequestBody(attachment, data); + } + case HTTP_REQUEST_END: { + return SendRequestEnd(attachment, data); + } + case HTTP_RESPONSE_HEADER: { + return SendResponseHeaders(attachment, data); + } + case HTTP_RESPONSE_BODY: { + return SendResponseBody(attachment, data); + } + case HTTP_RESPONSE_END: { + return SendResponseEnd(attachment, data); + } + default: + break; + } + + AttachmentVerdictResponse response = { + .verdict = ATTACHMENT_VERDICT_INSPECT, + .session_id = data->session_id, + .modifications = NULL + }; + return response; +} + +/// +/// @brief Connects to the keep-alive socket. +/// +/// @param attachment A pointer to a NanoAttachment struct containing attachment information. +/// +/// @return An int representing an opened socket, if failed returns -1. +/// +static int +connect_to_keep_alive_socket(NanoAttachment *attachment) +{ + struct sockaddr_un server; + int keep_alive_socket; + + // Connect a new socket. + keep_alive_socket = socket(AF_UNIX, SOCK_STREAM, 0); + if (keep_alive_socket < 0) { + write_dbg( + attachment, + attachment->worker_id, + DBG_LEVEL_WARNING, + "Could not create socket, Error: %s", + strerror(errno) + ); + return keep_alive_socket; + } + + server.sun_family = AF_UNIX; + strncpy(server.sun_path, SHARED_KEEP_ALIVE_PATH, sizeof(server.sun_path) - 1); + + if (connect(keep_alive_socket, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) != -1 ) { + return keep_alive_socket; + } + + write_dbg( + attachment, + attachment->worker_id, + DBG_LEVEL_DEBUG, + "Could not connect to nano service. Path: %s, Error: %s, Errno: %d", + server.sun_path, + strerror(errno), + errno + ); + close(keep_alive_socket); + + return -1; +} + +/// +/// @brief Sends the keep-alive signal to the alive socket. +/// +/// @param attachment A pointer to a NanoAttachment struct containing attachment information. +/// @param keep_alive_socket Socket descriptor for the keep-alive socket. +/// +/// @return A NanoCommunicationResult indicating the result of the communication operation. +/// +static NanoCommunicationResult +send_keep_alive_to_alive_socket(NanoAttachment *attachment, int keep_alive_socket) +{ + uint8_t container_id_size = strlen(attachment->container_id); + struct timeval timeout = get_absolute_timeout_val_sec(1); + NanoCommunicationResult res; + + // Exchanging worker id with the nano service. + res = write_to_service( + attachment, + &keep_alive_socket, + &attachment->worker_id, + sizeof(attachment->worker_id), + &timeout + ); + if (res != NANO_OK) { + // Failed to send worker id + write_dbg(attachment, attachment->worker_id, DBG_LEVEL_WARNING, "Failed to send worker id"); + return NANO_ERROR; + } + + // Exchanging container id size with the nano service. + res = write_to_service( + attachment, + &keep_alive_socket, + &container_id_size, + sizeof(container_id_size), + &timeout + ); + if (res != NANO_OK) { + // Failed to send container id size. + write_dbg(attachment, attachment->worker_id, DBG_LEVEL_WARNING, "Failed to send container id size"); + return NANO_ERROR; + } + + if (container_id_size > 0) { + // Exchanging container id with the nano service. + res = write_to_service( + attachment, + &keep_alive_socket, + attachment->container_id, + container_id_size, + &timeout + ); + if (res != NANO_OK) { + // Failed to send container id name. + write_dbg(attachment, attachment->worker_id, DBG_LEVEL_WARNING, "Failed to send container id"); + return NANO_ERROR; + } + } + + return NANO_OK; +} + +void +SendKeepAlive(NanoAttachment *attachment) +{ + int keep_alive_socket; + NanoCommunicationResult res; + + write_dbg( + attachment, + attachment->worker_id, + DBG_LEVEL_DEBUG, + "Keep alive signal. Family id: %s, UID: %u", + attachment->container_id, + attachment->worker_id + ); + + keep_alive_socket = connect_to_keep_alive_socket(attachment); + if (keep_alive_socket < 0) { + write_dbg(attachment, attachment->worker_id, DBG_LEVEL_WARNING, "Failed to connect to keep alive socket"); + return; + } + + write_dbg( + attachment, + attachment->worker_id, + DBG_LEVEL_DEBUG, + "connected to socket: %d. sending keep alive signals" + ); + + res = send_keep_alive_to_alive_socket(attachment, keep_alive_socket); + if (res == NANO_ERROR) { + write_dbg(attachment, attachment->worker_id, DBG_LEVEL_WARNING, "Failed to send keep alive data"); + } + + close(keep_alive_socket); +} + +int +IsSessionFinalized(NanoAttachment *attachment, HttpSessionData *session_data) +{ + if (session_data->verdict == TRAFFIC_VERDICT_INSPECT) { + write_dbg( + attachment, + attachment->worker_id, + DBG_LEVEL_TRACE, + "Inspecting data for session id: %d", + session_data->session_id + ); + return 0; + } + + write_dbg( + attachment, + attachment->worker_id, + DBG_LEVEL_TRACE, + "Skipping already inspected for session id: %d", + session_data->session_id + ); + + return 1; +} + +NanoWebResponseType +GetWebResponseType( + NanoAttachment *attachment, + HttpSessionData *session_data, + AttachmentVerdictResponse *response +) +{ + if (response->web_response_data == NULL) { + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_WARNING, + "Trying to get web response with no response object" + ); + return NO_WEB_RESPONSE; + } + + return response->web_response_data->web_response_type; +} + +int +IsResponseWithModification( + NanoAttachment *attachment, + HttpSessionData *session_data, + AttachmentVerdictResponse *response +) +{ + int res = response->modifications != NULL; + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_TRACE, + "Response %s have modifications", + res ? "does" : "does not" + ); + + return res; +} + +NanoResponseModifications +GetResponseModifications( + NanoAttachment *attachment, + HttpSessionData *session_data, + AttachmentVerdictResponse *response +) +{ + if (response == NULL) { + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_WARNING, + "Trying to get modifications with no response object" + ); + return (NanoResponseModifications) { + .modifications = NULL + }; + } + + return (NanoResponseModifications) { + .modifications = response->modifications + }; +} + +BlockPageData +GetBlockPage(NanoAttachment *attachment, HttpSessionData *session_data, AttachmentVerdictResponse *response) +{ + WebResponseData *web_response_data = response->web_response_data; + CustomResponseData *custom_response_data; + + if (web_response_data->web_response_type != CUSTOM_WEB_RESPONSE) { + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_WARNING, + "Trying to generate custom block page with a non custom response object" + ); + + return (BlockPageData) { + .response_code = 0, + .title_prefix = { .len = 0, .data = NULL }, + .title = { .len = 0, .data = NULL }, + .body_prefix = { .len = 0, .data = NULL }, + .body = { .len = 0, .data = NULL }, + .uuid_prefix = { .len = 0, .data = NULL }, + .uuid = { .len = 0, .data = NULL }, + .uuid_suffix = { .len = 0, .data = NULL } + }; + } + + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_TRACE, + "Getting custom block page" + ); + + custom_response_data = (CustomResponseData *) web_response_data->data; + return (BlockPageData) { + .response_code = custom_response_data->response_code, + .title_prefix = { .len = strlen(title_prefix), .data = (unsigned char *)title_prefix }, + .title = { .len = strlen((char *)custom_response_data->title), .data = custom_response_data->title }, + .body_prefix = { .len = strlen(body_prefix), .data = (unsigned char *)body_prefix }, + .body = { .len = strlen((char *)custom_response_data->body), .data = custom_response_data->body }, + .uuid_prefix = { .len = strlen(uuid_prefix), .data = (unsigned char *)uuid_prefix }, + .uuid = { .len = strlen((char *)web_response_data->uuid), .data = web_response_data->uuid }, + .uuid_suffix = { .len = strlen(uuid_suffix), .data = (unsigned char *)uuid_suffix } + }; +} + + +RedirectPageData +GetRedirectPage(NanoAttachment *attachment, HttpSessionData *session_data, AttachmentVerdictResponse *response) +{ + WebResponseData *web_response_data = response->web_response_data; + RedirectData *redirect_data; + + if (web_response_data->web_response_type != REDIRECT_WEB_RESPONSE) { + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_WARNING, + "Trying to generate custom block page with a non redirect response object" + ); + + return (RedirectPageData) { + .redirect_location = { .len = 0, .data = NULL } + }; + } + + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_TRACE, + "Getting redirect data" + ); + + redirect_data = (RedirectData *) web_response_data->data; + return (RedirectPageData) { + .redirect_location = { + .len = strlen((char*)redirect_data->redirect_location), + .data = redirect_data->redirect_location + } + }; +} + +void +FreeAttachmentResponseContent( + NanoAttachment *attachment, + HttpSessionData *session_data, + AttachmentVerdictResponse *response +) +{ + NanoHttpModificationList *current_modification; + NanoHttpModificationList *modification_list; + + if (response == NULL) { + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_WARNING, + "Attempting to free NULL response" + ); + return; + } + + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_TRACE, + "Freeing AttachmentResponse object" + ); + if (response->web_response_data != NULL) { + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_TRACE, + "Freeing custom web response data" + ); + + free(response->web_response_data->data); + free(response->web_response_data); + response->web_response_data = NULL; + } + + if (response->modifications != NULL) { + modification_list = response->modifications; + while (modification_list) { + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_TRACE, + "Freeing modifications list" + ); + current_modification = modification_list; + modification_list = modification_list->next; + free(current_modification); + } + response->modifications = NULL; + } + + return; +} + +HttpBody * +compressBody(NanoAttachment *attachment, HttpSessionData *session_data, HttpBody *bodies) +{ + return nano_compress_body(attachment, bodies, session_data); +} + + +HttpBody * +decompressBody(NanoAttachment *attachment, HttpSessionData *session_data, HttpBody *bodies) +{ + return nano_decompress_body(attachment, bodies, session_data); +} + +void +freeCompressedBody(NanoAttachment *attachment, HttpSessionData *session_data, HttpBody *bodies) +{ + nano_free_compressed_body(attachment, bodies, session_data); +} diff --git a/attachments/nano_attachment/nano_attachment_io.c b/attachments/nano_attachment/nano_attachment_io.c new file mode 100644 index 0000000..62d19e2 --- /dev/null +++ b/attachments/nano_attachment/nano_attachment_io.c @@ -0,0 +1,1776 @@ +#include "nano_attachment_io.h" + +#include +#include +#include +#include +#include + +#include "nano_attachment_common.h" +#include "nano_utils.h" +#include "nano_attachment_metric.h" +#include "nano_attachment_util.h" +#include "nano_configuration.h" + +#define MAX_HEADER_BULK_SIZE 10 +#define RESPONSE_CODE_COUNT 3 +#define CONTENT_LENGTH_COUNT 3 +#define HEADER_DATA_COUNT 4 +#define BODY_DATA_COUNT 5 +#define END_TRANSACTION_DATA_COUNT 2 +#define DELAYED_VERDICT_DATA_COUNT 2 + +/// @brief Sends a signal to the nano service to notify about new session data to inspect. +/// +/// This function sends a signal to the nano service to notify it about new session data +/// that needs to be inspected. +/// +/// @param attachment A pointer to a NanoAttachment structure representing the attachment to the nano service. +/// @param cur_session_id An unsigned 32-bit integer representing the current session ID. +/// +/// @return NANO_OK if the signal is sent successfully +/// NANO_ERROR if there is an error +/// NANO_TIMEOUT if a timeout occurs. +/// +static NanoCommunicationResult +notify_signal_to_service(NanoAttachment *attachment, uint32_t cur_session_id) +{ + int res = 0; + unsigned int bytes_written = 0; + struct timeval absolute_timeout = get_absolute_timeout_val_sec(1); + int failopen_enabled = (attachment->inspection_mode == NON_BLOCKING_THREAD); + struct pollfd s_poll; + + write_dbg( + attachment, + cur_session_id, + DBG_LEVEL_TRACE, + "Sending signal to the service to notify about new session data to inspect" + ); + + s_poll.fd = attachment->comm_socket; + s_poll.events = POLLOUT; + s_poll.revents = 0; + res = poll(&s_poll, 1, 0); + if (res > 0 && s_poll.revents & POLLHUP) { + write_dbg( + attachment, + cur_session_id, + DBG_LEVEL_DEBUG, + "Polling communication socket failed" + ); + return NANO_ERROR; + } + + while (bytes_written < sizeof(cur_session_id)) { + if (failopen_enabled && is_absolute_timeout_reached(&absolute_timeout)) { + write_dbg( + attachment, + cur_session_id, + DBG_LEVEL_WARNING, + "Reached timeout during attempt to signal nano service" + ); + return NANO_TIMEOUT; + } + + res = write( + attachment->comm_socket, + ((char *)&cur_session_id) + bytes_written, + sizeof(cur_session_id) - bytes_written + ); + + if (res < 0) { + write_dbg( + attachment, + cur_session_id, + DBG_LEVEL_WARNING, + "Failed to signal nano service, trying to restart communications" + ); + return NANO_ERROR; + } + + bytes_written += res; + } + + return NANO_OK; +} + +/// +/// @brief Signals the service fora possible session data waiting for inspection on +/// on the shared mememory (IPC). +/// +/// @param attachment A pointer to a NanoAttachment structure representing the attachment to the nano service. +/// @param cur_session_id An unsigned 32-bit integer representing the current session ID. +/// @param chunk_type An enumeration representing the type of HTTP chunk being sent. +/// +/// @return NANO_OK if the ack response is received and matches the current session ID, +/// NANO_ERROR if there is an error in signaling or reading the response, +/// NANO_AGAIN if the response indicates an old session ID and polling should be retried, +/// NANO_TIMEOUT if a timeout occurs while waiting for the response. +/// +static NanoCommunicationResult +signal_for_session_data(NanoAttachment *attachment, uint32_t cur_session_id, AttachmentDataType chunk_type) +{ + struct pollfd s_poll; + NanoCommunicationResult res = NANO_OK; + uint32_t reply_from_service; + int timeout = attachment->fail_open_timeout; + int retry; + + if (chunk_type == REQUEST_DELAYED_VERDICT) { + timeout = attachment->fail_open_delayed_timeout; + } + if (attachment->inspection_mode != NON_BLOCKING_THREAD) { + timeout = -1; + } + + res = notify_signal_to_service(attachment, cur_session_id); + if (res != NANO_OK) return res; + + write_dbg( + attachment, + cur_session_id, + DBG_LEVEL_TRACE, + "Successfully signaled to the service! pending to receive ack" + ); + + for (retry = 0; retry < 3; retry++) { + s_poll.fd = attachment->comm_socket; + s_poll.events = POLLIN; + s_poll.revents = 0; + res = poll(&s_poll, 1, timeout); + if (res < 0) { + // Polling from the nano service has failed. + write_dbg( + attachment, + cur_session_id, + DBG_LEVEL_TRACE, + "Polling from nano service had fail, failure: %d", + res); + return NANO_ERROR; + } + + if (res == 0) { + write_dbg(attachment, cur_session_id, DBG_LEVEL_TRACE, "Polling from nano service reached timeout"); + continue; + } + + res = read(attachment->comm_socket, ((char *)&reply_from_service), sizeof(reply_from_service)); + if (res <= 0) { + write_dbg( + attachment, + cur_session_id, + DBG_LEVEL_WARNING, + "Failed to read ack from nano service" + ); + return NANO_ERROR; + } + + if (reply_from_service == cur_session_id) { + // Read was successful and matches the current session Id. + write_dbg( + attachment, + cur_session_id, + DBG_LEVEL_TRACE, + "Received signal from nano service to the current session. Current session id: %d", + cur_session_id + ); + return NANO_OK; + } else if (reply_from_service == CORRUPTED_SESSION_ID) { + // Recieved corrupted session ID, returning error. + write_dbg( + attachment, + cur_session_id, + DBG_LEVEL_WARNING, + "Received signal from nano service regarding a corrupted session. Current session id: %d", + cur_session_id + ); + return NANO_ERROR; + } else { + // Recieved old session Id, attempting to poll again. + write_dbg( + attachment, + cur_session_id, + DBG_LEVEL_DEBUG, + "Received signal from nano service regarding a previous session." + " Current session id: %d, Signaled session id: %d", + cur_session_id, + reply_from_service + ); + return NANO_AGAIN; + } + } + write_dbg(attachment, cur_session_id, DBG_LEVEL_WARNING, "Reached timeout during attempt to signal nano service"); + return NANO_TIMEOUT; +} + +/// @brief Receives verdict data from the nano service. +/// +/// This function attempts to receive verdict data from the nano service IPC channel. +/// +/// @param attachment A pointer to a NanoAttachment structure representing the attachment to the nano service. +/// @param session_id An unsigned 32-bit integer representing the current session ID. +/// +/// @return A pointer to a HttpReplyFromService structure representing the received verdict data, +/// or NULL if the data could not be received after multiple attempts. +/// +static HttpReplyFromService * +receive_data_from_service(NanoAttachment *attachment, uint32_t session_id) +{ + int res, retry; + const char *reply_data; + uint16_t reply_size; + + write_dbg(attachment, session_id, DBG_LEVEL_TRACE, "Receiving verdict data from nano service"); + + for (retry = 0; retry < 5; retry++) { + if (!isDataAvailable(attachment->nano_service_ipc)) { + write_dbg( + attachment, + session_id, + DBG_LEVEL_TRACE, + "Service data is not available - trying again (retry = %d) in 1 u-seconds", + retry + ); + usleep(1); + continue; + } + res = receiveData(attachment->nano_service_ipc, &reply_size, &reply_data); + if (res < 0 || reply_data == NULL) { + write_dbg( + attachment, + session_id, + DBG_LEVEL_TRACE, + "Failed to receive verdict data - trying again (retry = %d) in 1 u-seconds", + retry + ); + + usleep(1); + continue; + } + return (HttpReplyFromService *)reply_data; + } + return NULL; +} + +NanoCommunicationResult +send_session_data_to_service( + NanoAttachment *attachment, + char **fragments, + const uint16_t *fragments_sizes, + uint8_t num_of_data_elem, + uint32_t cur_session_id, + AttachmentDataType chunk_type +) +{ + int attempt_num; + NanoCommunicationResult res = NANO_OK; + int err_code = 0; + write_dbg( + attachment, + cur_session_id, + DBG_LEVEL_TRACE, + "Sending session data chunk for inspection" + ); + + for (attempt_num = 1; attempt_num <= 5; attempt_num++) { + err_code = sendChunkedData( + attachment->nano_service_ipc, + fragments_sizes, + (const char **)fragments, + num_of_data_elem + ); + + write_dbg( + attachment, + cur_session_id, + DBG_LEVEL_DEBUG, + "Res code is %d, err code is %d", + res, + err_code + ); + + if (res == NANO_OK && err_code == 0) { + return NANO_OK; + } + + if (err_code != 0) { + write_dbg( + attachment, + cur_session_id, + DBG_LEVEL_DEBUG, + "Failed to send data for inspection - attempt number %d", + attempt_num + ); + } + + // Notify the nano service to inspect new session data. + // This notification is triggered when chunked data transmission fails. + res = signal_for_session_data(attachment, cur_session_id, chunk_type); + + if (res == NANO_ERROR) { + disconnect_communication(attachment); + res = restart_communication(attachment); + if (res == NANO_ERROR) return res; + } + } + + switch(err_code) + { + case -1: + write_dbg( + attachment, + cur_session_id, + DBG_LEVEL_WARNING, + "Failed to send data for inspection - Corrupted shared memory" + ); + break; + case -2: + write_dbg( + attachment, + cur_session_id, + DBG_LEVEL_WARNING, + "Failed to send data for inspection - Requested write size exceeds the write limit" + ); + break; + case -3: + write_dbg( + attachment, + cur_session_id, + DBG_LEVEL_WARNING, + "Failed to send data for inspection - Cannot write to a full queue" + ); + break; + case -4: + write_dbg( + attachment, + cur_session_id, + DBG_LEVEL_WARNING, + "Failed to send data for inspection - Attempted write to a location outside the queue" + ); + break; + default: + write_dbg( + attachment, + cur_session_id, + DBG_LEVEL_WARNING, + "Failed to send data for inspection - Unknown error code %d", + err_code + ); + break; + } + return NANO_ERROR; +} + +/// +/// @brief Allocates memory for a modification buffer and copies the given data into it. +/// +/// This function allocates memory for a modification buffer of size (data_size + 1) +/// bytes, copies the provided data into the buffer, and assigns the pointer to +/// the allocated memory to the 'target' parameter. +/// +/// @param attachment A pointer to a NanoAttachment structure representing the attachment. +/// @param session_id The ID of the session. +/// @param target A pointer to a pointer that will hold the address of the allocated memory. +/// Upon successful allocation, this pointer will be updated to point to the +/// allocated memory. +/// @param data_size The size of the data to be copied into the modification buffer. +/// @param data A pointer to the data to be copied into the modification buffer. +/// +/// @return NANO_OK if the memory allocation and copying are successful, NANO_ERROR otherwise. +/// +static NanoCommunicationResult +create_modification_buffer( + NanoAttachment *attachment, + SessionID session_id, + char **target, + uint16_t data_size, + char *data +) +{ + *target = malloc(data_size + 1); + if (*target == NULL) { + write_dbg( + attachment, + session_id, + DBG_LEVEL_WARNING, + "Failed to allocate modification buffer of size: %d", + data_size + ); + return NANO_ERROR; + } + + snprintf(*target, data_size + 1, "%s", data); + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "Successfully created modification buffer, size: %d", + data_size + ); + + return NANO_OK; +} + +/// +/// @brief Creates a new Modification List node. +/// +/// This function allocates memory for a new NanoHttpModificationList node, +/// initializes its fields with the provided modification data, and returns +/// a pointer to the created node. +/// +/// @param attachment A pointer to a NanoAttachment structure representing the attachment. +/// @param session_id The ID of the session. +/// @param modification A pointer to a HttpInjectData structure containing the modification data. +/// +/// @return A pointer to the newly created NanoHttpModificationList node if successful, +/// or NULL if memory allocation fails or if an error occurs during the creation process. +/// +static NanoHttpModificationList * +create_modification_node(NanoAttachment *attachment, SessionID session_id, HttpInjectData *modification) +{ + NanoCommunicationResult res; + NanoHttpModificationList *modification_node = malloc(sizeof(NanoHttpModificationList)); + if (modification_node == NULL) { + write_dbg( + attachment, + session_id, + DBG_LEVEL_WARNING, + "Failed to allocate modification node of size: %d", + sizeof(NanoHttpModificationList) + ); + return NULL; + } + + res = create_modification_buffer( + attachment, + session_id, + &modification_node->modification_buffer, + modification->injection_size, + modification->data + ); + + if (res != NANO_OK) { + free(modification_node); + return NULL; + } + + modification_node->next = NULL; + modification_node->modification.is_header = modification->is_header; + modification_node->modification.mod_type = modification->mod_type; + modification_node->modification.injection_pos = modification->injection_pos; + modification_node->modification.injection_size = modification->injection_size; + modification_node->modification.orig_buff_index = modification->orig_buff_index; + + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "Successfully created modification node. " + "Is header: %d, " + "Injection position: %d, " + "Injection size: %d, " + "Original buffer index: %d, " + "Modification data: %s, " + "Should change data: %d", + modification_node->modification.is_header, + modification_node->modification.injection_pos, + modification_node->modification.injection_size, + modification_node->modification.orig_buff_index, + modification_node->modification_buffer, + modification_node->modification.mod_type + ); + + return modification_node; +} + +/// +/// @brief Handles the response of an injection request by creating modification nodes +/// and updating the modification list. +/// +/// This function iterates over the injection data and creates modification nodes for each injection, +/// updating the modification list accordingly. If an error occurs during node creation, it logs a warning +/// and frees the memory associated with previously created modification nodes. +/// +/// @param attachment A pointer to a NanoAttachment structure representing the attachment. +/// @param session_id The ID of the session. +/// @param modification_list A pointer to a pointer to the head of the modification list. +/// @param inject_data A pointer to an array of HttpInjectData structures containing injection data. +/// @param modification_count The number of modifications in the injection data array. +/// +static void +handle_inject_response( + NanoAttachment *attachment, + SessionID session_id, + NanoHttpModificationList **modification_list, + HttpInjectData *inject_data, + uint8_t modification_count +) +{ + NanoHttpModificationList *new_modification = NULL; + NanoHttpModificationList *current_modification = NULL; + unsigned int modification_index; + + for (modification_index = 0; modification_index < modification_count; modification_index++) { + // Go over the modifications and create nodes. + new_modification = create_modification_node(attachment, session_id, inject_data); + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "create modification node %d out of %d", + modification_index, + modification_count + ); + + if (new_modification == NULL) { + write_dbg(attachment, session_id, DBG_LEVEL_WARNING, "Failed to create modification node"); + while (*modification_list) { + current_modification = *modification_list; + *modification_list = (*modification_list)->next; + free(current_modification->modification_buffer); + free(current_modification); + } + return; + } + if (*modification_list == NULL) { + *modification_list = new_modification; + current_modification = *modification_list; + } else { + while (*modification_list) { + current_modification = *modification_list; + *modification_list = (*modification_list)->next; + } + current_modification->next = new_modification; + current_modification = current_modification->next; + } + // Moving the pointer to the next injection. + inject_data = (HttpInjectData *)((char *)inject_data + sizeof(HttpInjectData) + inject_data->injection_size); + } +} + +/// +/// @brief Create a custom web response by the provided data +/// +/// @param[in] attachment Nano attachment. +/// @param[in] session_id Session ID. +/// @param[in] web_response_data Web response data. +/// @param[in, out] ctx_response_data Web response data to be set. +/// +static void +handle_custom_web_response( + NanoAttachment *attachment, + SessionID session_id, + WebResponseData **ctx_response_data, + HttpWebResponseData *web_response_data +) +{ + nano_str_t title = {0, NULL}; + nano_str_t body = {0, NULL}; + nano_str_t uuid; + size_t incident_prefix_size = strlen("Incident Id: "); + WebResponseData *new_response_data = NULL; + CustomResponseData *custom_response_data = NULL; + + uuid.len = web_response_data->uuid_size; + if (uuid.len >= UUID_SIZE) { + write_dbg(attachment, session_id, DBG_LEVEL_WARNING, "Custom response UUID is too long"); + return; + } + + title.len = web_response_data->response_data.custom_response_data.title_size; + if (title.len >= CUSTOM_RESPONSE_TITLE_SIZE) { + write_dbg(attachment, session_id, DBG_LEVEL_WARNING, "Custom response title is too long"); + return; + } + + body.len = web_response_data->response_data.custom_response_data.body_size; + if (body.len >= CUSTOM_RESPONSE_BODY_SIZE) { + write_dbg(attachment, session_id, DBG_LEVEL_WARNING, "Custom response body is too long"); + return; + } + + new_response_data = malloc(sizeof(WebResponseData)); + if (new_response_data == NULL) { + write_dbg(attachment, session_id, DBG_LEVEL_WARNING, "Failed to allocate memory for web response data"); + return; + } + + write_dbg(attachment, session_id, DBG_LEVEL_TRACE, "Preparing to set custom web response page"); + custom_response_data = malloc(sizeof(CustomResponseData)); + if (custom_response_data == NULL) { + write_dbg(attachment, session_id, DBG_LEVEL_WARNING, "Failed to allocate memory for custom response data"); + free(new_response_data); + return; + } + + // Setting custom web response title's data. + if (title.len > 0) { + title.data = (u_char *)web_response_data->response_data.custom_response_data.data; + } + + // Setting custom web response body's data. + if (body.len > 0) { + body.data = (u_char *)web_response_data->response_data.custom_response_data.data + title.len; + } + + uuid.data = (u_char *)web_response_data->response_data.custom_response_data.data + title.len + body.len; + + custom_response_data->response_code = web_response_data->response_data.custom_response_data.response_code; + + if (title.data != NULL) { + memcpy(custom_response_data->title, title.data, title.len); + } + custom_response_data->title[title.len] = '\0'; + + if (body.data != NULL) { + memcpy(custom_response_data->body, body.data, body.len); + } + custom_response_data->body[body.len] = '\0'; + + new_response_data->web_response_type = CUSTOM_WEB_RESPONSE; + memcpy(new_response_data->uuid, "Incident Id: ", incident_prefix_size); + memcpy(new_response_data->uuid + incident_prefix_size, uuid.data, uuid.len); + new_response_data->uuid[incident_prefix_size + uuid.len] = '\0'; + + new_response_data->data = custom_response_data; + *ctx_response_data = new_response_data; +} + +/// +/// @brief Create a redirect response by the provided data +/// +/// @param[in] attachment Nano attachment. +/// @param[in] session_id Session ID. +/// @param[in] web_response_data Web response data. +/// @param[in, out] ctx_response_data Web response data to be set. +/// +static void +handle_redirect_response( + NanoAttachment *attachment, + SessionID session_id, + WebResponseData **ctx_response_data, + HttpWebResponseData *web_response_data +) +{ + nano_str_t uuid; + nano_str_t redirect_location; + WebResponseData *new_response_data = NULL; + RedirectData *redirect_data = NULL; + + uuid.len = web_response_data->uuid_size; + if (uuid.len >= UUID_SIZE) { + write_dbg(attachment, session_id, DBG_LEVEL_WARNING, "Custom response UUID is too long"); + return; + } + + redirect_location.len = web_response_data->response_data.redirect_data.redirect_location_size; + if (redirect_location.len >= REDIRECT_RESPONSE_LOCATION_SIZE) { + write_dbg(attachment, session_id, DBG_LEVEL_WARNING, "Custom response redirect location is too long"); + return; + } + + new_response_data = malloc(sizeof(WebResponseData)); + if (new_response_data == NULL) { + write_dbg(attachment, session_id, DBG_LEVEL_WARNING, "Failed to allocate memory for web response data"); + return; + } + + write_dbg(attachment, session_id, DBG_LEVEL_TRACE, "Preparing to set redirect web response"); + + redirect_data = malloc(sizeof(RedirectData)); + if (redirect_data == NULL) { + write_dbg(attachment, session_id, DBG_LEVEL_WARNING, "Failed to allocate memory for custom response data"); + free(new_response_data); + return; + } + + redirect_location.data = (u_char *)web_response_data->response_data.redirect_data.redirect_location; + + memcpy(redirect_data->redirect_location, redirect_location.data, redirect_location.len); + redirect_data->redirect_location[redirect_location.len] = '\0'; + + uuid.data = (u_char *)web_response_data->response_data.redirect_data.redirect_location + redirect_location.len; + + new_response_data->web_response_type = REDIRECT_WEB_RESPONSE; + memcpy(new_response_data->uuid, uuid.data, uuid.len); + new_response_data->uuid[uuid.len] = '\0'; + new_response_data->data = redirect_data; + *ctx_response_data = new_response_data; +} + +/// +/// @brief Handles drop response received from a service. +/// +/// This function is responsible for processing different types of web responses +/// and taking appropriate actions based on the response type. +/// +/// @param[in] attachment Pointer to the NanoAttachment structure associated with the request. +/// @param[in] session_id The session ID of the request. +/// @param[in, out] ctx_response_data Pointer to the pointer of WebResponseData holding context response data. +/// @param[in, out] web_response_data Pointer to the HttpWebResponseData containing the web response data. +/// +static void +handle_drop_response( + NanoAttachment *attachment, + SessionID session_id, + WebResponseData **ctx_response_data, + HttpWebResponseData *web_response_data +) +{ + switch (web_response_data->web_response_type) { + case CUSTOM_WEB_RESPONSE: + handle_custom_web_response(attachment, session_id, ctx_response_data, web_response_data); + break; + case REDIRECT_WEB_RESPONSE: + handle_redirect_response(attachment, session_id, ctx_response_data, web_response_data); + break; + default: + write_dbg( + attachment, + session_id, + DBG_LEVEL_WARNING, + "Received an unknown web response type %d", + web_response_data->web_response_type + ); + break; + } +} + +NanoCommunicationResult +service_reply_receiver( + NanoAttachment *attachment, + HttpSessionData *session_data, + WebResponseData **web_response_data, + NanoHttpModificationList **modification_list, + AttachmentDataType chunk_type +) +{ + HttpReplyFromService *reply_p; + NanoHttpModificationList *current_modification = NULL; + NanoCommunicationResult res; + + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_TRACE, + "Receiving verdict replies for %d chunks of inspected data", + session_data->remaining_messages_to_reply + ); + + if (session_data->remaining_messages_to_reply == 0) { + session_data->verdict = TRAFFIC_VERDICT_INSPECT; + return NANO_OK; + } + + do { + res = signal_for_session_data(attachment, session_data->session_id, chunk_type); + } while (res == NANO_AGAIN); + + if (res != NANO_OK) { + disconnect_communication(attachment); + restart_communication(attachment); + return NANO_ERROR; + } + + while (session_data->remaining_messages_to_reply) { + // For each expected message, receive the reply from the nano service. + reply_p = receive_data_from_service(attachment, session_data->session_id); + if (reply_p == NULL) { + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_WARNING, + "Failed to get reply from the nano service" + ); + return NANO_ERROR; + } + + // If reply isn't reconfiguration, it should be handled as part of session data related replies. + // The reason is that the reconfiguration is not related to a specific session but to attachment as a whole. + // It is a signal sent by the nano service to let the attachment know that it should reconfigure itself + // due to a new configuration being applied and it is broadcasted to all attachment's instances. + if (reply_p->verdict != TRAFFIC_VERDICT_RECONF) { + if (reply_p->session_id != session_data->session_id) { + // Verify if incoming reply is of a correct session. + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_DEBUG, + "Ignoring verdict to an already handled request %d", + reply_p->session_id + ); + popData(attachment->nano_service_ipc); + continue; + } + + session_data->remaining_messages_to_reply--; + } + + session_data->verdict = reply_p->verdict; + + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_TRACE, + "Verdict %d received", + session_data->verdict + ); + + switch(session_data->verdict) { + case TRAFFIC_VERDICT_INJECT: { + // Verdict inject received from the nano service. + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_TRACE, + "Verdict inject received from the nano service" + ); + updateMetricField(attachment, INJECT_VERDICTS_COUNT, 1); + + handle_inject_response( + attachment, + session_data->session_id, + modification_list, + reply_p->modify_data->inject_data, + reply_p->modification_count + ); + + session_data->verdict = TRAFFIC_VERDICT_INSPECT; + break; + } + + case TRAFFIC_VERDICT_DROP: { + // After a drop verdict no more replies will be sent, so we can leave the loop + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_TRACE, + "Verdict drop received from the nano service" + ); + + updateMetricField(attachment, DROP_VERDICTS_COUNT, 1); + handle_drop_response( + attachment, + session_data->session_id, + web_response_data, + reply_p->modify_data->web_response_data + ); + + session_data->remaining_messages_to_reply = 0; + while (*modification_list) { + current_modification = *modification_list; + *modification_list = (*modification_list)->next; + free(current_modification->modification.data); + free(current_modification); + } + popData(attachment->nano_service_ipc); + return NANO_HTTP_FORBIDDEN; + } + + case TRAFFIC_VERDICT_ACCEPT: { + // After an accept verdict no more replies will be sent, so we can leave the loop + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_TRACE, + "Verdict accept received from the nano service" + ); + updateMetricField(attachment, ACCEPT_VERDICTS_COUNT, 1); + session_data->remaining_messages_to_reply = 0; + popData(attachment->nano_service_ipc); + return NANO_OK; + } + + case TRAFFIC_VERDICT_IRRELEVANT: { + // After an irrelevant verdict, ignore the verdict and continue to the next response. + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_TRACE, + "Verdict irrelevant received from the nano service" + ); + updateMetricField(attachment, IRRELEVANT_VERDICTS_COUNT, 1); + break; + } + + case TRAFFIC_VERDICT_RECONF: { + // After a reconfiguration verdict, reset attachment config. + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_TRACE, + "Verdict reconf received from the nano service" + ); + updateMetricField(attachment, RECONF_VERDICTS_COUNT, 1); + reset_attachment_config(attachment); + break; + } + + case TRAFFIC_VERDICT_INSPECT: { + // Inspect verdict, continue to the next response. + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_TRACE, + "Verdict inspect received from the nano service" + ); + updateMetricField(attachment, INSPECT_VERDICTS_COUNT, 1); + break; + } + + case TRAFFIC_VERDICT_DELAYED: { + // After a delayed verdict, query the nano agent again to get an updated verdict. + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_DEBUG, + "Verdict delayed received from the nano service" + ); + updateMetricField(attachment, HOLD_VERDICTS_COUNT, 1); + break; + } + } + popData(attachment->nano_service_ipc); + } + + write_dbg( + attachment, + session_data->session_id, + DBG_LEVEL_DEBUG, + "No finalized verdict (ACCEPT, DROP) has been received from the nano service" + ); + return NANO_OK; +} + +/// +/// @brief Set meta data fragment element data and size. +/// @param[in, out] meta_data_elems Fragments data array. +/// @param[in, out] meta_data_sizes Fragments data sizes array. +/// @param[in] data Data to set into the meta_data_elems array. +/// @param[in] size Size to be set into the meta_data_sizes array. +/// @param[in] idx Index of the arrays to set the data and size into. +/// +static void +set_fragment_elem(char **meta_data_elems, uint16_t *meta_data_sizes, void *data, uint16_t size, uint idx) +{ + meta_data_elems[idx] = data; + meta_data_sizes[idx] = size; +} + +/// +/// @brief Set meta data fragments identifiers. +/// @details The data identifiers will be set on the 0 and 1 slots of the array. +/// @param[in, out] meta_data_elems Fragments data array. +/// @param[in, out] meta_data_sizes Fragments data sizes array. +/// @param[in] data_type Data type identifier to be set. +/// @param[in] cur_request_id Request's Id. +/// +static void +set_fragments_identifiers( + char **meta_data_elems, + uint16_t *meta_data_sizes, + uint16_t *data_type, + uint32_t *cur_request_id +) +{ + set_fragment_elem(meta_data_elems, meta_data_sizes, data_type, sizeof(uint16_t), 0); + set_fragment_elem(meta_data_elems, meta_data_sizes, cur_request_id, sizeof(uint32_t), 1); +} + +/// +/// @brief Checks if inspection is required for the given source IP address. +/// +/// @param src_ip The source IP address to check for inspection requirement. +/// @return 1 if inspection is required, 0 if inspection can be skipped, -1 on error. +/// +static int +IsInspectionRequiredForSource(NanoAttachment *attachment, SessionID session_id, const nano_str_t *src_ip) +{ + if (!isIPAddress((char *)src_ip->data)) { + write_dbg(attachment, session_id, DBG_LEVEL_WARNING, "Input %s is not an IP adress", src_ip->data); + return -1; + } + + write_dbg(attachment, session_id, DBG_LEVEL_DEBUG, "IP is relevant: %s", src_ip->data); + + return !isSkipSource((char *)src_ip->data); +} + +NanoCommunicationResult +connect_to_comm_socket(NanoAttachment *attachment) +{ + struct sockaddr_un server; + int cur_errno = 0; // temp fix for errno changing during print + + // Close the old socket if there was one. + if (attachment->comm_socket > 0) { + close(attachment->comm_socket); + attachment->comm_socket = -1; + } + + // Connect a new socket. + attachment->comm_socket = socket(AF_UNIX, SOCK_STREAM, 0); + if (attachment->comm_socket < 0) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Could not create socket, Error: %s", strerror(errno)); + return NANO_ERROR; + } + + server.sun_family = AF_UNIX; + strncpy(server.sun_path, attachment->shared_verdict_signal_path, sizeof(server.sun_path) - 1); + + if (connect(attachment->comm_socket, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) != -1) { + return NANO_OK; + } + + cur_errno = errno; + write_dbg( + attachment, + 0, + DBG_LEVEL_DEBUG, + "Could not connect to nano service. Path: %s, Error: %s, Errno: %d", + server.sun_path, + strerror(errno), + cur_errno + ); + + return NANO_ERROR; +} + +NanoCommunicationResult +connect_to_registration_socket(NanoAttachment *attachment) +{ + struct sockaddr_un server; + int cur_errno = 0; // temp fix for errno changing during print + + // Connect a new socket. + attachment->registration_socket = socket(AF_UNIX, SOCK_STREAM, 0); + if (attachment->registration_socket < 0) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Could not create socket, Error: %s", strerror(errno)); + return NANO_ERROR; + } + + server.sun_family = AF_UNIX; + strncpy(server.sun_path, SHARED_REGISTRATION_SIGNAL_PATH, sizeof(server.sun_path) - 1); + + if (connect(attachment->registration_socket, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) != -1) { + return NANO_OK; + } + + cur_errno = errno; + write_dbg( + attachment, + 0, + DBG_LEVEL_DEBUG, + "Could not connect to nano service. Path: %s, Error: %s, Errno: %d", + server.sun_path, + strerror(errno), + cur_errno + ); + + return NANO_ERROR; +} + +void +nano_metadata_sender( + NanoAttachment *attachment, + HttpMetaData *metadata, + HttpEventThreadCtx *ctx, + uint32_t cur_request_id, + unsigned int *num_of_messages_sent, + bool is_verdict_requested +) +{ + uint16_t chunk_type; + NanoCommunicationResult res; + char *fragments[META_DATA_COUNT + 2]; + uint16_t fragments_sizes[META_DATA_COUNT + 2]; + uint8_t meta_data_count = META_DATA_COUNT - 4; + + write_dbg(attachment, cur_request_id, DBG_LEVEL_TRACE, "Sending request start meta data for inspection"); + + if(!IsInspectionRequiredForSource(attachment, cur_request_id, &metadata->client_ip)) { + write_dbg( + attachment, + cur_request_id, + DBG_LEVEL_DEBUG, + "Skipping IP Source" + ); + + ctx->session_data_p->verdict = TRAFFIC_VERDICT_IRRELEVANT; + ctx->res = NANO_DECLINED; + return; + } + + // Sets the fragments + chunk_type = REQUEST_START; + set_fragments_identifiers(fragments, fragments_sizes, &chunk_type, &cur_request_id); + + // Add protocol length to fragments. + set_fragment_elem( + fragments, + fragments_sizes, + &metadata->http_protocol.len, + sizeof(uint16_t), + HTTP_PROTOCOL_SIZE + 2 + ); + // Add protocol data to fragments. + set_fragment_elem( + fragments, + fragments_sizes, + metadata->http_protocol.data, + metadata->http_protocol.len, + HTTP_PROTOCOL_DATA + 2 + ); + + // Add method data length to fragments. + set_fragment_elem(fragments, fragments_sizes, &metadata->method_name.len, sizeof(uint16_t), HTTP_METHOD_SIZE + 2); + // Add method data to fragments + set_fragment_elem( + fragments, + fragments_sizes, + metadata->method_name.data, + metadata->method_name.len, + HTTP_METHOD_DATA + 2 + ); + + // Add host data length to the fragments. + set_fragment_elem( + fragments, + fragments_sizes, + &metadata->host.len, + sizeof(uint16_t), + HOST_NAME_SIZE + 2 + ); + // Add host data to the fragments. + set_fragment_elem( + fragments, + fragments_sizes, + metadata->host.data, + metadata->host.len, + HOST_NAME_DATA + 2 + ); + + // Add listening IP metadata. + set_fragment_elem( + fragments, + fragments_sizes, + &metadata->listening_ip.len, + sizeof(uint16_t), + LISTENING_ADDR_SIZE + 2 + ); + set_fragment_elem( + fragments, + fragments_sizes, + metadata->listening_ip.data, + metadata->listening_ip.len, + LISTENING_ADDR_DATA + 2 + ); + + // Add listening port data. + set_fragment_elem( + fragments, + fragments_sizes, + &metadata->listening_port, + sizeof(uint16_t), + LISTENING_PORT + 2 + ); + + // Add URI data. + set_fragment_elem(fragments, fragments_sizes, &metadata->uri.len, sizeof(uint16_t), URI_SIZE + 2); + set_fragment_elem(fragments, fragments_sizes, metadata->uri.data, metadata->uri.len, URI_DATA + 2); + + // Add client IP data. + set_fragment_elem(fragments, fragments_sizes, &metadata->client_ip.len, sizeof(uint16_t), CLIENT_ADDR_SIZE + 2); + set_fragment_elem( + fragments, + fragments_sizes, + metadata->client_ip.data, + metadata->client_ip.len, + CLIENT_ADDR_DATA + 2 + ); + + // Add client IP port. + set_fragment_elem( + fragments, + fragments_sizes, + &metadata->client_port, + sizeof(uint16_t), + CLIENT_PORT + 2 + ); + + if (metadata->parsed_host.len > 0) { + // Add parsed host data. + set_fragment_elem( + fragments, + fragments_sizes, + &metadata->parsed_host.len, + sizeof(uint16_t), + PARSED_HOST_SIZE + 2 + ); + set_fragment_elem( + fragments, + fragments_sizes, + metadata->parsed_host.data, + metadata->parsed_host.len, + PARSED_HOST_DATA + 2 + ); + meta_data_count += 2; + } + + if (metadata->parsed_uri.len > 0) { + // Add parsed URI data. + set_fragment_elem( + fragments, + fragments_sizes, + &metadata->parsed_uri.len, + sizeof(uint16_t), + PARSED_URI_SIZE + 2 + ); + set_fragment_elem( + fragments, + fragments_sizes, + metadata->parsed_uri.data, + metadata->parsed_uri.len, + PARSED_URI_DATA + 2 + ); + meta_data_count += 2; + } + + // Sends all the data to the nano service. + res = send_session_data_to_service( + attachment, + fragments, + fragments_sizes, + meta_data_count + 2, + cur_request_id, + chunk_type + ); + + if (res == NANO_ERROR) { + // Failed to send the metadata to nano service. + write_dbg( + attachment, + cur_request_id, + DBG_LEVEL_WARNING, + "Failed to send request meta data to the nano service. Session ID: %d", + cur_request_id + ); + ctx->res = NANO_ERROR; + return; + } + + if (res == NANO_OK) { + *num_of_messages_sent += 1; + } + + if (is_verdict_requested) { + ctx->res = service_reply_receiver( + attachment, + ctx->session_data_p, + &ctx->web_response_data, + &ctx->modifications, + chunk_type + ); + } +} + +void +nano_send_response_code( + NanoAttachment *attachment, + uint16_t response_code, + HttpEventThreadCtx *ctx, + uint32_t cur_request_id, + unsigned int *num_messages_sent +) +{ + char *fragments[RESPONSE_CODE_COUNT]; + uint16_t fragments_sizes[RESPONSE_CODE_COUNT]; + uint16_t chunk_type = RESPONSE_CODE; + NanoCommunicationResult res; + + write_dbg(attachment, cur_request_id, DBG_LEVEL_TRACE, "Sending response code (%d) for inspection", response_code); + + set_fragments_identifiers(fragments, fragments_sizes, &chunk_type, &cur_request_id); + set_fragment_elem(fragments, fragments_sizes, &response_code, sizeof(uint16_t), 2); + + res = send_session_data_to_service( + attachment, + fragments, + fragments_sizes, + RESPONSE_CODE_COUNT, + cur_request_id, + chunk_type + ); + + if (res != NANO_OK) { + ctx->res = NANO_ERROR; + return; + } + + *num_messages_sent += 1; +} + +void +nano_send_response_content_length( + NanoAttachment *attachment, + uint64_t content_length, + HttpEventThreadCtx *ctx, + uint32_t cur_request_id, + unsigned int *num_messages_sent +) +{ + char *fragments[CONTENT_LENGTH_COUNT]; + uint16_t fragments_sizes[CONTENT_LENGTH_COUNT]; + uint16_t chunk_type = CONTENT_LENGTH; + NanoCommunicationResult res; + + write_dbg( + attachment, + cur_request_id, + DBG_LEVEL_TRACE, + "Sending content length (%ld) to the intaker", + content_length + ); + + set_fragments_identifiers(fragments, fragments_sizes, &chunk_type, &cur_request_id); + set_fragment_elem(fragments, fragments_sizes, &content_length, sizeof(content_length), 2); + + res = send_session_data_to_service( + attachment, + fragments, + fragments_sizes, + CONTENT_LENGTH_COUNT, + cur_request_id, + chunk_type + ); + + if (res != NANO_OK) { + ctx->res = NANO_ERROR; + return; + } + + *num_messages_sent += 1; +} + +/// +/// @brief Sends a bulk of headers to the service using a NanoAttachment. +/// +/// This function takes a NanoAttachment pointer, +/// an array of data fragments, an array of data fragment sizes, the number +/// of headers in the bulk, a flag indicating if this is the last bulk, the index of the current bulk part, +/// and the current request ID. +/// +/// @param attachment Pointer to the NanoAttachment struct representing the attachment. +/// @param data Array of data fragments containing the bulk of headers. +/// @param data_sizes Array of sizes corresponding to the data fragments. +/// @param header_type Type of the headers (REQUEST_HEADER or RESPONSE_HEADER). +/// @param num_headers Number of headers in the bulk. +/// @param is_last_part Flag indicating if this is the last bulk. +/// @param bulk_part_index Index of the current bulk part. +/// @param cur_request_id Current request ID. +/// +/// @return NanoCommunicationResult NANO_OK if successful, NANO_ERROR otherwise. +/// +static NanoCommunicationResult +send_header_bulk( + NanoAttachment *attachment, + char **data, + uint16_t *data_sizes, + AttachmentDataType header_type, + const unsigned int num_headers, + uint8_t is_last_part, + uint8_t bulk_part_index, + uint32_t cur_request_id +) +{ + NanoCommunicationResult res; + + set_fragments_identifiers(data, data_sizes, (uint16_t *)&header_type, &cur_request_id); + set_fragment_elem(data, data_sizes, &is_last_part, sizeof(is_last_part), 2); + set_fragment_elem(data, data_sizes, &bulk_part_index, sizeof(bulk_part_index), 3); + + res = send_session_data_to_service( + attachment, + data, + data_sizes, + HEADER_DATA_COUNT * num_headers + 4, + cur_request_id, + REQUEST_HEADER + ); + + if (res != NANO_OK) { + write_dbg(attachment, cur_request_id, DBG_LEVEL_TRACE, "Failed to send bulk of %iu headers", num_headers); + return NANO_ERROR; + } + + write_dbg( + attachment, + cur_request_id, + DBG_LEVEL_TRACE, + "Successfully sent bulk number %ui with %d headers", + bulk_part_index, + num_headers + ); + + return NANO_OK; +} + +/// +/// @brief Adds an HTTP header to a bulk data structure. +/// +/// This function takes an array of fragments, an array of fragment sizes, +/// an HttpHeaderData struct representing the header, +/// and an index to determine the position in the bulk data structure where the header should be added. +/// +/// @param attachment Pointer to the NanoAttachment struct representing the attachment. +/// @param session_id The session ID associated with the attachment. +/// @param fragments Array of fragments to add the header to. +/// @param fragments_sizes Array of sizes corresponding to the fragments. +/// @param header Pointer to the HttpHeaderData struct representing the header to add. +/// @param index Index indicating where in the bulk data structure to add the header. +/// +static inline void +add_header_to_bulk( + NanoAttachment *attachment, + uint32_t session_id, + char **fragments, + uint16_t *fragments_sizes, + HttpHeaderData *header, + unsigned int index +) +{ + write_dbg( + attachment, + session_id, + DBG_LEVEL_TRACE, + "Sending current header (key: '%.*s', value: '%.*s') to inspection", + header->key.len, + header->key.data, + header->value.len, + header->value.data + ); + + unsigned int pos = index * HEADER_DATA_COUNT; + set_fragment_elem(fragments, fragments_sizes, &header->key.len, sizeof(uint16_t), pos + HEADER_KEY_SIZE + 4); + set_fragment_elem(fragments, fragments_sizes, header->key.data, header->key.len, pos + HEADER_KEY_DATA + 4); + set_fragment_elem(fragments, fragments_sizes, &header->value.len, sizeof(uint16_t), pos + HEADER_VAL_SIZE + 4); + set_fragment_elem(fragments, fragments_sizes, header->value.data, header->value.len, pos + HEADER_VAL_DATA + 4); +} + +void +nano_header_sender( + NanoAttachment *attachment, + HttpHeaders *headers, + HttpEventThreadCtx *ctx, + AttachmentDataType header_type, + uint32_t cur_request_id, + unsigned int *num_messages_sent, + bool is_verdict_requested +) +{ + int is_final_header = 0; + int bulk_index = 0; + char *fragments[HEADER_DATA_COUNT * MAX_HEADER_BULK_SIZE + 4]; + uint16_t fragments_sizes[HEADER_DATA_COUNT * MAX_HEADER_BULK_SIZE + 4]; + size_t header_index = 0; + int fragment_index = 0; + NanoCommunicationResult send_bulk_result; + + write_dbg( + attachment, + cur_request_id, + DBG_LEVEL_TRACE, + "Sending %s headers for inspection", + header_type == REQUEST_HEADER ? "request" : "response" + ); + + for (header_index = 0; header_index < headers->headers_count ; header_index++) { + if (header_index == headers->headers_count - 1) { + is_final_header = 1; + } + + add_header_to_bulk( + attachment, + cur_request_id, + fragments, + fragments_sizes, + &headers->data[header_index], + fragment_index + ); + + fragment_index++; + if (fragment_index < MAX_HEADER_BULK_SIZE && !is_final_header) continue; + + send_bulk_result = send_header_bulk( + attachment, + fragments, + fragments_sizes, + header_type, + fragment_index, + is_final_header, + bulk_index, + cur_request_id + ); + if (send_bulk_result != NANO_OK) { + write_dbg( + attachment, + cur_request_id, + DBG_LEVEL_WARNING, + "Failed to send request headers to the nano service. Session ID: %d", + cur_request_id + ); + ctx->res = NANO_ERROR; + return; + } + + bulk_index++; + fragment_index = 0; + } + + *num_messages_sent += bulk_index; + + write_dbg( + attachment, + cur_request_id, + DBG_LEVEL_TRACE, + "Exit after inspection of %d headers", + headers->headers_count + ); + + if (is_verdict_requested) { + ctx->res = service_reply_receiver( + attachment, + ctx->session_data_p, + &ctx->web_response_data, + &ctx->modifications, + header_type + ); + } +} + +void +nano_body_sender( + NanoAttachment *attachment, + HttpBody *bodies, + HttpEventThreadCtx *ctx, + AttachmentDataType body_type, + uint32_t cur_request_id, + unsigned int *num_messages_sent +) +{ + char *fragments[BODY_DATA_COUNT]; + uint16_t fragments_sizes[BODY_DATA_COUNT]; + uint8_t is_final_chunk = 0; + uint8_t body_index = 0; + nano_str_t *body = NULL; + NanoCommunicationResult send_bulk_result; + + write_dbg( + attachment, + cur_request_id, + DBG_LEVEL_TRACE, + "Sending %s bodies for inspection", + body_type == REQUEST_BODY ? "request" : "response" + ); + + for (body_index = 0; body_index < bodies->bodies_count ; body_index++) { + if (body_index == bodies->bodies_count - 1) { + is_final_chunk = 1; + } + + set_fragments_identifiers(fragments, fragments_sizes, (uint16_t *)&body_type, &cur_request_id); + + set_fragment_elem(fragments, fragments_sizes, &is_final_chunk, sizeof(is_final_chunk), 2); + set_fragment_elem(fragments, fragments_sizes, &body_index, sizeof(body_index), 3); + + body = &bodies->data[body_index]; + set_fragment_elem(fragments, fragments_sizes, body->data, body->len, 4); + + send_bulk_result = send_session_data_to_service( + attachment, + fragments, + fragments_sizes, + BODY_DATA_COUNT, + cur_request_id, + body_type + ); + if (send_bulk_result != NANO_OK) { + write_dbg( + attachment, + cur_request_id, + DBG_LEVEL_WARNING, + "Failed to send request body to the nano service. Session ID: %d", + cur_request_id + ); + ctx->res = NANO_ERROR; + return; + } + } + + *num_messages_sent += body_index; + + write_dbg( + attachment, + cur_request_id, + DBG_LEVEL_TRACE, + "Exit after inspection of %d body chunks", + bodies->bodies_count + ); + + ctx->res = service_reply_receiver( + attachment, + ctx->session_data_p, + &ctx->web_response_data, + &ctx->modifications, + body_type + ); +} + +void +nano_end_transaction_sender( + NanoAttachment *attachment, + AttachmentDataType end_transaction_type, + HttpEventThreadCtx *ctx, + SessionID cur_request_id, + unsigned int *num_messages_sent +) +{ + char *fragments[END_TRANSACTION_DATA_COUNT]; + uint16_t fragments_sizes[END_TRANSACTION_DATA_COUNT]; + NanoCommunicationResult res; + + write_dbg( + attachment, + cur_request_id, + DBG_LEVEL_TRACE, + "Sending end %s event flag for inspection", + end_transaction_type == REQUEST_END ? "request" : "response" + ); + + set_fragments_identifiers(fragments, fragments_sizes, (uint16_t *)&end_transaction_type, &cur_request_id); + + res = send_session_data_to_service( + attachment, + fragments, + fragments_sizes, + END_TRANSACTION_DATA_COUNT, + cur_request_id, + attachment->fail_open_timeout + ); + if (res != NANO_OK) { + write_dbg( + attachment, + cur_request_id, + DBG_LEVEL_TRACE, + "Failed to send end %s event flag for inspection", + end_transaction_type == REQUEST_END ? "request" : "response" + ); + return; + } + + *num_messages_sent += 1; + + ctx->res = service_reply_receiver( + attachment, + ctx->session_data_p, + &ctx->web_response_data, + &ctx->modifications, + end_transaction_type + ); +} + +void +nano_request_delayed_verdict( + NanoAttachment *attachment, + HttpEventThreadCtx *ctx, + SessionID cur_request_id, + unsigned int *num_messages_sent +) +{ + char *fragments[DELAYED_VERDICT_DATA_COUNT]; + uint16_t fragments_sizes[DELAYED_VERDICT_DATA_COUNT]; + AttachmentDataType wait_transaction_type = REQUEST_DELAYED_VERDICT; + NanoCommunicationResult res; + + write_dbg( + attachment, + cur_request_id, + DBG_LEVEL_TRACE, + "Sending delayed event flag for inspection" + ); + + set_fragments_identifiers(fragments, fragments_sizes, (uint16_t *)&wait_transaction_type, &cur_request_id); + + res = send_session_data_to_service( + attachment, + fragments, + fragments_sizes, + DELAYED_VERDICT_DATA_COUNT, + cur_request_id, + attachment->fail_open_timeout + ); + if (res != NANO_OK) { + write_dbg( + attachment, + cur_request_id, + DBG_LEVEL_TRACE, + "Failed to send delayed event flag for inspection" + ); + return; + } + + *num_messages_sent += 1; + + ctx->res = service_reply_receiver( + attachment, + ctx->session_data_p, + &ctx->web_response_data, + &ctx->modifications, + wait_transaction_type + ); +} + +void +nano_send_metric_data_sender(NanoAttachment *attachment) +{ + char *fragments; + uint16_t fragments_sizes; + uint16_t data_size; + NanoCommunicationResult res; + NanoHttpMetricData metric_data_to_send; + + write_dbg( + attachment, + 0, + DBG_LEVEL_DEBUG, + "Sending metric data to service" + ); + + metric_data_to_send.data_type = METRIC_DATA_FROM_PLUGIN; + data_size = METRIC_TYPES_COUNT * sizeof(metric_data_to_send.data[0]); + memcpy(metric_data_to_send.data, attachment->metric_data, data_size); + + fragments = (char *)&metric_data_to_send; + fragments_sizes = sizeof(NanoHttpMetricData); + res = send_session_data_to_service( + attachment, + &fragments, + &fragments_sizes, + 1, + 0, + attachment->fail_open_timeout + ); + + if (res != NANO_OK) { + write_dbg( + attachment, + 0, + DBG_LEVEL_TRACE, + "Failed to send metric data to the nano service worker ID" + ); + return; + } + + reset_metric_data(attachment); +} diff --git a/attachments/nano_attachment/nano_attachment_io.h b/attachments/nano_attachment/nano_attachment_io.h new file mode 100644 index 0000000..21972d4 --- /dev/null +++ b/attachments/nano_attachment/nano_attachment_io.h @@ -0,0 +1,230 @@ +#ifndef __NANO_ATTACHMENT_IO_H__ +#define __NANO_ATTACHMENT_IO_H__ + +#include + +#include "nano_attachment_common.h" +#include "nano_initializer.h" +#include "nano_attachment_sender_thread.h" +#include "shmem_ipc_2.h" + +/// @brief Sends session data chunk to a nano service for inspection. +/// +/// This function sends the provided data fragments to the nano service for inspection. +/// +/// @param attachment A pointer to a NanoAttachment structure representing the attachment to the nano service. +/// @param fragments An array of pointers to character arrays representing the data fragments to send. +/// @param fragments_sizes An array of uint16_t values representing the sizes of the data fragments. +/// @param num_of_data_elem An 8-bit integer representing the number of data elements (fragments) to send. +/// @param cur_session_id An unsigned 32-bit integer representing the current session ID. +/// @param chunk_type An enumeration representing the type of data chunk being sent. +/// +/// @return NANO_OK if the data is sent successfully, NANO_ERROR otherwise. +/// +NanoCommunicationResult +send_session_data_to_service( + NanoAttachment *attachment, + char **fragments, + const uint16_t *fragments_sizes, + uint8_t num_of_data_elem, + uint32_t cur_session_id, + AttachmentDataType chunk_type +); + +/// +/// @brief Connect to the communication socket. +/// +/// This function creates a new socket and connects it to the verdict +/// Unix domain socket address. If the attachment already has a communication +/// socket open, it is closed before creating a new one. +/// +/// @param[in] attachment The NanoAttachment struct containing socket information. +/// @returns A NanoCommunicationResult indicating the success of the operation. +/// +NanoCommunicationResult connect_to_comm_socket(NanoAttachment *attachment); + +/// +/// @brief Create an unix socket and connect to the attachment registration service. +/// @param[in] attachment Points to initiated NanoAttachment struct. +/// @returns NanoCommunicationResult +/// - #NANO_OK +/// - #NANO_ERROR +/// +NanoCommunicationResult connect_to_registration_socket(NanoAttachment *attachment); + +/// @brief Receives and processes replies from a nano service regarding traffic inspection verdicts. +/// +/// This function waits for replies from the service and handles each reply based on the verdict received. +/// +/// @param attachment A pointer to a NanoAttachment structure representing the attachment to the nano service. +/// @param session_data A pointer to a HttpSessionData which holds the session data. +/// @param web_response_data A pointer to a WebResponseData structure representing the response data. +/// @param modification_list A pointer to a pointer to a NanoHttpModificationList structure +/// representing a list of HTTP modifications. +/// +/// @return NANO_OK if the function completes successfully +/// NANO_ERROR if an error occurs during processing +/// NANO_HTTP_FORBIDDEN if a drop verdict is received. +/// +NanoCommunicationResult +service_reply_receiver( + NanoAttachment *attachment, + HttpSessionData *session_data, + WebResponseData **web_response_data, + NanoHttpModificationList **modification_list, + AttachmentDataType chunk_type +); + +/// +/// @brief Sends request start metadata for inspection to the nano service. +/// +/// @param attachment Pointer to the NanoAttachment struct representing the attachment. +/// @param metadata Pointer to the HttpMetaData struct containing the HTTP metadata. +/// @param ctx Pointer to the HttpEventThreadCtx struct containing the HTTP event context. +/// @param cur_request_id The current request ID. +/// @param num_of_messages_sent Pointer to an unsigned int to store the number of messages sent. +/// @param is_verdict_requested Boolean value indicating if a verdict is requested. +/// +void +nano_metadata_sender( + NanoAttachment *attachment, + HttpMetaData *metadata, + HttpEventThreadCtx *ctx, + uint32_t cur_request_id, + unsigned int *num_of_messages_sent, + bool is_verdict_requested +); + +/// +/// @brief Sends a response code for inspection. +/// +/// This function sends a response code for inspection to a service. +/// +/// @param attachment A pointer to the NanoAttachment structure. +/// @param response_code The response code to send. +/// @param ctx The HttpEventThreadCtx context. +/// @param cur_request_id The current request ID. +/// @param num_messages_sent A pointer to the number of messages sent. +/// +void +nano_send_response_code( + NanoAttachment *attachment, + uint16_t response_code, + HttpEventThreadCtx *ctx, + uint32_t cur_request_id, + unsigned int *num_messages_sent +); + +/// +/// @brief Sends the content length to the intaker. +/// +/// This function sends the content length to the intaker for processing. +/// +/// @param attachment A pointer to the NanoAttachment structure. +/// @param content_length The content length to send. +/// @param ctx The HttpEventThreadCtx context. +/// @param cur_request_id The current request ID. +/// @param num_messages_sent A pointer to the number of messages sent. +/// +void +nano_send_response_content_length( + NanoAttachment *attachment, + uint64_t content_length, + HttpEventThreadCtx *ctx, + uint32_t cur_request_id, + unsigned int *num_messages_sent +); + +/// +/// @brief Sends HTTP headers for inspection using a NanoAttachment. +/// +/// This function takes a NanoAttachment pointer, an HttpHeaders struct containing the headers to send, +/// the type of the headers (request or response), the current request ID, and a pointer to store +/// the number of messages sent. +/// +/// @param attachment Pointer to the NanoAttachment struct representing the attachment. +/// @param headers Pointer to the HttpHeaders struct containing the headers to send. +/// @param ctx Pointer to the HttpEventThreadCtx struct containing the context of the current thread. +/// @param header_type Type of the headers (REQUEST_HEADER or RESPONSE_HEADER). +/// @param cur_request_id Current request ID. +/// @param num_messages_sent Pointer to an unsigned int to store the number of messages sent. +/// +void +nano_header_sender( + NanoAttachment *attachment, + HttpHeaders *headers, + HttpEventThreadCtx *ctx, + AttachmentDataType header_type, + uint32_t cur_request_id, + unsigned int *num_messages_sent, + bool is_verdict_requested +); + +/// +/// @brief Sends the body of a request or response for inspection to a nano service. +/// +/// This function iterates over the body chunks, creates fragments, and sends them +/// in bulk to the service. It also handles the final chunk and updates the number +/// of messages sent. +/// +/// @param attachment Pointer to a NanoAttachment struct representing the attachment/module. +/// @param bodies Pointer to an HttpBody struct containing the HTTP request/response body data. +/// @param ctx Pointer to an HttpEventThreadCtx struct representing the HTTP event thread context. +/// @param body_type Enum value indicating whether the body is a request or response body. +/// @param cur_request_id Current request ID for logging and tracking purposes. +/// @param num_messages_sent Pointer to an unsigned int to track the number of messages sent. +/// +void +nano_body_sender( + NanoAttachment *attachment, + HttpBody *bodies, + HttpEventThreadCtx *ctx, + AttachmentDataType body_type, + uint32_t cur_request_id, + unsigned int *num_messages_sent +); + +/// +/// @brief Sends an end transaction event to a service for inspection. +/// +/// @param attachment The NanoAttachment struct representing the attachment/module. +/// @param end_transaction_type The type of end transaction event (REQUEST_END or RESPONSE_END). +/// @param ctx Pointer to the HttpEventThreadCtx struct containing the context of the current thread. +/// @param cur_request_id The ID of the current request. +/// @param num_messages_sent Pointer to an unsigned integer to store the number of messages sent. +/// @return NANO_OK if the end transaction event was sent successfully, NANO_ERROR otherwise. +/// +void +nano_end_transaction_sender( + NanoAttachment *attachment, + AttachmentDataType end_transaction_type, + HttpEventThreadCtx *ctx, + SessionID cur_request_id, + unsigned int *num_messages_sent +); + +/// +/// @brief Sends delayed transaction event to a service for inspection. +/// +/// @param attachment The NanoAttachment struct representing the attachment/module. +/// @param ctx Pointer to the HttpEventThreadCtx struct containing the context of the current thread. +/// @param cur_request_id The ID of the current request. +/// @param num_messages_sent Pointer to an unsigned integer to store the number of messages sent. +/// +void +nano_request_delayed_verdict( + NanoAttachment *attachment, + HttpEventThreadCtx *ctx, + SessionID cur_request_id, + unsigned int *num_messages_sent +); + +/// +/// @brief Sends attachment's metric data to the service. +/// +/// @param attachment The NanoAttachment struct representing the attachment/module, which contains the metric data. +/// +void +nano_send_metric_data_sender(NanoAttachment *attachment); + +#endif // __NANO_ATTACHMENT_IO_H__ diff --git a/attachments/nano_attachment/nano_attachment_metric.c b/attachments/nano_attachment/nano_attachment_metric.c new file mode 100644 index 0000000..cc8fae6 --- /dev/null +++ b/attachments/nano_attachment/nano_attachment_metric.c @@ -0,0 +1,83 @@ +#include "nano_attachment_metric.h" + +#include "nano_initializer.h" +#include "nano_attachment_common.h" + +void +reset_metric_data(NanoAttachment *attachment) +{ + int i; + for (i = 0 ; i < METRIC_TYPES_COUNT ; i++) { + attachment->metric_data[i] = 0; + attachment->metric_average_data_divisor[i] = 0; + } +} + +static void +updateCounterMetricField(NanoAttachment *attachment, AttachmentMetricType metric_type, uint64_t value) +{ + attachment->metric_data[metric_type] += value; +} + +static void +updateAverageMetricField(NanoAttachment *attachment, AttachmentMetricType metric_type, uint64_t value) +{ + attachment->metric_data[metric_type] = + (((attachment->metric_data[metric_type] * attachment->metric_average_data_divisor[metric_type]) + value) / + (attachment->metric_average_data_divisor[metric_type] + 1)); + attachment->metric_average_data_divisor[metric_type] += 1; +} + +static void +updateMaxMetricField(NanoAttachment *attachment, AttachmentMetricType metric_type, uint64_t value) +{ + if (attachment->metric_data[metric_type] < value) attachment->metric_data[metric_type] = value; +} + +static void +updateMinMetricField(NanoAttachment *attachment, AttachmentMetricType metric_type, uint64_t value) +{ + if (attachment->metric_data[metric_type] == 0) { + attachment->metric_data[metric_type] = value; + } else if (attachment->metric_data[metric_type] > value) { + attachment->metric_data[metric_type] = value; + } +} + +void +updateMetricField(NanoAttachment *attachment, AttachmentMetricType metric_type, uint64_t value) +{ + switch (metric_type) { + case CPU_USAGE: + case AVERAGE_VM_MEMORY_USAGE: + case AVERAGE_RSS_MEMORY_USAGE: + case AVERAGE_REQ_BODY_SIZE_UPON_TIMEOUT: + case AVERAGE_RES_BODY_SIZE_UPON_TIMEOUT: + case AVERAGE_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT: + case AVERAGE_REQ_PPROCESSING_TIME_UNTIL_VERDICT: + case AVERAGE_RES_PPROCESSING_TIME_UNTIL_VERDICT: { + if (value != 0) updateAverageMetricField(attachment, metric_type, value); + break; + } + case MAX_VM_MEMORY_USAGE: + case MAX_RSS_MEMORY_USAGE: + case MAX_REQ_BODY_SIZE_UPON_TIMEOUT: + case MAX_RES_BODY_SIZE_UPON_TIMEOUT: + case MAX_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT: + case MAX_REQ_PPROCESSING_TIME_UNTIL_VERDICT: + case MAX_RES_PPROCESSING_TIME_UNTIL_VERDICT: { + if (value != 0) updateMaxMetricField(attachment, metric_type, value); + break; + } + case MIN_REQ_BODY_SIZE_UPON_TIMEOUT: + case MIN_RES_BODY_SIZE_UPON_TIMEOUT: + case MIN_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT: + case MIN_REQ_PPROCESSING_TIME_UNTIL_VERDICT: + case MIN_RES_PPROCESSING_TIME_UNTIL_VERDICT: { + if (value != 0) updateMinMetricField(attachment, metric_type, value); + break; + } + default: + updateCounterMetricField(attachment, metric_type, value); + } +} diff --git a/attachments/nano_attachment/nano_attachment_metric.h b/attachments/nano_attachment/nano_attachment_metric.h new file mode 100644 index 0000000..775c7d9 --- /dev/null +++ b/attachments/nano_attachment/nano_attachment_metric.h @@ -0,0 +1,33 @@ +#ifndef __NANO_ATTACHMENT_METRIC_H__ +#define __NANO_ATTACHMENT_METRIC_H__ + +#include "nano_attachment_common.h" +#include "nano_initializer.h" + +/// +/// @brief Updates a specified metric field of the NanoAttachment structure. +/// +/// This function updates the value of a specified metric field within the NanoAttachment structure. +/// It selects the appropriate update strategy (counter, average, maximum, or minimum) based on the type +/// of the metric provided. For average, maximum, and minimum metrics, the function updates the metric value +/// only if the provided value is non-zero. For counter metrics, the value is always incremented. +/// +/// @param attachment A pointer to the NanoAttachment structure. +/// @param metric_type The type of the metric to be updated. This determines the update strategy used. +/// @param value The value to update the metric with. For average, maximum, and minimum metrics, +/// this value must be non-zero to be considered. +/// +void updateMetricField(NanoAttachment *attachment, AttachmentMetricType metric_type, uint64_t value); + +/// +/// @brief Resets all metric data fields of the NanoAttachment structure. +/// +/// This function resets the metric data and the average data divisor fields within the NanoAttachment structure +/// to zero. It is typically used to initialize or clear the metric data before starting a new measurement session +/// or after completing an existing session. +/// +/// @param attachment A pointer to the NanoAttachment structure. +/// +void reset_metric_data(NanoAttachment *attachment); + +#endif // __NANO_ATTACHMENT_METRIC_H__ diff --git a/attachments/nano_attachment/nano_attachment_sender.c b/attachments/nano_attachment/nano_attachment_sender.c new file mode 100644 index 0000000..870fb64 --- /dev/null +++ b/attachments/nano_attachment/nano_attachment_sender.c @@ -0,0 +1,795 @@ +#include "nano_attachment_sender.h" + +#include +#include + +#include "nano_attachment_sender_thread.h" +#include "nano_attachment_thread.h" +#include "nano_utils.h" +#include "nano_attachment_metric.h" + +static unsigned char default_uuid[] = "20118dba-81f7-4999-8e94-003cf242f5dd\0"; +static const size_t default_uuid_size = 37; + +static unsigned char default_title[] = "Default Title\0"; +static const size_t default_title_size = 14; + +static unsigned char default_body[] = "Default Body\0"; +static const size_t default_body_size = 13; + +static uint16_t default_response_code = 403; + +/// +/// @brief Creates a default block page. +/// +/// @param attachment Pointer to the NanoAttachment struct representing the attachment. +/// @param session_id The session ID associated with the attachment. +/// @return A pointer to a WebResponseData struct containing the default block page data. +/// +static WebResponseData* +CreateDefaultBlockPage(NanoAttachment *attachment, SessionID session_id) +{ + WebResponseData *web_response_data = NULL; + CustomResponseData *custom_response_data = NULL; + + web_response_data = (WebResponseData *)malloc(sizeof(WebResponseData)); + if (web_response_data == NULL) { + write_dbg( + attachment, + session_id, + DBG_LEVEL_WARNING, + "Failed to allocate memory for WebResponseData" + ); + return NULL; + } + + custom_response_data = (CustomResponseData *)malloc(sizeof(CustomResponseData)); + if (custom_response_data == NULL) { + write_dbg( + attachment, + session_id, + DBG_LEVEL_WARNING, + "Failed to allocate memory for CustomResponseData" + ); + free(web_response_data); + return NULL; + } + + web_response_data->web_response_type = CUSTOM_WEB_RESPONSE; + memcpy(web_response_data->uuid, default_uuid, default_uuid_size); + custom_response_data->response_code = default_response_code; + memcpy(custom_response_data->title, default_title, default_title_size); + memcpy(custom_response_data->body, default_body, default_body_size); + web_response_data->data = (DataBuffer*)custom_response_data; + + return web_response_data; +} + +/// +/// @brief Get a string representation of the AttachmentVerdict enum. +/// +/// @param verdict The AttachmentVerdict enum value. +/// @return A string representation of the enum value. +/// +static const char* +AttachmentVerdictToString(AttachmentVerdict verdict) +{ + switch (verdict) { + case ATTACHMENT_VERDICT_INSPECT: + return "inspect"; + case ATTACHMENT_VERDICT_ACCEPT: + return "accept"; + case ATTACHMENT_VERDICT_DROP: + return "drop"; + case ATTACHMENT_VERDICT_INJECT: + return "inject"; + default: + return "unknown"; + } +} + +/// +/// @brief Sends a verdict response for a corrupt memory condition. +/// +/// @param attachment Pointer to the NanoAttachment struct representing the attachment. +/// @param session_data_p Pointer to the HttpSessionData struct containing the session data. +/// @return An AttachmentVerdictResponse struct containing the session ID and verdict. +/// +static AttachmentVerdictResponse +SendCorruptMemoryVerdict(NanoAttachment *attachment, HttpSessionData *session_data_p) +{ + AttachmentVerdictResponse response = { + .session_id = session_data_p->session_id, + .web_response_data = NULL, + .modifications = NULL + }; + + if (attachment->fail_mode_verdict == NANO_OK) { + updateMetricField(attachment, INSPECTION_OPEN_FAILURES_COUNT, 1); + response.verdict = ATTACHMENT_VERDICT_ACCEPT; + session_data_p->verdict = TRAFFIC_VERDICT_ACCEPT; + } else { + updateMetricField(attachment, INSPECTION_CLOSE_FAILURES_COUNT, 1); + response.verdict = ATTACHMENT_VERDICT_DROP; + session_data_p->verdict = TRAFFIC_VERDICT_DROP; + response.web_response_data = CreateDefaultBlockPage(attachment, session_data_p->session_id); + } + + write_dbg( + attachment, + response.session_id, + DBG_LEVEL_DEBUG, + "Shared memory is corrupted, returning default fail mode verdict. Session id: %d, verdict: %s", + response.session_id, + response.verdict == ATTACHMENT_VERDICT_ACCEPT ? "accept" : "drop" + ); + return response; +} + +/// +/// @brief Sends a verdict response for a thread timeout condition. +/// +/// @param attachment Pointer to the NanoAttachment struct representing the attachment. +/// @param session_id The session ID associated with the attachment. +/// @param ctx Pointer to the HttpEventThreadCtx struct containing the HTTP event context. +/// @return An AttachmentVerdictResponse struct containing the session ID and verdict. +/// +static AttachmentVerdictResponse +SendThreadTimeoutVerdict(NanoAttachment *attachment, SessionID session_id, HttpEventThreadCtx *ctx) +{ + AttachmentVerdictResponse response = { + .session_id = session_id, + .web_response_data = NULL, + .modifications = NULL + }; + + if (attachment->fail_mode_verdict == NANO_OK) { + response.verdict = ATTACHMENT_VERDICT_ACCEPT; + ctx->session_data_p->verdict = TRAFFIC_VERDICT_ACCEPT; + } else { + response.verdict = ATTACHMENT_VERDICT_DROP; + ctx->session_data_p->verdict = TRAFFIC_VERDICT_DROP; + response.web_response_data = CreateDefaultBlockPage(attachment, session_id); + } + + write_dbg( + attachment, + response.session_id, + DBG_LEVEL_DEBUG, + "Thread failed, returning fail mode verdict. Session id: %d, verdict: %s", + response.session_id, + response.verdict == ATTACHMENT_VERDICT_ACCEPT ? "accept" : "drop" + ); + return response; +} + +/// +/// @brief Finalizes a successful response by determining the verdict based on the HTTP response code. +/// +/// @param attachment Pointer to the NanoAttachment struct representing the attachment. +/// @param session_id The session ID associated with the attachment. +/// @param ctx Pointer to the HttpEventThreadCtx struct containing the HTTP event context. +/// @return An AttachmentVerdictResponse struct containing the session ID and verdict. +/// +static AttachmentVerdictResponse +FinalizeSuccessfulResponse( + NanoAttachment *attachment, + SessionID session_id, + HttpEventThreadCtx *ctx +) +{ + AttachmentVerdictResponse response = { + .session_id = session_id, + .web_response_data = ctx->web_response_data, + .modifications = ctx->modifications + }; + + switch (ctx->session_data_p->verdict) { + case TRAFFIC_VERDICT_INSPECT: + response.verdict = ATTACHMENT_VERDICT_INSPECT; + break; + case TRAFFIC_VERDICT_ACCEPT: + response.verdict = ATTACHMENT_VERDICT_ACCEPT; + break; + case TRAFFIC_VERDICT_DROP: + response.verdict = ATTACHMENT_VERDICT_DROP; + break; + case TRAFFIC_VERDICT_INJECT: + // Not yet supported + response.verdict = ATTACHMENT_VERDICT_INSPECT; + break; + default: + write_dbg( + attachment, + session_id, + DBG_LEVEL_WARNING, + "Unknown verdict %d", + ctx->session_data_p->verdict + ); + response.verdict = ATTACHMENT_VERDICT_INSPECT; + break; + } + + updateMetricField(attachment, INSPECTION_SUCCESSES_COUNT, 1); + + write_dbg( + attachment, + response.session_id, + DBG_LEVEL_DEBUG, + "Finalizing successful response to Session id: %d, verdict: %s", + response.session_id, + AttachmentVerdictToString(response.verdict) + ); + return response; +} + +/// +/// @brief Finalizes an irrelevant response by setting the verdict to accept. +/// +/// @param attachment Pointer to the NanoAttachment struct representing the attachment. +/// @param session_id The session ID associated with the attachment. +/// @return An AttachmentVerdictResponse struct containing the session ID and verdict. +/// +static AttachmentVerdictResponse +FinalizeIrrelevantResponse(NanoAttachment *attachment, SessionID session_id) +{ + AttachmentVerdictResponse response = { + .verdict = ATTACHMENT_VERDICT_ACCEPT, + .session_id = session_id, + .web_response_data = NULL, + .modifications = NULL + }; + + updateMetricField(attachment, IRRELEVANT_VERDICTS_COUNT, 1); + + write_dbg( + attachment, + response.session_id, + DBG_LEVEL_TRACE, + "Finalizing irrelevant response to Session id: %d", + response.session_id + ); + return response; +} + +/// +/// @brief Finalizes a failed response by determining the verdict +/// based on the fail mode verdict associated with the attachment. +/// +/// @param attachment Pointer to the NanoAttachment struct representing the attachment. +/// @param session_id The session ID associated with the attachment. +/// @param ctx Pointer to the HttpEventThreadCtx struct containing the HTTP event context. +/// @return An AttachmentVerdictResponse struct containing the session ID and verdict. +/// +static AttachmentVerdictResponse +FinalizeFailedResponse(NanoAttachment *attachment, SessionID session_id, HttpEventThreadCtx *ctx) +{ + AttachmentVerdictResponse response = { + .session_id = session_id, + .web_response_data = NULL, + .modifications = NULL + }; + + if (attachment->fail_mode_verdict == NANO_OK) { + updateMetricField(attachment, INSPECTION_OPEN_FAILURES_COUNT, 1); + response.verdict = ATTACHMENT_VERDICT_ACCEPT; + ctx->session_data_p->verdict = TRAFFIC_VERDICT_ACCEPT; + } else { + updateMetricField(attachment, INSPECTION_CLOSE_FAILURES_COUNT, 1); + response.verdict = ATTACHMENT_VERDICT_DROP; + ctx->session_data_p->verdict = TRAFFIC_VERDICT_DROP; + response.web_response_data = CreateDefaultBlockPage(attachment, session_id); + } + + write_dbg( + attachment, + response.session_id, + DBG_LEVEL_TRACE, + "Handling Failure with fail %s mode", + response.verdict == ATTACHMENT_VERDICT_ACCEPT ? "open" : "close" + ); + return response; +} + +AttachmentVerdictResponse +SendRequestFilter(NanoAttachment *attachment, AttachmentData *data) +{ + HttpEventThreadCtx ctx; + HttpSessionData *session_data_p = data->session_data; + SessionID session_id = session_data_p->session_id; + int res; + + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "Request filter handling session ID: %d", + session_id + ); + + if (handle_shmem_corruption(attachment) == NANO_ERROR) { + return SendCorruptMemoryVerdict(attachment, session_data_p); + } + + write_dbg(attachment, session_id, DBG_LEVEL_DEBUG, "spawn SendRequestFilterThread"); + res = NanoRunInThreadTimeout( + attachment, + data, + SendRequestFilterThread, + (void *)&ctx, + attachment->req_header_thread_timeout_msec, + "SendRequestFilterThread", + REQUEST + ); + + if (!res) { + updateMetricField(attachment, REQ_METADATA_THREAD_TIMEOUT, 1); + return SendThreadTimeoutVerdict(attachment, session_id, &ctx); + } + + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "finished SendMetadataThread successfully. res=%d", + ctx.res + ); + + if (ctx.res == NANO_DECLINED) { + return FinalizeIrrelevantResponse(attachment, session_id); + } + + if (ctx.res != NANO_HTTP_FORBIDDEN && ctx.res != NANO_OK) { + return FinalizeFailedResponse(attachment, session_id, &ctx); + } + + return FinalizeSuccessfulResponse(attachment, session_id, &ctx); +} + +AttachmentVerdictResponse +SendMetadata(NanoAttachment *attachment, AttachmentData *data) +{ + HttpEventThreadCtx ctx; + HttpSessionData *session_data_p = data->session_data; + SessionID session_id = session_data_p->session_id; + int res; + + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "Request start handling session ID: %d", + session_id + ); + + if (handle_shmem_corruption(attachment) == NANO_ERROR) { + return SendCorruptMemoryVerdict(attachment, session_data_p); + } + + write_dbg(attachment, session_id, DBG_LEVEL_DEBUG, "spawn SendMetadataThread"); + res = NanoRunInThreadTimeout( + attachment, + data, + SendMetadataThread, + (void *)&ctx, + attachment->req_start_thread_timeout_msec, + "SendMetadataThread", + REQUEST + ); + + if (!res) { + updateMetricField(attachment, REQ_METADATA_THREAD_TIMEOUT, 1); + return SendThreadTimeoutVerdict(attachment, session_id, &ctx); + } + + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "finished SendMetadataThread successfully. res=%d", + ctx.res + ); + + if (ctx.res == NANO_DECLINED) { + return FinalizeIrrelevantResponse(attachment, session_id); + } + + if (ctx.res != NANO_HTTP_FORBIDDEN && ctx.res != NANO_OK) { + return FinalizeFailedResponse(attachment, session_id, &ctx); + } + + return FinalizeSuccessfulResponse(attachment, session_id, &ctx); +} + +AttachmentVerdictResponse +SendRequestHeaders(NanoAttachment *attachment, AttachmentData *data) +{ + HttpEventThreadCtx ctx; + HttpSessionData *session_data_p = data->session_data; + SessionID session_id = session_data_p->session_id; + int res; + + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "Request header handling session ID: %d", + session_id + ); + + if (handle_shmem_corruption(attachment) == NANO_ERROR) { + return SendCorruptMemoryVerdict(attachment, session_data_p); + } + + write_dbg(attachment, session_id, DBG_LEVEL_DEBUG, "spawn SendRequestHeadersThread"); + res = NanoRunInThreadTimeout( + attachment, + data, + SendRequestHeadersThread, + (void *)&ctx, + attachment->req_header_thread_timeout_msec, + "SendRequestHeadersThread", + REQUEST + ); + + if (!res) { + updateMetricField(attachment, REQ_HEADER_THREAD_TIMEOUT, 1); + return SendThreadTimeoutVerdict(attachment, session_id, &ctx); + } + + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "finished SendRequestHeadersThread successfully. res=%d", + ctx.res + ); + + if (session_data_p->verdict == TRAFFIC_VERDICT_DELAYED) { + write_dbg(attachment, session_id, DBG_LEVEL_DEBUG, "spawn SendDelayedVerdictRequestThread"); + res = NanoRunInThreadTimeout( + attachment, + data, + SendDelayedVerdictRequestThread, + (void *)&ctx, + attachment->waiting_for_verdict_thread_timeout_msec, + "SendDelayedVerdictRequestThread", + REQUEST + ); + if (!res) { + updateMetricField(attachment, HOLD_THREAD_TIMEOUT, 1); + return SendThreadTimeoutVerdict(attachment, session_id, &ctx); + } + + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "finished SendDelayedVerdictRequestThread successfully. res=%d", + ctx.res + ); + } + + if (ctx.res != NANO_HTTP_FORBIDDEN && ctx.res != NANO_OK) { + return FinalizeFailedResponse(attachment, session_id, &ctx); + } + + return FinalizeSuccessfulResponse(attachment, session_id, &ctx); +} + +AttachmentVerdictResponse +SendResponseHeaders(NanoAttachment *attachment, AttachmentData *data) +{ + HttpEventThreadCtx ctx; + HttpSessionData *session_data_p = data->session_data; + SessionID session_id = session_data_p->session_id; + int res; + + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "Response header handling session ID: %d", + session_id + ); + + if (handle_shmem_corruption(attachment) == NANO_ERROR) { + return SendCorruptMemoryVerdict(attachment, session_data_p); + } + + write_dbg(attachment, session_id, DBG_LEVEL_DEBUG, "spawn SendResponseHeadersThread"); + res = NanoRunInThreadTimeout( + attachment, + data, + SendResponseHeadersThread, + (void *)&ctx, + attachment->res_header_thread_timeout_msec, + "SendResponseHeadersThread", + RESPONSE + ); + + if (!res) { + updateMetricField(attachment, RES_HEADER_THREAD_TIMEOUT, 1); + return SendThreadTimeoutVerdict(attachment, session_id, &ctx); + } + + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "finished SendResponseHeadersThread successfully. res=%d", + ctx.res + ); + + if (ctx.res != NANO_HTTP_FORBIDDEN && ctx.res != NANO_OK) { + return FinalizeFailedResponse(attachment, session_id, &ctx); + } + + return FinalizeSuccessfulResponse(attachment, session_id, &ctx); +} + +AttachmentVerdictResponse +SendRequestBody(NanoAttachment *attachment, AttachmentData *data) +{ + HttpEventThreadCtx ctx; + HttpSessionData *session_data_p = data->session_data; + SessionID session_id = session_data_p->session_id; + int res; + + write_dbg(attachment, session_id, DBG_LEVEL_DEBUG, "Request body handling session ID: %d", session_id); + + if (handle_shmem_corruption(attachment) == NANO_ERROR) { + return SendCorruptMemoryVerdict(attachment, session_data_p); + } + + write_dbg(attachment, session_id, DBG_LEVEL_DEBUG, "spawn SendRequestBodyThread"); + res = NanoRunInThreadTimeout( + attachment, + data, + SendRequestBodyThread, + (void *)&ctx, + attachment->req_body_thread_timeout_msec, + "SendRequestBodyThread", + REQUEST + ); + + if (!res) { + updateMetricField(attachment, REQ_BODY_THREAD_TIMEOUT, 1); + return SendThreadTimeoutVerdict(attachment, session_id, &ctx); + } + + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "finished SendRequestBodyThread successfully. res=%d", + ctx.res + ); + + if (session_data_p->verdict == TRAFFIC_VERDICT_DELAYED) { + write_dbg(attachment, session_id, DBG_LEVEL_DEBUG, "spawn SendDelayedVerdictRequestThread"); + res = NanoRunInThreadTimeout( + attachment, + data, + SendDelayedVerdictRequestThread, + (void *)&ctx, + attachment->waiting_for_verdict_thread_timeout_msec, + "SendDelayedVerdictRequestThread", + REQUEST + ); + if (!res) { + updateMetricField(attachment, HOLD_THREAD_TIMEOUT, 1); + return SendThreadTimeoutVerdict(attachment, session_id, &ctx); + } + + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "finished SendDelayedVerdictRequestThread successfully. res=%d", + ctx.res + ); + } + + if (ctx.res != NANO_HTTP_FORBIDDEN && ctx.res != NANO_OK) { + return FinalizeFailedResponse(attachment, session_id, &ctx); + } + + return FinalizeSuccessfulResponse(attachment, session_id, &ctx); +} + +AttachmentVerdictResponse +SendResponseBody(NanoAttachment *attachment, AttachmentData *data) +{ + HttpEventThreadCtx ctx; + HttpSessionData *session_data_p = data->session_data; + SessionID session_id = session_data_p->session_id; + int res; + + write_dbg(attachment, session_id, DBG_LEVEL_DEBUG, "Response body handling session ID: %d", session_id); + + if (handle_shmem_corruption(attachment) == NANO_ERROR) { + return SendCorruptMemoryVerdict(attachment, session_data_p); + } + + write_dbg(attachment, session_id, DBG_LEVEL_DEBUG, "spawn SendResponseBodyThread"); + res = NanoRunInThreadTimeout( + attachment, + data, + SendResponseBodyThread, + (void *)&ctx, + attachment->res_body_thread_timeout_msec, + "SendResponseBodyThread", + RESPONSE + ); + + if (!res) { + updateMetricField(attachment, RES_BODY_THREAD_TIMEOUT, 1); + return SendThreadTimeoutVerdict(attachment, session_id, &ctx); + } + + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "finished SendResponseBodyThread successfully. res=%d", + ctx.res + ); + + if (ctx.res != NANO_HTTP_FORBIDDEN && ctx.res != NANO_OK) { + return FinalizeFailedResponse(attachment, session_id, &ctx); + } + + return FinalizeSuccessfulResponse(attachment, session_id, &ctx); +} + +AttachmentVerdictResponse +SendRequestEnd(NanoAttachment *attachment, AttachmentData *data) +{ + HttpEventThreadCtx ctx; + HttpSessionData *session_data_p = data->session_data; + SessionID session_id = session_data_p->session_id; + int res; + + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "Request end handling session ID: %d", + session_id + ); + + if (handle_shmem_corruption(attachment) == NANO_ERROR) { + return SendCorruptMemoryVerdict(attachment, session_data_p); + } + + write_dbg(attachment, session_id, DBG_LEVEL_DEBUG, "spawn SendRequestEndThread"); + res = NanoRunInThreadTimeout( + attachment, + data, + SendRequestEndThread, + (void *)&ctx, + attachment->req_header_thread_timeout_msec, + "SendRequestEndThread", + REQUEST + ); + + if (!res) { + updateMetricField(attachment, REQ_END_THREAD_TIMEOUT, 1); + return SendThreadTimeoutVerdict(attachment, session_id, &ctx); + } + + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "finished SendRequestEndThread successfully. res=%d", + ctx.res + ); + + if (ctx.res != NANO_HTTP_FORBIDDEN && ctx.res != NANO_OK) { + return FinalizeFailedResponse(attachment, session_id, &ctx); + } + + return FinalizeSuccessfulResponse(attachment, session_id, &ctx); +} + +AttachmentVerdictResponse +SendResponseEnd(NanoAttachment *attachment, AttachmentData *data) +{ + HttpEventThreadCtx ctx; + HttpSessionData *session_data_p = data->session_data; + SessionID session_id = session_data_p->session_id; + int res; + + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "Response end handling session ID: %d", + session_id + ); + + if (handle_shmem_corruption(attachment) == NANO_ERROR) { + return SendCorruptMemoryVerdict(attachment, session_data_p); + } + + write_dbg(attachment, session_id, DBG_LEVEL_DEBUG, "spawn SendResponseEndThread"); + res = NanoRunInThreadTimeout( + attachment, + data, + SendResponseEndThread, + (void *)&ctx, + attachment->req_header_thread_timeout_msec, + "SendResponseEndThread", + RESPONSE + ); + + if (!res) { + updateMetricField(attachment, RES_END_THREAD_TIMEOUT, 1); + return SendThreadTimeoutVerdict(attachment, session_id, &ctx); + } + + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "finished SendResponseEndThread successfully. res=%d", + ctx.res + ); + + if (ctx.res != NANO_HTTP_FORBIDDEN && ctx.res != NANO_OK) { + return FinalizeFailedResponse(attachment, session_id, &ctx); + } + + return FinalizeSuccessfulResponse(attachment, session_id, &ctx); +} + +NanoCommunicationResult +SendMetricData(NanoAttachment *attachment) +{ + HttpEventThreadCtx ctx; + int res; + + write_dbg( + attachment, + 0, + DBG_LEVEL_DEBUG, + "Sending metric data saved in worker ID: %d", + attachment->worker_id + ); + + if (handle_shmem_corruption(attachment) == NANO_ERROR) { + write_dbg( + attachment, + 0, + DBG_LEVEL_DEBUG, + "Failed to send metric data, shmem corruption Worker ID: %d", + attachment->worker_id + ); + return NANO_ERROR; + } + + res = NanoRunInThreadTimeout( + attachment, + NULL, + SendMetricToServiceThread, + (void *)&ctx, + attachment->metric_timeout_timeout, + "SendMetricToServiceThread", + METRICS + ); + + if (!res) { + write_dbg( + attachment, + 0, + DBG_LEVEL_DEBUG, + "Thread timeout while sending metric data from worker ID: %d", + attachment->worker_id + ); + return NANO_ERROR; + } + + return NANO_OK; +} diff --git a/attachments/nano_attachment/nano_attachment_sender.h b/attachments/nano_attachment/nano_attachment_sender.h new file mode 100644 index 0000000..5e0b68a --- /dev/null +++ b/attachments/nano_attachment/nano_attachment_sender.h @@ -0,0 +1,137 @@ +#ifndef __NANO_ATTACHMENT_SENDER_H__ +#define __NANO_ATTACHMENT_SENDER_H__ + +#include "nano_attachment_common.h" +#include "nano_initializer.h" + +/// +/// @brief Sends start request data to the nano service. +/// +/// This function handles the sending of starting meta data, request headers and end request to the nano service. +/// It creates a new thread to perform the sending operation, ensuring that the main execution flow +/// is not blocked. It also handles potential errors and timeouts that may occur during +/// the sending process. +/// +/// @param attachment A pointer to the NanoAttachment structure. +/// @param data A pointer to AttachmentData structure containing the data to send and the session data. +/// +/// @return An AttachmentVerdictResponse structure indicating the outcome of the operation. +/// +AttachmentVerdictResponse SendRequestFilter(NanoAttachment *attachment, AttachmentData *data); + +/// +/// @brief Sends start request data to the nano service. +/// +/// This function handles the sending of starting meta data to the nano service. It creates +/// a new thread to perform the sending operation, ensuring that the main execution flow +/// is not blocked. It also handles potential errors and timeouts that may occur during +/// the sending process. +/// +/// @param attachment A pointer to the NanoAttachment structure. +/// @param data A pointer to AttachmentData structure containing the data to send and the session data. +/// +/// @return An AttachmentVerdictResponse structure indicating the outcome of the operation. +/// +AttachmentVerdictResponse SendMetadata(NanoAttachment *attachment, AttachmentData *data); + +/// +/// @brief Sends request headers to the nano service. +/// +/// This function handles the sending of request headers to the nano service. It creates +/// a new thread to perform the sending operation, ensuring that the main execution flow +/// is not blocked. It also handles potential errors and timeouts that may occur during +/// the sending process. +/// +/// @param attachment A pointer to the NanoAttachment structure. +/// @param data A pointer to AttachmentData structure containing the headers to send and the session data. +/// +/// @return An AttachmentVerdictResponse structure indicating the outcome of the operation. +/// +AttachmentVerdictResponse SendRequestHeaders(NanoAttachment *attachment, AttachmentData *data); + +/// +/// @brief Sends response headers to the nano service. +/// +/// This function handles the sending of response headers to the nano service. It creates +/// a new thread to perform the sending operation, ensuring that the main execution flow +/// is not blocked. It also handles potential errors and timeouts that may occur during +/// the sending process. +/// +/// @param attachment A pointer to the NanoAttachment structure. +/// @param data A pointer to AttachmentData structure containing the headers to send and the session data. +/// +/// @return An AttachmentVerdictResponse structure indicating the outcome of the operation. +/// +AttachmentVerdictResponse SendResponseHeaders(NanoAttachment *attachment, AttachmentData *data); + +/// +/// @brief Sends request body to the nano service. +/// +/// This function handles the sending of request body to the nano service. It creates +/// a new thread to perform the sending operation, ensuring that the main execution flow +/// is not blocked. It also handles potential errors and timeouts that may occur during +/// the sending process. +/// +/// @param attachment A pointer to the NanoAttachment structure. +/// @param data A pointer to AttachmentData structure containing the body to send and the session data. +/// +/// @return An AttachmentVerdictResponse structure indicating the outcome of the operation. +/// +AttachmentVerdictResponse SendRequestBody(NanoAttachment *attachment, AttachmentData *data); + +/// +/// @brief Sends response body to the nano service. +/// +/// This function handles the sending of response body to the nano service. It creates +/// a new thread to perform the sending operation, ensuring that the main execution flow +/// is not blocked. It also handles potential errors and timeouts that may occur during +/// the sending process. +/// +/// @param attachment A pointer to the NanoAttachment structure. +/// @param data A pointer to AttachmentData structure containing the body to send and the session data. +/// +/// @return An AttachmentVerdictResponse structure indicating the outcome of the operation. +/// +AttachmentVerdictResponse SendResponseBody(NanoAttachment *attachment, AttachmentData *data); + +/// +/// @brief Sends end request signal to the nano service. +/// +/// This function handles the sending a request end signal to the nano service. It creates +/// a new thread to perform the sending operation, ensuring that the main execution flow +/// is not blocked. It also handles potential errors and timeouts that may occur during +/// the sending process. +/// +/// @param attachment A pointer to the NanoAttachment structure. +/// @param data A pointer to AttachmentData structure containing the necessery data to send and the session data. +/// +/// @return An AttachmentVerdictResponse structure indicating the outcome of the operation. +/// +AttachmentVerdictResponse SendRequestEnd(NanoAttachment *attachment, AttachmentData *data); + +/// +/// @brief Sends end response signal to the nano service. +/// +/// This function handles the sending a response end signal to the nano service. It creates +/// a new thread to perform the sending operation, ensuring that the main execution flow +/// is not blocked. It also handles potential errors and timeouts that may occur during +/// the sending process. +/// +/// @param attachment A pointer to the NanoAttachment structure. +/// @param data A pointer to AttachmentData structure containing the necessery data to send and the session data. +/// +/// @return An AttachmentVerdictResponse structure indicating the outcome of the operation. +/// +AttachmentVerdictResponse SendResponseEnd(NanoAttachment *attachment, AttachmentData *data); + +/// +/// @brief Sends metric data to the nano service and resets it on the attachment. +/// +/// @param attachment A pointer to the NanoAttachment structure that contains metrics data. +/// +/// @return An NanoCommunication enum indicating the outcome of the operation. +/// NANO_OK if the operation was successful, NANO_ERROR otherwise. +/// +NanoCommunicationResult SendMetricData(NanoAttachment *attachment); + +#endif // __NANO_ATTACHMENT_SENDER_H__ diff --git a/attachments/nano_attachment/nano_attachment_sender_thread.c b/attachments/nano_attachment/nano_attachment_sender_thread.c new file mode 100644 index 0000000..1f573ec --- /dev/null +++ b/attachments/nano_attachment/nano_attachment_sender_thread.c @@ -0,0 +1,330 @@ +#include "nano_attachment_sender_thread.h" + +#include +#include + +#include "nano_initializer.h" +#include "nano_attachment_sender.h" +#include "nano_attachment_common.h" +#include "nano_attachment_io.h" +#include "nano_utils.h" +#include "nano_compression.h" + +static HttpHeaderData * +get_http_header(HttpHeaders *http_headers, const char *header_name) { + size_t i; + for (i = 0; i < http_headers->headers_count; ++i) { + if (strcmp((char*)http_headers->data[i].key.data, header_name) == 0) { + return &http_headers->data[i]; + } + } + return NULL; +} + +static void +set_response_content_encoding( + NanoAttachment *attachment, + HttpSessionData *session_data_p, + HttpHeaders *http_headers +) +{ + write_dbg( + attachment, + session_data_p->session_id, + DBG_LEVEL_TRACE, + "Determining response body's content encoding" + ); + + const HttpHeaderData *content_encoding = get_http_header(http_headers, "content-encoding"); + + if (content_encoding == NULL) { + session_data_p->response_data.compression_type = NO_COMPRESSION; + return; + } + + if (strcmp((char*)content_encoding->value.data, "gzip") == 0) { + session_data_p->response_data.compression_type = GZIP; + } else if (strcmp((char*)content_encoding->value.data, "deflate") == 0) { + session_data_p->response_data.compression_type = ZLIB; + } else if (strcmp((char*)content_encoding->value.data, "identity") == 0) { + session_data_p->response_data.compression_type = NO_COMPRESSION; + } else { + write_dbg( + attachment, + session_data_p->session_id, + DBG_LEVEL_WARNING, + "Unsupported response content encoding: %.*s", + content_encoding->value.data + ); + session_data_p->response_data.compression_type = NO_COMPRESSION; + } +} + +void +init_thread_ctx(HttpEventThreadCtx *ctx, NanoAttachment *attachment, AttachmentData *data) +{ + ctx->attachment = attachment; + ctx->data = data; + ctx->session_data_p = (data == NULL) ? NULL : data->session_data; + ctx->res = NANO_OK; + ctx->web_response_data = NULL; + ctx->modifications = NULL; +} + +void * +RegistrationCommSocketThread(void *_ctx) +{ + HttpEventThreadCtx *ctx = (HttpEventThreadCtx *)_ctx; + NanoAttachment *attachment = ctx->attachment; + + ctx->res = connect_to_comm_socket(attachment); + + return NULL; +} + +void * +RegistrationSocketThread(void *_ctx) +{ + HttpEventThreadCtx *ctx = (HttpEventThreadCtx *)_ctx; + NanoAttachment *attachment = ctx->attachment; + + ctx->res = connect_to_registration_socket(attachment); + return 0; +} + +void * +SendRequestFilterThread(void *_ctx) +{ + HttpEventThreadCtx *ctx = (HttpEventThreadCtx *)_ctx; + + HttpRequestFilterData *start_data = (HttpRequestFilterData*)ctx->data->data; + HttpMetaData *metadata = start_data->meta_data; + HttpHeaders *headers = start_data->req_headers; + bool contains_body = start_data->contains_body; + NanoAttachment *attachment = ctx->attachment; + HttpSessionData *session_data_p = ctx->session_data_p; + bool is_verdict_requested = false; + + nano_metadata_sender( + attachment, + metadata, + ctx, + session_data_p->session_id, + &session_data_p->remaining_messages_to_reply, + is_verdict_requested + ); + + nano_header_sender( + attachment, + headers, + ctx, + REQUEST_HEADER, + session_data_p->session_id, + &session_data_p->remaining_messages_to_reply, + is_verdict_requested + ); + + if (!contains_body) { + nano_end_transaction_sender( + attachment, + REQUEST_END, + ctx, + session_data_p->session_id, + &session_data_p->remaining_messages_to_reply + ); + } + + return NULL; +} + +void * +SendMetadataThread(void *_ctx) +{ + HttpEventThreadCtx *ctx = (HttpEventThreadCtx *)_ctx; + HttpMetaData *metadata = (HttpMetaData*)ctx->data->data; + NanoAttachment *attachment = ctx->attachment; + HttpSessionData *session_data_p = ctx->session_data_p; + bool is_verdict_requested = false; + + nano_metadata_sender( + attachment, + metadata, + ctx, + session_data_p->session_id, + &session_data_p->remaining_messages_to_reply, + is_verdict_requested + ); + + return NULL; +} + +void * +SendRequestHeadersThread(void *_ctx) +{ + HttpEventThreadCtx *ctx = (HttpEventThreadCtx *)_ctx; + HttpHeaders *headers = (HttpHeaders*)ctx->data->data; + NanoAttachment *attachment = ctx->attachment; + HttpSessionData *session_data_p = ctx->session_data_p; + bool is_verdict_requested = false; + + nano_header_sender( + attachment, + headers, + ctx, + REQUEST_HEADER, + session_data_p->session_id, + &session_data_p->remaining_messages_to_reply, + is_verdict_requested + ); + + return NULL; +} + +void * +SendResponseHeadersThread(void *_ctx) +{ + HttpEventThreadCtx *ctx = (HttpEventThreadCtx *)_ctx; + ResHttpHeaders *headers = (ResHttpHeaders*)ctx->data->data; + NanoAttachment *attachment = ctx->attachment; + HttpSessionData *session_data_p = ctx->session_data_p; + HttpHeaders *http_headers = headers->headers; + bool is_verdict_requested = true; + + nano_send_response_code( + attachment, + headers->response_code, + ctx, + session_data_p->session_id, + &session_data_p->remaining_messages_to_reply + ); + + nano_send_response_content_length( + attachment, + headers->content_length, + ctx, + session_data_p->session_id, + &session_data_p->remaining_messages_to_reply + ); + + set_response_content_encoding( + attachment, + session_data_p, + http_headers + ); + + nano_header_sender( + attachment, + http_headers, + ctx, + RESPONSE_HEADER, + session_data_p->session_id, + &session_data_p->remaining_messages_to_reply, + is_verdict_requested + ); + + return NULL; +} + +void * +SendRequestBodyThread(void *_ctx) +{ + HttpEventThreadCtx *ctx = (HttpEventThreadCtx *)_ctx; + HttpBody *bodies = (HttpBody*)ctx->data->data; + NanoAttachment *attachment = ctx->attachment; + HttpSessionData *session_data_p = ctx->session_data_p; + + nano_body_sender( + attachment, + bodies, + ctx, + REQUEST_BODY, + session_data_p->session_id, + &session_data_p->remaining_messages_to_reply + ); + + return NULL; +} + +void * +SendResponseBodyThread(void *_ctx) +{ + HttpEventThreadCtx *ctx = (HttpEventThreadCtx *)_ctx; + HttpBody *bodies = (HttpBody*)ctx->data->data; + NanoAttachment *attachment = ctx->attachment; + HttpSessionData *session_data_p = ctx->session_data_p; + + nano_body_sender( + attachment, + bodies, + ctx, + RESPONSE_BODY, + session_data_p->session_id, + &session_data_p->remaining_messages_to_reply + ); + + return NULL; +} + +void * +SendRequestEndThread(void *_ctx) +{ + HttpEventThreadCtx *ctx = (HttpEventThreadCtx *)_ctx; + NanoAttachment *attachment = ctx->attachment; + HttpSessionData *session_data_p = ctx->session_data_p; + + nano_end_transaction_sender( + attachment, + REQUEST_END, + ctx, + session_data_p->session_id, + &session_data_p->remaining_messages_to_reply + ); + + return NULL; +} + +void * +SendResponseEndThread(void *_ctx) +{ + HttpEventThreadCtx *ctx = (HttpEventThreadCtx *)_ctx; + NanoAttachment *attachment = ctx->attachment; + HttpSessionData *session_data_p = ctx->session_data_p; + + nano_end_transaction_sender( + attachment, + RESPONSE_END, + ctx, + session_data_p->session_id, + &session_data_p->remaining_messages_to_reply + ); + + return NULL; +} + +void * +SendDelayedVerdictRequestThread(void *_ctx) +{ + HttpEventThreadCtx *ctx = (HttpEventThreadCtx *)_ctx; + NanoAttachment *attachment = ctx->attachment; + HttpSessionData *session_data_p = ctx->session_data_p; + + nano_request_delayed_verdict( + attachment, + ctx, + session_data_p->session_id, + &session_data_p->remaining_messages_to_reply + ); + + return NULL; +} + +void * +SendMetricToServiceThread(void *_data) +{ + HttpEventThreadCtx *ctx = (HttpEventThreadCtx *)_data; + NanoAttachment *attachment = ctx->attachment; + + nano_send_metric_data_sender(attachment); + + return NULL; +} diff --git a/attachments/nano_attachment/nano_attachment_sender_thread.h b/attachments/nano_attachment/nano_attachment_sender_thread.h new file mode 100644 index 0000000..e487ae4 --- /dev/null +++ b/attachments/nano_attachment/nano_attachment_sender_thread.h @@ -0,0 +1,178 @@ +#ifndef __NANO_ATTACHMENT_SENDER_THREAD_H__ +#define __NANO_ATTACHMENT_SENDER_THREAD_H__ + +#include "nano_attachment_common.h" +#include "nano_initializer.h" + +/// @struct HttpEventThreadCtx +/// @brief Holds all the information needed to communicate with the attachment service. +typedef struct HttpEventThreadCtx +{ + NanoAttachment *attachment; ///< NanoAttachment. + AttachmentData *data; ///< Attachment data. + HttpSessionData *session_data_p; ///< Provided session data. + + /// Connection results with the attachment service + /// - #NANO_OK + /// - #NANO_ERROR + NanoCommunicationResult res; + + WebResponseData *web_response_data; ///< Web response data. + NanoHttpModificationList *modifications; ///< Context's modification. +} HttpEventThreadCtx; + +/// +/// @brief Initializes a thread context structure for an NANO event thread. +/// +/// This function initializes a thread context structure with the provided data +/// and default values for other fields. +/// +/// @param ctx A pointer to a struct HttpEventThreadCtx structure representing the thread context to initialize. +/// @param attachment A pointer to a NanoAttachment structure representing the attachment data for the thread. +/// @param data A pointer to an AttachmentData structure representing the attachment data for the thread. +/// +void +init_thread_ctx(HttpEventThreadCtx *ctx, NanoAttachment *attachment, AttachmentData *data); + +/// +/// @brief Connect attachment communication socket to the nano service. +/// +/// @param _ctx A pointer to the HttpEventThreadCtx context. +/// +/// @return NULL. +/// +void * RegistrationCommSocketThread(void *_ctx); + +/// +/// @brief Connect attachment to registration socket to the nano service. +/// +/// @param _ctx A pointer to the HttpEventThreadCtx context. +/// +/// @return NULL. +/// +void * RegistrationSocketThread(void *_ctx); + +/// +/// @brief Sends request start data to the nano service. +/// +/// This thread function sends metadata to start a request interaction with the nano service. +/// +/// @param _ctx A pointer to the HttpEventThreadCtx context. +/// +/// @return NULL. +/// +void * SendMetadataThread(void *_ctx); + +/// +/// @brief Sends request headers to the nano service. +/// +/// This thread function sends request headers to the nano service using the provided +/// HttpEventThreadCtx context. It updates the session data and handles any +/// errors that occur during the header sending process. +/// +/// @param _ctx A pointer to the HttpEventThreadCtx context. +/// +/// @return NULL. +/// +void * SendRequestHeadersThread(void *_ctx); + +/// +/// @brief Sends response headers to the nano service. +/// +/// This thread function sends response headers to the nano service using the provided +/// HttpEventThreadCtx context. It updates the session data and handles any +/// errors that occur during the header sending process. +/// +/// @param _ctx A pointer to the HttpEventThreadCtx context. +/// +/// @return NULL. +/// +void * SendResponseHeadersThread(void *_ctx); + +/// +/// @brief Sends request body to the nano service. +/// +/// This thread function sends request body to the nano service using the provided +/// HttpEventThreadCtx context. It updates the session data and handles any +/// errors that occur during the body sending process. +/// +/// @param _ctx A pointer to the HttpEventThreadCtx context. +/// +/// @return NULL. +/// +void * SendRequestBodyThread(void *_ctx); + +/// +/// @brief Sends response body to the nano service. +/// +/// This thread function sends response body to the nano service using the provided +/// HttpEventThreadCtx context. It updates the session data and handles any +/// errors that occur during the body sending process. +/// +/// @param _ctx A pointer to the HttpEventThreadCtx context. +/// +/// @return NULL. +/// +void * SendResponseBodyThread(void *_ctx); + +/// +/// @brief Sends request end data to the nano service. +/// +/// This thread function sends signal to the nano service that the response has ended +/// and with it the whole session transaction. +/// +/// @param _ctx A pointer to the HttpEventThreadCtx context. +/// +/// @return NULL. +/// +void * SendRequestEndThread(void *_ctx); + +/// +/// @brief Sends response end data to the nano service. +/// +/// This thread function sends signal to the nano service that the response has ended +/// and with it the whole session transaction. +/// +/// @param _ctx A pointer to the HttpEventThreadCtx context. +/// +/// @return NULL. +/// +void * SendResponseEndThread(void *_ctx); + + +/// +/// @brief Thread function to send request filters. +/// +/// This thread function sends metadata, request headers and end request +/// to the nano service using the provided HttpEventThreadCtx context. +/// It updates the session data and handles any errors that occur during the header sending process. +/// +/// @param _ctx A pointer to the HttpEventThreadCtx context. +/// +/// @return NULL +/// +void * SendRequestFilterThread(void *_ctx); + +/// +/// @brief Send query for requesting delayed data verdict. +/// +/// This thread function sends a delayed data query to the service and waits for the response. +/// +/// @param _ctx A pointer to the HttpEventThreadCtx context. +/// +/// @return NULL. +/// +void * SendDelayedVerdictRequestThread(void *_ctx); + +/// +/// @brief Send data metric fo the service. +/// +/// This thread function sends data metric to the service and resets it. +/// +/// @param _ctx A pointer to the HttpEventThreadCtx context. +/// +/// @return NULL. +/// +void * SendMetricToServiceThread(void *_data); + +#endif // __NANO_ATTACHMENT_SENDER_THREAD_H__ diff --git a/attachments/nano_attachment/nano_attachment_thread.c b/attachments/nano_attachment/nano_attachment_thread.c new file mode 100644 index 0000000..0a118e8 --- /dev/null +++ b/attachments/nano_attachment/nano_attachment_thread.c @@ -0,0 +1,233 @@ +#include "nano_attachment_thread.h" + +#include +#include +#include +#include +#include +#include + +#include "nano_attachment_sender.h" +#include "nano_attachment_sender_thread.h" +#include "nano_utils.h" +#include "nano_attachment_metric.h" + +/// +/// @brief Calculate the processing time of a transaction and update the session data accordingly. +/// +/// @param session_data_p Pointer to the HttpSessionData struct containing session data. +/// @param thread_time_begin Pointer to the timespec struct representing the start time of the transaction. +/// @param transaction_type The type of transaction (REQUEST or RESPONSE). +/// +static void +CalcProcessingTime( + HttpSessionData *session_data_p, + struct timespec *thread_time_begin, + TransactionType transaction_type +) +{ + struct timespec thread_time_end; + + if (transaction_type == START || transaction_type == METRICS) return; + + clock_gettime(CLOCK_REALTIME, &thread_time_end); + + double begin_usec = (thread_time_begin->tv_sec * 1000 * 1000) + (thread_time_begin->tv_nsec / 1000); + double end_usec = (thread_time_end.tv_sec * 1000 * 1000) + (thread_time_end.tv_nsec / 1000); + double elapsed = end_usec - begin_usec; + if (transaction_type == REQUEST) { + session_data_p->req_proccesing_time += elapsed; + } else { + session_data_p->res_proccesing_time += elapsed; + } +} + +/// +/// @brief runs the provided routine with the arguments in a non thread and without a timeout. +/// @param[in, out] attachment A pointer to the NanoAttachment struct. +/// @param[in] session_id The session ID. +/// @param[in, out] thread_func A pointer to the provided routine to run in a thread. +/// @param[in, out] arg Routine's arguments. +/// @param[in, out] func_name Called thread timeout. +/// @returns 1 +/// +int +NanoRunInWithoutThreadTimeout( + NanoAttachment *attachment, + SessionID session_id, + CpThreadRoutine thread_func, + void *arg, + char *func_name +) +{ + write_dbg( + attachment, + session_id, + DBG_LEVEL_TRACE, + "Executing cb in blocking mode, fn=%s", + func_name + ); + + thread_func(arg); + + return 1; +} + +int +NanoRunInThreadTimeout( + NanoAttachment *attachment, + AttachmentData *data, + CpThreadRoutine thread_func, + void *arg, + int timeout_msecs, + char *func_name, + TransactionType transaction_type +) +{ + int status = 0; + int ret = 0; + void *res = NULL; + pthread_t thread; + struct timespec ts; + struct timespec thread_time_begin; + uint32_t session_id; + HttpEventThreadCtx *ctx = (HttpEventThreadCtx *)arg; + + session_id = (data == NULL) ? attachment->worker_id : data->session_data->session_id; + init_thread_ctx(ctx, attachment, data); + + if (attachment->inspection_mode == NO_THREAD) { + return NanoRunInWithoutThreadTimeout(attachment, session_id, thread_func, arg, func_name); + } + + if (clock_gettime(CLOCK_REALTIME, &thread_time_begin) == -1) { + updateMetricField(attachment, THREAD_FAILURE, 1); + write_dbg( + attachment, + session_id, + DBG_LEVEL_ERROR, + "clock_gettime(CLOCK_REALTIME) failed. Status: %s", + strerror(errno) + ); + return 0; + } + + /// Runs the routine in a dedicated thread. + write_dbg( + attachment, + session_id, + DBG_LEVEL_TRACE, + "Executing cb in dedicated thread, fn=%s", + func_name + ); + + if (pthread_create(&thread, NULL, thread_func, arg) != 0) { + updateMetricField(attachment, THREAD_FAILURE, 1); + write_dbg( + attachment, + session_id, + DBG_LEVEL_TRACE, + "pthread_create failed with errno=%d, fn=%s", + errno, + func_name + ); + return 0; + } + + if (attachment->inspection_mode == BLOCKING_THREAD) { + // Runs the function in a blocking thread. + status = pthread_join(thread, &res); + write_dbg( + attachment, + session_id, + DBG_LEVEL_TRACE, + "pthread_join returned from blocking call. status=%d, fn=%s", + status, + func_name + ); + return status == 0; + } + + if (clock_gettime(CLOCK_REALTIME, &ts) == -1) { + updateMetricField(attachment, THREAD_FAILURE, 1); + write_dbg( + attachment, + session_id, + DBG_LEVEL_ERROR, + "clock_gettime(CLOCK_REALTIME) failed. Status: %s", + strerror(errno) + ); + return 0; + } + + // Convert milliseconds to timespec + ts.tv_sec += timeout_msecs / 1000; + ts.tv_nsec += (timeout_msecs % 1000) * 1000 * 1000; + if (ts.tv_nsec > 1000 * 1000 * 1000) { + ts.tv_nsec -= 1000 * 1000 * 1000; + ++ts.tv_sec; + } + + long long tv_nsec = ts.tv_nsec + (timeout_msecs % 1000) * 1000000; + + ts.tv_sec += timeout_msecs / 1000 + tv_nsec / 1000000000; + ts.tv_nsec = tv_nsec % 1000000000; + + status = pthread_timedjoin_np(thread, NULL, &ts); + + if (status != 0) { + write_dbg( + attachment, + session_id, + status == ETIMEDOUT ? DBG_LEVEL_DEBUG : DBG_LEVEL_WARNING, + "pthread_timejoin_np returns with %d (%s), fn=%s", + status, + strerror(status), + func_name + ); + + ret = pthread_cancel(thread); + write_dbg( + attachment, + session_id, + DBG_LEVEL_DEBUG, + "pthread_cancel returns with ret=%d, fn=%s", + ret, + func_name + ); + + ret = pthread_join(thread, &res); + if (ret != 0) { + updateMetricField(attachment, THREAD_FAILURE, 1); + write_dbg( + attachment, + session_id, + DBG_LEVEL_WARNING, + "pthread_join failed while fail open is enabled. RET=%d, fn=%s", + ret, + func_name + ); + return ret != 0; + } + + updateMetricField(attachment, res == PTHREAD_CANCELED ? THREAD_TIMEOUT : THREAD_FAILURE, 1); + if (res == PTHREAD_CANCELED) { + write_dbg(attachment, session_id, DBG_LEVEL_DEBUG, "thread was canceled, fn=%s", func_name); + } + + write_dbg(attachment, session_id, DBG_LEVEL_DEBUG, "pthread_join returns with ret=%d", ret); + } else { + write_dbg( + attachment, + session_id, + DBG_LEVEL_TRACE, + "Successfully executed thread. fn=%s", + func_name + ); + } + + if (data != NULL) { + CalcProcessingTime(data->session_data, &thread_time_begin, transaction_type); + } + return status == 0; +} diff --git a/attachments/nano_attachment/nano_attachment_thread.h b/attachments/nano_attachment/nano_attachment_thread.h new file mode 100644 index 0000000..6eff23c --- /dev/null +++ b/attachments/nano_attachment/nano_attachment_thread.h @@ -0,0 +1,46 @@ +#ifndef __NANO_ATTACHMENT_THREAD_H__ +#define __NANO_ATTACHMENT_THREAD_H__ + +#include + +#include "nano_initializer.h" + +typedef void *(*CpThreadRoutine)(void *); ///< Func + +typedef enum TransactionType +{ + START, + REQUEST, + RESPONSE, + METRICS, + REGISTRATION +} TransactionType; + +/// +/// @brief Runs a function in a thread with a timeout. +/// +/// This function runs the specified thread function in a dedicated thread with a timeout. +/// If the function does not complete within the specified timeout, it is cancelled. +/// +/// @param[in] attachment The NanoAttachment object. +/// @param[in] data The AttachmentData object. +/// @param[in] thread_func The function to run in the thread. +/// @param[in] arg The argument to pass to the thread function. +/// @param[in] timeout_msecs The timeout value in milliseconds. +/// @param[in] func_name The name of the function for debugging purposes. +/// @param[in] transaction_type The type of transaction (request or response), used for timeout. +/// +/// @return Returns 1 if the function completes within the timeout, 0 otherwise. +/// +int +NanoRunInThreadTimeout( + NanoAttachment *attachment, + AttachmentData *data, + CpThreadRoutine thread_func, + void *arg, + int timeout_msecs, + char *func_name, + TransactionType transaction_type +); + +#endif // __NANO_ATTACHMENT_THREAD_H__ diff --git a/attachments/nano_attachment/nano_attachment_util/CMakeLists.txt b/attachments/nano_attachment/nano_attachment_util/CMakeLists.txt new file mode 100644 index 0000000..8dcd766 --- /dev/null +++ b/attachments/nano_attachment/nano_attachment_util/CMakeLists.txt @@ -0,0 +1,8 @@ +add_definitions(-DUSERSPACE) +add_library(nano_attachment_util SHARED nano_attachment_util.cc) +target_link_libraries(nano_attachment_util http_configuration) + +# add_subdirectory(nano_attachment_util_ut) + +install(TARGETS nano_attachment_util DESTINATION lib) +install(TARGETS nano_attachment_util DESTINATION nginx_attachment/lib PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ) diff --git a/attachments/nano_attachment/nano_attachment_util/nano_attachment_util.cc b/attachments/nano_attachment/nano_attachment_util/nano_attachment_util.cc new file mode 100644 index 0000000..a45afa5 --- /dev/null +++ b/attachments/nano_attachment/nano_attachment_util/nano_attachment_util.cc @@ -0,0 +1,251 @@ +// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "nano_attachment_util.h" + +#include + +#include "http_configuration.h" + +using namespace std; + +static HttpAttachmentConfiguration conf_data; + +int +initAttachmentConfig(c_str conf_file) +{ + return conf_data.init(conf_file); +} + +NanoHttpInspectionMode +getInspectionMode() +{ + return static_cast(conf_data.getNumericalValue("nginx_inspection_mode")); +} + +unsigned int +getNumOfNginxIpcElements() +{ + return conf_data.getNumericalValue("num_of_nginx_ipc_elements"); +} + +unsigned int +getKeepAliveIntervalMsec() +{ + return conf_data.getNumericalValue("keep_alive_interval_msec"); +} + +unsigned int +getDbgLevel() +{ + return conf_data.getNumericalValue("dbg_level"); +} + +int +isDebugContext(c_str client, c_str server, unsigned int port, c_str method, c_str host, c_str uri) +{ + auto &ctx = conf_data.getDebugContext(); + return + (ctx.client == "" || ctx.client == client) && + (ctx.server == "" || ctx.server == server) && + (ctx.port == 0 || ctx.port == port) && + (ctx.method == "" || ctx.method == method) && + (ctx.host == "" || ctx.host == host) && + (ctx.uri == "" || ctx.uri == uri); +} + +c_str +getStaticResourcesPath() +{ + return conf_data.getStringValue("static_resources_path").c_str(); +} + +int +isFailOpenMode() +{ + return conf_data.getNumericalValue("is_fail_open_mode_enabled"); +} + +unsigned int +getFailOpenTimeout() +{ + return conf_data.getNumericalValue("fail_open_timeout"); +} + +int +isFailOpenHoldMode() +{ + return conf_data.getNumericalValue("is_fail_open_mode_hold_enabled"); +} + +unsigned int +getFailOpenHoldTimeout() +{ + return conf_data.getNumericalValue("fail_open_hold_timeout"); +} + +unsigned int +getMaxSessionsPerMinute() +{ + return conf_data.getNumericalValue("max_sessions_per_minute"); +} + +int +isFailOpenOnSessionLimit() +{ + return conf_data.getStringValue("sessions_per_minute_limit_verdict") == "Accept"; +} + +unsigned int +getRegistrationThreadTimeout() +{ + return conf_data.getNumericalValue("registration_thread_timeout_msec"); +} + +unsigned int +getReqProccessingTimeout() +{ + return conf_data.getNumericalValue("req_proccessing_timeout_msec"); +} + +unsigned int +getReqHeaderThreadTimeout() +{ + return conf_data.getNumericalValue("req_header_thread_timeout_msec"); +} + +unsigned int +getReqBodyThreadTimeout() +{ + return conf_data.getNumericalValue("req_body_thread_timeout_msec"); +} + +unsigned int +getResProccessingTimeout() +{ + return conf_data.getNumericalValue("res_proccessing_timeout_msec"); +} + +unsigned int +getResHeaderThreadTimeout() +{ + return conf_data.getNumericalValue("res_header_thread_timeout_msec"); +} + +unsigned int +getResBodyThreadTimeout() +{ + return conf_data.getNumericalValue("res_body_thread_timeout_msec"); +} + +unsigned int +getWaitingForVerdictThreadTimeout() +{ + return conf_data.getNumericalValue("waiting_for_verdict_thread_timeout_msec"); +} + +int +isIPAddress(c_str ip_str) +{ + int address_family = AF_INET; + for (int i = 0; ip_str[i]; ++i) { + if (ip_str[i] == ':') address_family = AF_INET6; + } + + char placeholder[16]; + return inet_pton(address_family, ip_str, placeholder); +} + +struct IpAddress +{ + union { + struct in_addr ipv4; + struct in6_addr ipv6; + } ip; + bool is_ipv4; + + bool + operator<(const IpAddress &other) const + { + if (is_ipv4 != other.is_ipv4) return is_ipv4 < other.is_ipv4; + if (is_ipv4) return memcmp(&ip.ipv4, &other.ip.ipv4, sizeof(struct in_addr)) < 0; + return memcmp(&ip.ipv6, &other.ip.ipv6, sizeof(struct in6_addr)) < 0; + } + + bool + operator<=(const IpAddress &other) const + { + return !(other < *this); + } +}; + +static IpAddress +createIPAddress(c_str ip_str) +{ + IpAddress res; + + for (int i = 0; ip_str[i]; ++i) { + if (ip_str[i] == ':') { + res.is_ipv4 = false; + inet_pton(AF_INET6, ip_str, &res.ip.ipv6); + return res; + } + } + + res.is_ipv4 = true; + inet_pton(AF_INET, ip_str, &res.ip.ipv4); + return res; +} + +static bool +isIPInRange(const IpAddress &ip, const IpAddress &start, const IpAddress &end) +{ + if (ip.is_ipv4 != start.is_ipv4 || ip.is_ipv4 != end.is_ipv4) return false; + return start <= ip && ip <= end; +} + +static bool +isIPInRange(const IpAddress &ip, const string &range) +{ + auto delimiter = range.find('-'); + + if (delimiter == string::npos) { + if (!isIPAddress(range.c_str())) return false; + auto address = createIPAddress(range.c_str()); + return isIPInRange(ip, address, address); + } + + auto start_str = range.substr(0, delimiter); + if (!isIPAddress(start_str.c_str())) return false; + auto start_addr = createIPAddress(start_str.c_str()); + + auto end_str = range.substr(delimiter + 1); + if (!isIPAddress(end_str.c_str())) return false; + auto end_addr = createIPAddress(end_str.c_str()); + + return isIPInRange(ip, start_addr, end_addr); +} + +int +isSkipSource(c_str ip_str) +{ + if (!isIPAddress(ip_str)) return 0; + auto ip = createIPAddress(ip_str); + + for (auto &range : conf_data.getExcludeSources()) { + if (isIPInRange(ip, range)) return 1; + } + + return 0; +} diff --git a/attachments/nano_attachment/nano_attachment_util/nano_attachment_util_ut/CMakeLists.txt b/attachments/nano_attachment/nano_attachment_util/nano_attachment_util_ut/CMakeLists.txt new file mode 100644 index 0000000..10ca3af --- /dev/null +++ b/attachments/nano_attachment/nano_attachment_util/nano_attachment_util_ut/CMakeLists.txt @@ -0,0 +1,8 @@ +include_directories(${Boost_INCLUDE_DIRS}) +include_directories(${CMAKE_SOURCE_DIR}/attachments/nano_attachment/nano_attachment_util) + +add_unit_test( + nano_attachment_util_ut + "nano_attachment_util_ut.cc" + "nano_attachment_util" +) diff --git a/attachments/nano_attachment/nano_attachment_util/nano_attachment_util_ut/nano_attachment_util_ut.cc b/attachments/nano_attachment/nano_attachment_util/nano_attachment_util_ut/nano_attachment_util_ut.cc new file mode 100644 index 0000000..a2bee84 --- /dev/null +++ b/attachments/nano_attachment/nano_attachment_util/nano_attachment_util_ut/nano_attachment_util_ut.cc @@ -0,0 +1,125 @@ +#include +#include +#include +#include + +#include "nano_attachment_util.h" +#include "cptest.h" +#include "c_common/ip_common.h" + +using namespace std; +using namespace testing; + +class HttpAttachmentUtilTest : public Test +{ +public: + string + createIPRangesString(const vector &ip_ranges) + { + stringstream ip_ranges_string_stream; + ip_ranges_string_stream << "["; + for (auto iterator = ip_ranges.begin(); iterator < ip_ranges.end() - 1; iterator++) { + ip_ranges_string_stream << "\"" << *iterator << "\"" << ", "; + } + ip_ranges_string_stream << "\"" << ip_ranges.back() << "\"]"; + + return ip_ranges_string_stream.str(); + } + + const string attachment_configuration_file_name = "cp_nano_http_attachment_conf"; + const vector ip_ranges = { "8.8.8.8", "9.9.9.9-10.10.10.10", "0:0:0:0:0:0:0:2-0:0:0:0:0:0:0:5"}; + const string static_resources_path = "/dev/shm/static_resources/"; +}; + +TEST_F(HttpAttachmentUtilTest, GetValidAttachmentConfiguration) +{ + string valid_configuration = + "{\n" + "\"context_values\": {" + "\"clientIp\": \"1.2.3.4\"," + "\"listeningIp\": \"5.6.7.8\"," + "\"uriPrefix\": \"/abc\"," + "\"hostName\": \"test\"," + "\"httpMethod\": \"GET\"," + "\"listeningPort\": 80" + "}," + "\"is_fail_open_mode_enabled\": 0,\n" + "\"fail_open_timeout\": 1234,\n" + "\"is_fail_open_mode_hold_enabled\": 1,\n" + "\"fail_open_hold_timeout\": 4321,\n" + "\"sessions_per_minute_limit_verdict\": \"Accept\",\n" + "\"max_sessions_per_minute\": 0,\n" + "\"num_of_nginx_ipc_elements\": 200,\n" + "\"keep_alive_interval_msec\": 10000,\n" + "\"dbg_level\": 2,\n" + "\"nginx_inspection_mode\": 1,\n" + "\"operation_mode\": 0,\n" + "\"req_body_thread_timeout_msec\": 155,\n" + "\"req_proccessing_timeout_msec\": 42,\n" + "\"registration_thread_timeout_msec\": 101,\n" + "\"res_proccessing_timeout_msec\": 420,\n" + "\"res_header_thread_timeout_msec\": 1,\n" + "\"res_body_thread_timeout_msec\": 0,\n" + "\"waiting_for_verdict_thread_timeout_msec\": 75,\n" + "\"req_header_thread_timeout_msec\": 10,\n" + "\"ip_ranges\": " + createIPRangesString(ip_ranges) + ",\n" + "\"static_resources_path\": \"" + static_resources_path + "\"" + "}\n"; + ofstream valid_configuration_file(attachment_configuration_file_name); + valid_configuration_file << valid_configuration; + valid_configuration_file.close(); + + EXPECT_EQ(initAttachmentConfig(attachment_configuration_file_name.c_str()), 1); + EXPECT_EQ(getDbgLevel(), 2u); + EXPECT_EQ(getStaticResourcesPath(), static_resources_path); + EXPECT_EQ(isFailOpenMode(), 0); + EXPECT_EQ(getFailOpenTimeout(), 1234u); + EXPECT_EQ(isFailOpenHoldMode(), 1); + EXPECT_EQ(getFailOpenHoldTimeout(), 4321u); + EXPECT_EQ(isFailOpenOnSessionLimit(), 1); + EXPECT_EQ(getMaxSessionsPerMinute(), 0u); + EXPECT_EQ(getNumOfNginxIpcElements(), 200u); + EXPECT_EQ(getKeepAliveIntervalMsec(), 10000u); + EXPECT_EQ(getResProccessingTimeout(), 420u); + EXPECT_EQ(getReqProccessingTimeout(), 42u); + EXPECT_EQ(getRegistrationThreadTimeout(), 101u); + EXPECT_EQ(getReqHeaderThreadTimeout(), 10u); + EXPECT_EQ(getReqBodyThreadTimeout(), 155u); + EXPECT_EQ(getResHeaderThreadTimeout(), 1u); + EXPECT_EQ(getResBodyThreadTimeout(), 0u); + EXPECT_EQ(getWaitingForVerdictThreadTimeout(), 75u); + EXPECT_EQ(getInspectionMode(), NanoHttpInspectionMode::BLOCKING_THREAD); + + EXPECT_EQ(isDebugContext("1.2.3.4", "5.6.7.8", 80, "GET", "test", "/abc"), 1); + EXPECT_EQ(isDebugContext("1.2.3.9", "5.6.7.8", 80, "GET", "test", "/abc"), 0); + EXPECT_EQ(isDebugContext("1.2.3.4", "5.6.7.9", 80, "GET", "test", "/abc"), 0); + EXPECT_EQ(isDebugContext("1.2.3.4", "5.6.7.8", 88, "GET", "test", "/abc"), 0); + EXPECT_EQ(isDebugContext("1.2.3.4", "5.6.7.8", 80, "POST", "test", "/abc"), 0); + EXPECT_EQ(isDebugContext("1.2.3.4", "5.6.7.8", 80, "GET", "est", "/abc"), 0); + EXPECT_EQ(isDebugContext("1.2.3.4", "5.6.7.8", 80, "GET", "test", "/ab"), 0); + + EXPECT_EQ(isSkipSource("8.8.8.8"), 1); + EXPECT_EQ(isSkipSource("8.8.8.9"), 0); + EXPECT_EQ(isSkipSource("8.8.8.10"), 0); + + EXPECT_EQ(isSkipSource("9.9.9.8"), 0); + EXPECT_EQ(isSkipSource("9.9.9.9"), 1); + EXPECT_EQ(isSkipSource("9.255.0.0"), 1); + EXPECT_EQ(isSkipSource("10.10.10.10"), 1); + EXPECT_EQ(isSkipSource("10.10.10.11"), 0); + + EXPECT_EQ(isSkipSource("0:0:0:0:0:0:0:1"), 0); + EXPECT_EQ(isSkipSource("0:0:0:0:0:0:0:2"), 1); + EXPECT_EQ(isSkipSource("0:0:0:0:0:0:0:4"), 1); + EXPECT_EQ(isSkipSource("0:0:0:0:0:0:0:5"), 1); + EXPECT_EQ(isSkipSource("0:0:0:0:0:0:0:6"), 0); +} + +TEST_F(HttpAttachmentUtilTest, CheckIPAddrValidity) +{ + EXPECT_EQ(isIPAddress("10.0.0.1"), 1); + EXPECT_EQ(isIPAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), 1); + + EXPECT_EQ(isIPAddress("333.0.0.1"), 0); + EXPECT_EQ(isIPAddress("2001:0gb8:85a3:0000:0000:8a2e:0370:7334"), 0); +} diff --git a/attachments/nano_attachment/nano_blockpage.h b/attachments/nano_attachment/nano_blockpage.h new file mode 100644 index 0000000..fe1cd99 --- /dev/null +++ b/attachments/nano_attachment/nano_blockpage.h @@ -0,0 +1,24 @@ +// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// @file ngx_cp_http_usercheck.h +/// \brief This file contains the string for the block-page returned instead of the normal server response + +const char *title_prefix = "
"; + +const char *body_prefix = "

"; + +const char *uuid_prefix = "


"; + +const char *uuid_suffix = "

"; diff --git a/attachments/nano_attachment/nano_compression.c b/attachments/nano_attachment/nano_compression.c new file mode 100755 index 0000000..75a35b3 --- /dev/null +++ b/attachments/nano_attachment/nano_compression.c @@ -0,0 +1,130 @@ +#include "nano_compression.h" + +#include + +#include "nano_attachment_common.h" +#include "nano_initializer.h" +#include "compression_utils.h" +#include "nano_utils.h" + +HttpBody * +nano_compress_body( + NanoAttachment *attachment, + HttpBody *bodies, + HttpSessionData *session_data_p +) +{ + CompressionResult compression_result; + HttpBody *compressed_body; + size_t i; + + if (session_data_p->response_data.compression_type == NO_COMPRESSION) { + return NULL; + } + write_dbg( + attachment, + session_data_p->session_id, + DBG_LEVEL_TRACE, + "Compressing body" + ); + + if (session_data_p->response_data.compression_stream == NULL) { + session_data_p->response_data.compression_stream = initCompressionStream(); + } + + compressed_body = malloc(sizeof(HttpBody)); + if (compressed_body == NULL) { + return NULL; + } + + compressed_body->bodies_count = bodies->bodies_count; + compressed_body->data = malloc(bodies->bodies_count * sizeof(nano_str_t)); + if (compressed_body->data == NULL) { + free(compressed_body); + return NULL; + } + + for (i = 0; i < bodies->bodies_count; ++i) { + compression_result = compressData( + session_data_p->response_data.compression_stream, + session_data_p->response_data.compression_type, + bodies->data[i].len, + bodies->data[i].data, + i == bodies->bodies_count - 1 + ); + compressed_body->data[i].len = compression_result.num_output_bytes; + compressed_body->data[i].data = compression_result.output; + } + + return compressed_body; +} + +HttpBody * +nano_decompress_body( + NanoAttachment *attachment, + HttpBody *bodies, + HttpSessionData *session_data_p +) +{ + DecompressionResult decompression_result; + HttpBody *decompressed_body; + size_t i; + + if (session_data_p->response_data.compression_type == NO_COMPRESSION) { + return NULL; + } + write_dbg( + attachment, + session_data_p->session_id, + DBG_LEVEL_TRACE, + "Decompressing body" + ); + + if (session_data_p->response_data.decompression_stream == NULL) { + session_data_p->response_data.decompression_stream = initCompressionStream(); + } + + decompressed_body = malloc(sizeof(HttpBody)); + if (decompressed_body == NULL) { + return NULL; + } + + decompressed_body->bodies_count = bodies->bodies_count; + decompressed_body->data = malloc(bodies->bodies_count * sizeof(nano_str_t)); + if (decompressed_body->data == NULL) { + free(decompressed_body); + return NULL; + } + + for (i = 0; i < bodies->bodies_count; ++i) { + decompression_result = decompressData( + session_data_p->response_data.decompression_stream, + bodies->data[i].len, + bodies->data[i].data + ); + decompressed_body->data[i].len = decompression_result.num_output_bytes; + decompressed_body->data[i].data = decompression_result.output; + } + + return decompressed_body; +} + +void +nano_free_compressed_body( + NanoAttachment *attachment, + HttpBody *bodies, + HttpSessionData *session_data_p +) +{ + if (bodies == NULL) { + return; + } + write_dbg( + attachment, + session_data_p->session_id, + DBG_LEVEL_TRACE, + "Freeing compressed body" + ); + free(bodies->data); + free(bodies); +} diff --git a/attachments/nano_attachment/nano_compression.h b/attachments/nano_attachment/nano_compression.h new file mode 100755 index 0000000..5108b35 --- /dev/null +++ b/attachments/nano_attachment/nano_compression.h @@ -0,0 +1,45 @@ +#ifndef __NANO_COMPRESSION_H__ +#define __NANO_COMPRESSION_H__ + +#include "nano_attachment_sender_thread.h" + +/// @brief Compresses the given HTTP body using the specified compression type in the session data. +/// +/// @param attachment Pointer to the NanoAttachment structure. +/// @param bodies Pointer to the HttpBody structure containing the data to be compressed. +/// @param session_data_p Pointer to the HttpSessionData structure containing session-specific data. +/// +/// @return Pointer to a new HttpBody structure containing the compressed data, +/// or NULL if compression is not needed or fails. +HttpBody *nano_compress_body( + NanoAttachment *attachment, + HttpBody *bodies, + HttpSessionData *session_data_p +); + +/// @brief Decompresses the given HTTP body using the specified compression type in the session data. +/// +/// @param attachment Pointer to the NanoAttachment structure. +/// @param bodies Pointer to the HttpBody structure containing the data to be decompressed. +/// @param session_data_p Pointer to the HttpSessionData structure containing session-specific data. +/// +/// @return Pointer to a new HttpBody structure containing the decompressed data, +/// or NULL if decompression is not needed or fails. +HttpBody *nano_decompress_body( + NanoAttachment *attachment, + HttpBody *bodies, + HttpSessionData *session_data_p +); + +/// @brief Frees the memory allocated for the compressed HTTP body. +/// +/// @param attachment Pointer to the NanoAttachment structure. +/// @param bodies Pointer to the HttpBody structure containing the compressed data to be freed. +/// @param session_data_p Pointer to the HttpSessionData structure containing session-specific data. +void nano_free_compressed_body( + NanoAttachment *attachment, + HttpBody *bodies, + HttpSessionData *session_data_p +); + +#endif // __NANO_COMPRESSION_H__ diff --git a/attachments/nano_attachment/nano_configuration.c b/attachments/nano_attachment/nano_configuration.c new file mode 100644 index 0000000..7f16cb6 --- /dev/null +++ b/attachments/nano_attachment/nano_configuration.c @@ -0,0 +1,144 @@ +#include "nano_configuration.h" + +#include "nano_attachment_common.h" +#include "nano_utils.h" +#include "nano_attachment_util.h" + +#include +#include + +NanoCommunicationResult +init_attachment_config(NanoAttachment *attachment, const char *conf_path) +{ + int new_dbg_level = DBG_LEVEL_COUNT; + + if (access(conf_path, F_OK) != 0) return NANO_ERROR; + + if (attachment->is_configuration_updated == NANO_OK) return NANO_OK; + + // Initiate attachment using the file in the provided conf_path. + if (!initAttachmentConfig(conf_path)) { + write_dbg(attachment, attachment->worker_id, DBG_LEVEL_WARNING, "Failed to load the configuration"); + return NANO_ERROR; + } + + new_dbg_level = getDbgLevel(); + + if (new_dbg_level >= DBG_LEVEL_COUNT) { + write_dbg( + attachment, + attachment->worker_id, + DBG_LEVEL_WARNING, + "Illegal debug level received: %d", + new_dbg_level + ); + attachment->is_configuration_updated = NANO_ERROR; + return NANO_ERROR; + } + write_dbg(attachment, attachment->worker_id, DBG_LEVEL_DEBUG, "Setting debug level to: %d", new_dbg_level); + // Setting a new debug level. + attachment->dbg_level = new_dbg_level; + + // Setting fail open/close. + attachment->fail_mode_verdict = isFailOpenMode() == 1 ? NANO_OK : NANO_ERROR; + attachment->fail_open_timeout = getFailOpenTimeout(); + + // Setting fail delayed open/close + attachment->fail_mode_delayed_verdict = isFailOpenHoldMode() == 1 ? NANO_OK : NANO_ERROR; + attachment->fail_open_delayed_timeout = getFailOpenHoldTimeout(); + + // Setting attachment's variables. + attachment->sessions_per_minute_limit_verdict = + isFailOpenOnSessionLimit() ? ATTACHMENT_VERDICT_ACCEPT : ATTACHMENT_VERDICT_DROP; + attachment->max_sessions_per_minute = getMaxSessionsPerMinute(); + attachment->inspection_mode = getInspectionMode(); + if (attachment->inspection_mode >= INSPECTION_MODE_COUNT) { + write_dbg( + attachment, + attachment->worker_id, + DBG_LEVEL_WARNING, + "Illegal inspection mode received: %d", + attachment->inspection_mode + ); + attachment->is_configuration_updated = NANO_ERROR; + return NANO_ERROR; + } + + attachment->req_max_proccessing_ms_time = getReqProccessingTimeout(); + attachment->res_max_proccessing_ms_time = getResProccessingTimeout(); + attachment->registration_thread_timeout_msec = getRegistrationThreadTimeout(); + attachment->req_header_thread_timeout_msec = getReqHeaderThreadTimeout(); + attachment->req_body_thread_timeout_msec = getReqBodyThreadTimeout(); + attachment->res_header_thread_timeout_msec = getResHeaderThreadTimeout(); + attachment->res_body_thread_timeout_msec = getResBodyThreadTimeout(); + attachment->waiting_for_verdict_thread_timeout_msec = getWaitingForVerdictThreadTimeout(); + + attachment->num_of_nano_ipc_elements = getNumOfNginxIpcElements(); + attachment->keep_alive_interval_msec = getKeepAliveIntervalMsec(); + + // set_static_resources_path(getStaticResourcesPath()); + attachment->is_configuration_updated = NANO_OK; + + attachment->logging_data->dbg_level = attachment->dbg_level; + attachment->logging_data->worker_id = attachment->worker_id; + attachment->logging_data->fd = attachment->logging_fd; + + write_dbg( + attachment, + attachment->worker_id, + DBG_LEVEL_INFO, + "Successfully loaded configuration. " + "inspection mode: %d, " + "debug level: %d, " + "failure mode: %s, " + "fail mode timeout: %u msec, " + "failure delayed mode: %s, " + "fail mode delayed timeout: %u msec, " + "sessions per minute limit verdict: %s, " + "max sessions per minute: %u, " + "req max processing time: %u msec, " + "res max processing time: %u msec, " + "registration thread timeout: %u msec, " + "req start thread timeout: %u msec, " + "req header thread timeout: %u msec, " + "req body thread timeout: %u msec, " + "res header thread timeout: %u msec, " + "res body thread timeout: %u msec, " + "delayed thread timeout: %u msec, " + "static resources path: %s, " + "num of nginx ipc elements: %u, " + "keep alive interval msec: %u msec", + attachment->inspection_mode, + attachment->dbg_level, + (attachment->fail_mode_verdict == NANO_OK ? "fail-open" : "fail-close"), + attachment->fail_open_timeout, + (attachment->fail_mode_delayed_verdict == NANO_OK ? "fail-open" : "fail-close"), + attachment->fail_open_delayed_timeout, + attachment->sessions_per_minute_limit_verdict == ATTACHMENT_VERDICT_ACCEPT ? "Accept" : "Drop", + attachment->max_sessions_per_minute, + attachment->req_max_proccessing_ms_time, + attachment->res_max_proccessing_ms_time, + attachment->registration_thread_timeout_msec, + attachment->req_start_thread_timeout_msec, + attachment->req_header_thread_timeout_msec, + attachment->req_body_thread_timeout_msec, + attachment->res_header_thread_timeout_msec, + attachment->res_body_thread_timeout_msec, + attachment->waiting_for_verdict_thread_timeout_msec, + getStaticResourcesPath(), + attachment->num_of_nano_ipc_elements, + attachment->keep_alive_interval_msec + ); + + return NANO_OK; +} + +NanoCommunicationResult +reset_attachment_config(NanoAttachment *attachment) +{ + write_dbg(attachment, attachment->worker_id, DBG_LEVEL_INFO, "Resetting attachment configuration"); + + attachment->is_configuration_updated = NANO_ERROR; + attachment->current_config_version++; + return init_attachment_config(attachment, SHARED_ATTACHMENT_CONF_PATH); +} diff --git a/attachments/nano_attachment/nano_configuration.h b/attachments/nano_attachment/nano_configuration.h new file mode 100644 index 0000000..885b181 --- /dev/null +++ b/attachments/nano_attachment/nano_configuration.h @@ -0,0 +1,37 @@ +/// @file nano_configuration.h +#ifndef __NANO_CONFIGURATION_H__ +#define __NANO_CONFIGURATION_H__ + +#include +#include + +#include "nano_attachment_common.h" +#include "nano_initializer.h" + +/// +/// @brief Initializes the general configuration for a NanoAttachment object. +/// +/// This function initializes the general configuration for the specified NanoAttachment object +/// using the configuration file located at the specified path. It updates various configuration +/// parameters such as debug level, fail-open/fail-close mode, session limits, timeouts, and others +/// based on the configuration file. +/// +/// @param attachment A pointer to the NanoAttachment object to initialize the configuration for. +/// @param conf_path The path to the configuration file. +/// @return A NanoCommunicationResult indicating the result of the operation. +/// +NanoCommunicationResult init_attachment_config(NanoAttachment *attachment, const char *conf_path); + +/// +/// @brief Resets the configuration of a NanoAttachment object. +/// +/// This function resets the configuration of the specified NanoAttachment object by +/// marking it as not updated, incrementing the current configuration version, and +/// reinitializing the general configuration using the specified configuration path. +/// +/// @param attachment A pointer to the NanoAttachment object to reset the configuration for. +/// @return A NanoCommunicationResult indicating the result of the operation. +/// +NanoCommunicationResult reset_attachment_config(NanoAttachment *attachment); + +#endif // __NANO_CONFIGURATION_H__ diff --git a/attachments/nano_attachment/nano_initializer.c b/attachments/nano_attachment/nano_initializer.c new file mode 100644 index 0000000..65e5fd4 --- /dev/null +++ b/attachments/nano_attachment/nano_initializer.c @@ -0,0 +1,859 @@ +// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// @file nano_initializer.c +#include "nano_initializer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nano_attachment_common.h" +#include "attachment_types.h" +#include "nano_configuration.h" +#include "nano_attachment_io.h" +#include "shmem_ipc_2.h" +#include "nano_utils.h" +#include "nano_attachment_sender_thread.h" +#include "nano_attachment_thread.h" + +NanoCommunicationResult +write_to_service( + NanoAttachment *attachment, + int *socket, + void *data, + uint32_t size, + struct timeval *absolute_end_time) +{ + int res = 0; + // `remaining_size` and `cur_data_ptr` are used to keep track of where we are in the memory. + // This allows us to write to the socket in parts (if we need to). + uint32_t remaining_size = size; + char *cur_data_ptr = data; + + while (remaining_size > 0) { + // If the operation exceeded the allowed time, treat it as a failure. + if (is_absolute_timeout_reached(absolute_end_time)) { + close(*socket); + *socket = -1; + write_dbg( + attachment, + 0, + DBG_LEVEL_TRACE, + "Reached timeout while communicating with the socket" + ); + return NANO_TIMEOUT; + } + + res = write(*socket, (void *)cur_data_ptr, remaining_size); + + // `res` is -1 in case of an error: write functions failed or socket wasn't available. + if (res < 0) { + close(*socket); + *socket = -1; + write_dbg( + attachment, + 0, + DBG_LEVEL_TRACE, + "Failed to communicate with the socket, Error: %s", + strerror(errno) + ); + return NANO_ERROR; + } + + remaining_size -= res; + cur_data_ptr += res; + } + return NANO_OK; +} + +NanoCommunicationResult +read_from_service( + NanoAttachment *attachment, + int *socket, + void *data, + uint32_t size, + struct timeval *absolute_end_time) +{ + int res = 0; + // `remaining_size` and `cur_data_ptr` are used to keep track of where we are in the memory. + // This allows us to read from the socket in parts (if we need to). + uint32_t remaining_size = size; + char *cur_data_ptr = data; + + while (remaining_size > 0) { + // If the operation exceeded the allowed time, treat it as a failure. + if (is_absolute_timeout_reached(absolute_end_time)) { + close(*socket); + *socket = -1; + write_dbg( + attachment, + 0, + DBG_LEVEL_TRACE, + "Reached timeout while communicating with the socket" + ); + return NANO_TIMEOUT; + } + + // The read operation must not block the attachment indefinitely. + // To avoid that we check whether the socket has data to be read prior to the read operation. + // If the socket doesn't have data to be read from within a reasonable time, we treat this as an error. + struct pollfd s_poll; + s_poll.fd = *socket; + s_poll.events = POLLIN; + s_poll.revents = 0; + res = poll(&s_poll, 1, 3000); + + if (res <= 0 || (s_poll.revents & POLLIN) == 0) { + close(*socket); + *socket = -1; + write_dbg( + attachment, + 0, + DBG_LEVEL_TRACE, + "Failed to communicate with the socket, Error: %s", + strerror(errno) + ); + return NANO_ERROR; + } + + res = read(*socket, (void *)cur_data_ptr, remaining_size); + remaining_size -= res; + cur_data_ptr += res; + } + return NANO_OK; +} + +/// +/// @brief Send communication data to the communication socket. +/// +/// This function sends various data (unique identifier, process UID, process GID) +/// to the service through the communication socket. It sends the data in the +/// following order: +/// 1. The length of the unique identifier for this instance. +/// 2. The unique identifier for this instance. +/// 3. The process UID. +/// 4. The process GID. +/// +/// @param[in] attachment The NanoAttachment struct containing the data to send. +/// @returns A NanoCommunicationResult indicating the success of the operation. +/// - #NANO_OK: The data was successfully sent. +/// - #NANO_ERROR: An error occurred during data transmission. +/// +NanoCommunicationResult +send_comm_data_to_comm_socket(NanoAttachment *attachment) +{ + NanoCommunicationResult res; + uint8_t uid_size_to_send = strlen(attachment->unique_id); + struct timeval timeout = get_absolute_timeout_val_sec(1); + + res = write_to_service( + attachment, + &attachment->comm_socket, + &uid_size_to_send, + sizeof(uid_size_to_send), + &timeout + ); + if (res != NANO_OK) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Failed to send unique id size"); + return NANO_ERROR; + } + + res = write_to_service( + attachment, + &attachment->comm_socket, + attachment->unique_id, + uid_size_to_send, + &timeout + ); + if (res != NANO_OK) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Failed to send unique id %s", attachment->unique_id); + return NANO_ERROR; + } + + res = write_to_service( + attachment, + &attachment->comm_socket, + &attachment->nano_user_id, + sizeof(uint32_t), + &timeout + ); + if (res != NANO_OK) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Failed to send nano user id"); + return NANO_ERROR; + } + + res = write_to_service( + attachment, + &attachment->comm_socket, + &attachment->nano_group_id, + sizeof(uint32_t), + &timeout + ); + if (res != NANO_OK) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Failed to send nano group id"); + return NANO_ERROR; + } + + return NANO_OK; +} + +/// +/// @brief Initialize the signaling socket for communication. +/// +/// This function connects to the communication socket, sends communication data, +/// and waits for an acknowledgment from the service that communication has been +/// established. If any step fails, the function closes the communication socket +/// and returns an error. +/// +/// @param[in] attachment The NanoAttachment struct containing socket information. +/// @returns A NanoCommunicationResult indicating the success of the operation. +/// +/// - #NANO_OK: Signaling socket initialized successfully. +/// - #NANO_ERROR: An error occurred during initialization. +/// +static NanoCommunicationResult +init_signaling_socket(NanoAttachment *attachment) +{ + uint8_t initialization_ack; + HttpEventThreadCtx ctx; + int t_res; + NanoCommunicationResult res; + struct timeval timeout = get_absolute_timeout_val_sec(1); + + write_dbg(attachment, 0, DBG_LEVEL_DEBUG, "spawn RegistrationCommSocketThread"); + t_res = NanoRunInThreadTimeout( + attachment, + NULL, + RegistrationCommSocketThread, + (void *)&ctx, + attachment->registration_thread_timeout_msec, + "RegistrationCommSocketThread", + REGISTRATION + ); + + if (!t_res || ctx.res != NANO_OK) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Failed to connect to connection socket"); + close(attachment->comm_socket); + attachment->comm_socket = -1; + if (attachment->registration_state != PENDING) { + attachment->registration_state = NOT_REGISTERED; + } + return NANO_ERROR; + } + + res = send_comm_data_to_comm_socket(attachment); + if (res != NANO_OK) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Failed to send comm data"); + close(attachment->comm_socket); + attachment->comm_socket = -1; + if (attachment->registration_state != PENDING) { + attachment->registration_state = NOT_REGISTERED; + } + return NANO_ERROR; + } + + // Get an acknowledgement form the service that communication has been established. + res = read_from_service( + attachment, + &attachment->comm_socket, + &initialization_ack, + sizeof(initialization_ack), + &timeout + ); + if (res != NANO_OK) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Failed to read communication ack"); + close(attachment->comm_socket); + attachment->comm_socket = -1; + if (attachment->registration_state != PENDING) { + attachment->registration_state = NOT_REGISTERED; + } + return NANO_ERROR; + } + + write_dbg( + attachment, + 0, + DBG_LEVEL_DEBUG, + "Successfully connected on client socket %d", + attachment->comm_socket + ); + return NANO_OK; +} + +static NanoCommunicationResult +createDirectory(NanoAttachment *attachment, const char *path) +{ + struct stat st; + + if (stat(path, &st) == 0) { + write_dbg( + attachment, + 0, + DBG_LEVEL_DEBUG, + "Nano attachment logging directory already exists" + ); + return NANO_OK; + } + if (mkdir(path, 0755) == 0) { + write_dbg( + attachment, + 0, + DBG_LEVEL_DEBUG, + "Successfully created logging directory" + ); + return NANO_OK; + } + + write_dbg( + attachment, + 0, + DBG_LEVEL_WARNING, + "Failed to create logging directory" + ); + + return NANO_ERROR; +} + +NanoCommunicationResult +set_docker_id(NanoAttachment *attachment) +{ + size_t len = MAX_NGINX_UID_LEN; + char *line = NULL; + char *docker_ptr = NULL; + char *containerd_ptr = NULL; + FILE *file = fopen(CONTAINER_ID_FILE_PATH, "r"); + if (file == NULL) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Failed to open %s", CONTAINER_ID_FILE_PATH); + return NANO_ERROR; + } + write_dbg(attachment, 0, DBG_LEVEL_DEBUG, "opened file %s", CONTAINER_ID_FILE_PATH); + + line = malloc(MAX_NGINX_UID_LEN); + if (line == NULL) { + write_dbg(attachment, 0, DBG_LEVEL_DEBUG, "Failed to allocate memory for reading docker id file"); + fclose(file); + return NANO_ERROR; + } + + // Reading the file line by line. + bool uid_read = false; + while (getline(&line, &len, file) != -1) { + docker_ptr = strstr(line, "docker/"); + containerd_ptr = strstr(line, "cri-containerd-"); + + if (docker_ptr != NULL) + { + // We've found a line with "docker/" so the identifier will be right after that. + write_dbg(attachment, 0, DBG_LEVEL_DEBUG, "checking for docker/"); + docker_ptr += strlen("docker/"); + strncpy(attachment->container_id, docker_ptr, MAX_CONTAINER_ID_LEN - 1); + attachment->container_id[MAX_CONTAINER_ID_LEN - 1] = '\0'; + uid_read = true; + break; + } + + if (containerd_ptr != NULL) + { + write_dbg(attachment, 0, DBG_LEVEL_DEBUG, "checking for cri-containerd-"); + containerd_ptr += strlen("cri-containerd-"); + strncpy(attachment->container_id, containerd_ptr, MAX_CONTAINER_ID_LEN - 1); + attachment->container_id[MAX_CONTAINER_ID_LEN - 1] = '\0'; + uid_read = true; + break; + } + } + + if (!uid_read) { + const char *env_var_name = "OPENAPPSEC_UID"; // Replace with your environment variable name + const char *env_value = getenv(env_var_name); + + if (env_value) { + strncpy(attachment->container_id, env_value, MAX_CONTAINER_ID_LEN - 1); + attachment->container_id[MAX_CONTAINER_ID_LEN - 1] = '\0'; + uid_read = true; + } + } + + free(line); + fclose(file); + + if (!uid_read) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Severe error - failed to get uid!"); + } + + return NANO_OK; +} + +/// +///@brief Sends registration data to the registration socket. +/// +/// This function sends registration data to the registration socket of the given NanoAttachment. +/// It sends the attachment type, worker ID, total number of workers, +/// and the size and content of the container id (docker ID). +/// If any of the send operations fail, it returns NANO_ERROR; otherwise, it returns NANO_OK. +/// +///@param attachment The NanoAttachment containing the registration socket and other necessary information. +///@return NanoCommunicationResult Returns NANO_OK if the registration data is successfully sent, otherwise NANO_ERROR. +/// +NanoCommunicationResult +send_registration_data_to_registration_socket(NanoAttachment *attachment) +{ + uint8_t attachment_type = attachment->attachment_type; + uint8_t worker_id = attachment->worker_id + 1; + uint8_t workers_amount = attachment->num_of_workers; + struct timeval absolute_timeout = get_absolute_timeout_val_sec(1); + uint8_t container_id_size = strlen(attachment->container_id); + NanoCommunicationResult res; + + // Send to the attachment manager the following details: + // 1. The type of the attachment (fixed NGINX). + // 2. The number of this worker. + // 3. The total amount of workers. + // 4. The size of the docker ID. + // 5. If the docker ID isn't empty (size 0), the docker id itself. + // If any of these fail - return an error. + res = write_to_service( + attachment, + &attachment->registration_socket, + &attachment_type, + sizeof(attachment_type), + &absolute_timeout + ); + if (res != NANO_OK) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Failed to send attachment type"); + return NANO_ERROR; + } + + res = write_to_service( + attachment, + &attachment->registration_socket, + &worker_id, + sizeof(worker_id), + &absolute_timeout + ); + if (res != NANO_OK) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Failed to send worker ID"); + return NANO_ERROR; + } + + res = write_to_service( + attachment, + &attachment->registration_socket, + &workers_amount, + sizeof(workers_amount), + &absolute_timeout + ); + if (res != NANO_OK) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Failed to send workers amount"); + return NANO_ERROR; + } + + res = write_to_service( + attachment, + &attachment->registration_socket, + &container_id_size, + sizeof(container_id_size), + &absolute_timeout + ); + if (res != NANO_OK) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Failed to send container id size"); + return NANO_ERROR; + } + + if (container_id_size > 0) { + res = write_to_service( + attachment, + &attachment->registration_socket, + attachment->container_id, + container_id_size, + &absolute_timeout + ); + if (res != NANO_OK) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Failed to send container id"); + return NANO_ERROR; + } + } + + return NANO_OK; +} + + +/// +/// @brief Reads the verdict signal path from the registration socket. +/// +/// This function reads the verdict signal path from the registration socket of the given NanoAttachment. +/// It first reads the length of the signal path, then reads the signal path itself. +/// If the read operations fail or the path length exceeds the maximum allowed length, it returns NANO_ERROR; +/// otherwise, it returns NANO_OK. +/// +/// @param attachment The NanoAttachment containing the registration socket and other necessary information. +/// @return NanoCommunicationResult Returns NANO_OK if the signal path is successfully read, otherwise NANO_ERROR. +/// +NanoCommunicationResult +read_verdict_signal_path_from_registration_socket(NanoAttachment *attachment) +{ + uint8_t path_length; + int registration_socket = attachment->registration_socket; + uint8_t worker_id = attachment->worker_id + 1; + uint8_t workers_amount = attachment->num_of_workers; + NanoCommunicationResult res; + struct timeval absolute_timeout = get_absolute_timeout_val_sec(1); + // Read from the attachment manager: + // 1. The length of signal path. + // 2. The signal path itself. + // If that fails - return an error. + res = read_from_service( + attachment, + &attachment->registration_socket, + &path_length, + sizeof(path_length), + &absolute_timeout + ); + if (res != NANO_OK) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Failed to read path length"); + return NANO_ERROR; + } + + if (path_length >= MAX_SHARED_MEM_PATH_LEN) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Verdict path length is too long"); + return NANO_ERROR; + } + + res = read_from_service( + attachment, + &attachment->registration_socket, + attachment->shared_verdict_signal_path, + path_length, + &absolute_timeout + ); + if (res != NANO_OK) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Failed to read socket path"); + return NANO_ERROR; + } + + // Successfully go the shared communication path - add null termination and exit. + attachment->shared_verdict_signal_path[path_length] = '\0'; + write_dbg( + attachment, + 0, + DBG_LEVEL_DEBUG, + "Successfully registered on client. socket: %d, instance ID: %u, instances amount: %u, received path: %s", + registration_socket, + (uint32_t)worker_id, + (uint32_t)workers_amount, + attachment->shared_verdict_signal_path + ); + return NANO_OK; +} + +NanoCommunicationResult +register_to_attachments_manager(NanoAttachment *attachment) +{ + NanoCommunicationResult res; + HttpEventThreadCtx ctx; + int t_res; + + // If there was an old socket, close it. + if (attachment->registration_socket > 0) { + close(attachment->registration_socket); + attachment->registration_socket = -1; + } + + write_dbg(attachment, 0, DBG_LEVEL_DEBUG, "spawn RegistrationSocketThread"); + t_res = NanoRunInThreadTimeout( + attachment, + NULL, + RegistrationSocketThread, + (void *)&ctx, + attachment->registration_thread_timeout_msec, + "RegistrationSocketThread", + REGISTRATION + ); + + if (!t_res || ctx.res != NANO_OK) { + write_dbg( + attachment, + 0, + DBG_LEVEL_WARNING, + "Failed to connect to registration socket" + ); + close(attachment->registration_socket); + attachment->registration_socket = -1; + return NANO_ERROR; + } + + res = send_registration_data_to_registration_socket(attachment); + if (res != NANO_OK) { + write_dbg( + attachment, + 0, + DBG_LEVEL_WARNING, + "Failed to send registration data" + ); + close(attachment->registration_socket); + attachment->registration_socket = -1; + return NANO_ERROR; + } + + res = read_verdict_signal_path_from_registration_socket(attachment); + if (res != NANO_OK) { + write_dbg( + attachment, + 0, + DBG_LEVEL_WARNING, + "Failed to read verdict signal path" + ); + close(attachment->registration_socket); + attachment->registration_socket = -1; + return NANO_ERROR; + } + + close(attachment->registration_socket); + attachment->registration_socket = -1; + return NANO_OK; +} + +NanoCommunicationResult +set_unique_id(NanoAttachment *attachment) +{ + unsigned int unique_id_size = 0; + long unsigned int nano_worker_id = attachment->worker_id + 1; + + if (strlen(attachment->container_id) > 0) { + unique_id_size += snprintf( + attachment->unique_id, + MAX_NGINX_UID_LEN, + "%s_%lu", + attachment->container_id, + nano_worker_id); + } else { + unique_id_size += snprintf(attachment->unique_id, MAX_NGINX_UID_LEN, "%lu", nano_worker_id); + } + + if (unique_id_size <= 0) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Failed to set attachment's unique_id"); + return NANO_ERROR; + } + + if (unique_id_size >= MAX_NGINX_UID_LEN) { + write_dbg(attachment, 0, DBG_LEVEL_INFO, "Unique ID is too long, trancheated to: %s", attachment->unique_id); + } + + write_dbg(attachment, 0, DBG_LEVEL_INFO, "Successfully set attachment's unique_id: '%s'", attachment->unique_id); + return NANO_OK; +} + +NanoCommunicationResult +set_logging_fd(NanoAttachment *attachment, int logging_fd) +{ + char full_logging_path[128]; + + if (logging_fd > 0) { + attachment->is_default_fd = 0; + attachment->logging_fd = logging_fd; + write_dbg(attachment, 0, DBG_LEVEL_DEBUG, "Successfully set provided logging_fd"); + return NANO_OK; + } + + if (createDirectory(attachment, LOGGING_DIRECTORY_PATH) != NANO_OK) { + return NANO_ERROR; + } + + snprintf(full_logging_path, sizeof(full_logging_path), "%s-%s.dbg", LOGGING_FILE_PATH, attachment->container_id); + attachment->logging_fd = open(full_logging_path, O_WRONLY | O_CREAT | O_APPEND, 0644); + + if (attachment->logging_fd < 0) { + return NANO_ERROR; + } + + attachment->is_default_fd = 1; + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Successfully opened logging file"); + return NANO_OK; +} + +void +close_logging_fd(NanoAttachment *attachment) +{ + if (attachment->logging_fd > 0 && attachment->is_default_fd) { + close(attachment->logging_fd); + attachment->logging_fd = -1; + } + free(attachment->logging_data); + attachment->logging_data = NULL; +} + +NanoCommunicationResult +nano_attachment_init_process(NanoAttachment *attachment) +{ + attachment->nano_user_id = getuid(); + attachment->nano_group_id = getgid(); + attachment->num_of_connection_attempts++; + + init_attachment_config(attachment, SHARED_ATTACHMENT_CONF_PATH); + + if (access(SHARED_REGISTRATION_SIGNAL_PATH, F_OK) != 0) { + write_dbg(attachment, 0, DBG_LEVEL_TRACE, "Attachment registration manager is turned off"); + return NANO_ABORT; + } + + if (attachment->registration_state == PENDING) { + write_dbg(attachment, 0, DBG_LEVEL_INFO, "Registration to the Attachments Manager is in process"); + return NANO_ERROR; + } + + if (attachment->registration_state == NOT_REGISTERED) { + // Register with the attachment manager. + if (register_to_attachments_manager(attachment) == NANO_ERROR) { + write_dbg(attachment, 0, DBG_LEVEL_INFO, "Failed to register to Attachments Manager service"); + return NANO_ERROR; + } + attachment->registration_state = REGISTERED; + } + + if (init_attachment_config(attachment, SHARED_ATTACHMENT_CONF_PATH) == NANO_ERROR) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Failed to initialize attachment's configuration"); + return NANO_ERROR; + } + + if (attachment->comm_socket < 0) { + // Signal to the service to start communication. + write_dbg(attachment, 0, DBG_LEVEL_DEBUG, "Registering to nano service"); + if (init_signaling_socket(attachment) == NANO_ERROR) { + write_dbg(attachment, 0, DBG_LEVEL_DEBUG, "Failed to register to the Nano Service"); + return NANO_ERROR; + } + } + + // Initalize the the communication channel with the service. + if (attachment->nano_service_ipc == NULL) { + write_dbg(attachment, 0, DBG_LEVEL_INFO, "Initializing IPC channel"); + attachment->nano_service_ipc = initIpc( + attachment->unique_id, + attachment->nano_user_id, + attachment->nano_group_id, + 0, + attachment->num_of_nano_ipc_elements, + attachment->logging_data, + write_dbg_impl + ); + if (attachment->nano_service_ipc == NULL) { + restart_communication(attachment); + write_dbg(attachment, 0, DBG_LEVEL_INFO, "Failed to initialize IPC with nano service"); + return NANO_ERROR; + } + } + + write_dbg( + attachment, + 0, + DBG_LEVEL_INFO, + "NGINX attachment (UID='%s') successfully registered to nano service after %d attempts.", + attachment->unique_id, + attachment->num_of_connection_attempts + ); + + attachment->num_of_connection_attempts = 0; + + return NANO_OK; +} + +NanoCommunicationResult +restart_communication(NanoAttachment *attachment) +{ + write_dbg(attachment, 0, DBG_LEVEL_TRACE, "Restarting communication channels with nano service"); + if (attachment->nano_service_ipc != NULL) { + destroyIpc(attachment->nano_service_ipc, 0); + attachment->nano_service_ipc = NULL; + } + + if (init_signaling_socket(attachment) == NANO_ERROR) { + if (register_to_attachments_manager(attachment) == NANO_ERROR) { + write_dbg(attachment, 0, DBG_LEVEL_DEBUG, "Failed to register to Attachments Manager service"); + return NANO_ERROR; + } + + if (init_signaling_socket(attachment) == NANO_ERROR) { + write_dbg(attachment, 0, DBG_LEVEL_DEBUG, "Failed to init the signaling socket"); + return NANO_ERROR; + } + } + attachment->nano_service_ipc = initIpc( + attachment->unique_id, + attachment->nano_user_id, + attachment->nano_group_id, + 0, + attachment->num_of_nano_ipc_elements, + attachment->logging_data, + write_dbg_impl + ); + if (attachment->nano_service_ipc == NULL) { + write_dbg(attachment, 0, DBG_LEVEL_DEBUG, "Failed to init IPC"); + return NANO_ERROR; + } + return NANO_OK; +} + +void +disconnect_communication(NanoAttachment *attachment) +{ + write_dbg(attachment, 0, DBG_LEVEL_DEBUG, "Disconnecting communication channels with nano service"); + + if (attachment->comm_socket > 0) { + close(attachment->comm_socket); + attachment->comm_socket = -1; + } + if (attachment->nano_service_ipc != NULL) { + destroyIpc(attachment->nano_service_ipc, 0); + attachment->nano_service_ipc = NULL; + } +} + +NanoCommunicationResult +handle_shmem_corruption(NanoAttachment *attachment) +{ + NanoCommunicationResult res; + + if (attachment->nano_service_ipc == NULL) { + disconnect_communication(attachment); + res = restart_communication(attachment); + if (res != NANO_OK) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Failed to restart communication"); + return NANO_ERROR; + } + } + + if (isCorruptedShmem(attachment->nano_service_ipc, 0)) { + write_dbg(attachment, 0, DBG_LEVEL_WARNING, "Shared memory is corrupted! restarting communication"); + disconnect_communication(attachment); + return NANO_ERROR; + } + + return NANO_OK; +} + +int +isIpcReady(NanoAttachment *attachment) +{ + return attachment->nano_service_ipc != NULL && attachment->comm_socket > 0; +} diff --git a/attachments/nano_attachment/nano_initializer.h b/attachments/nano_attachment/nano_initializer.h new file mode 100644 index 0000000..f95030e --- /dev/null +++ b/attachments/nano_attachment/nano_initializer.h @@ -0,0 +1,218 @@ +// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// @file nano_initializer.h +#ifndef __NANO_INITIALIZER_H__ +#define __NANO_INITIALIZER_H__ + +#include +#include +#include + +#include "nano_attachment_common.h" +#include "shmem_ipc_2.h" + +#define LOGGING_DIRECTORY_PATH "/var/log/nano_attachment" ///< Default logging directory path. +#define LOGGING_FILE_NAME "nano_attachment" ///< Default logging file name. +#define LOGGING_FILE_PATH LOGGING_DIRECTORY_PATH "/" LOGGING_FILE_NAME + +typedef enum nano_attachment_registration_state { + NOT_REGISTERED, + PENDING, + REGISTERED +} nano_attachment_registration_state; ///< Indicates the current attachment registation stage. + +typedef struct NanoAttachment { + char unique_id[MAX_NGINX_UID_LEN]; // Holds the unique identifier for this instance. + char container_id[MAX_NGINX_UID_LEN]; // Holds the container id of the attachment. + char shared_verdict_signal_path[MAX_SHARED_MEM_PATH_LEN]; // Holds the path associating the attachment and service. + uint8_t worker_id; // Holds the worker number of the attachment. + uint8_t num_of_workers; // Holds the number of workers in the attachment. + uint32_t nano_user_id; // Holds the user id of the attachment. + uint32_t nano_group_id; // Holds the group id of the attachment. + int registration_socket; // Holds the file descriptor used for registering the instance. + nano_attachment_registration_state registration_state; // Holds the current attachment registation stage. + + uint8_t attachment_type; // Holds the type of the attachment. + SharedMemoryIPC *nano_service_ipc; // Holds the shared memory IPC of the nano service. + int comm_socket; // Holds the communication socket of the attachment. + + int is_default_fd; // Holds a value indicating if the logging file descriptor is the default one. + int logging_fd; // Holds the file descriptor for logging. + LoggingData *logging_data; // Holds the logging data of the attachment. + + NanoCommunicationResult is_configuration_updated; // Holds the result of the configuration update. + unsigned int current_config_version; // Holds the current configuration version. + + int fail_mode_verdict; ///< Fail open verdict incase of a timeout. + int fail_mode_delayed_verdict; ///< Fail open verdict incase of a timeout when waiting for delayed verdict. + nano_http_cp_debug_level_e dbg_level; ///< Default debug level. + int num_of_connection_attempts; ///< Maximum number of attempted connections. + unsigned int fail_open_timeout; ///< Fail open timeout in milliseconds. + unsigned int fail_open_delayed_timeout; ///< Fail open delayed timeout in milliseconds. + AttachmentVerdict sessions_per_minute_limit_verdict; ///< Session per minute limit verdict. + unsigned int max_sessions_per_minute; ///< Masimum session per minute. + unsigned int req_max_proccessing_ms_time; ///< Total Request processing timeout in milliseconds. + unsigned int res_max_proccessing_ms_time; ///< Total Response processing timeout in milliseconds. + unsigned int registration_thread_timeout_msec; ///< Registration timeout in milliseconds. + unsigned int req_start_thread_timeout_msec; ///< Request start processing timeout in milliseconds. + unsigned int req_header_thread_timeout_msec; ///< Request header processing timeout in milliseconds. + unsigned int req_body_thread_timeout_msec; ///< Request body processing timeout in milliseconds. + unsigned int res_header_thread_timeout_msec; ///< Response header processing timeout in milliseconds. + unsigned int res_body_thread_timeout_msec; ///< Response body processing timeout in milliseconds. + unsigned int waiting_for_verdict_thread_timeout_msec; ///< Wait thread processing timeout in milliseconds. + unsigned int metric_timeout_timeout; ///< Metric timeout in milliseconds. + NanoHttpInspectionMode inspection_mode; ///< Default inspection mode. + unsigned int num_of_nano_ipc_elements; ///< Number of NANO IPC elements. + uint64_t keep_alive_interval_msec; ///< Keep alive interval in milliseconds. + +#ifdef __cplusplus + uint64_t metric_data[static_cast(AttachmentMetricType::METRIC_TYPES_COUNT)]; + uint64_t metric_average_data_divisor[static_cast(AttachmentMetricType::METRIC_TYPES_COUNT)]; +#else + uint64_t metric_data[METRIC_TYPES_COUNT]; + uint64_t metric_average_data_divisor[METRIC_TYPES_COUNT]; +#endif +} NanoAttachment; + +/// +/// @brief Initialize all the attachments resources and communication channels. +/// @param[in] attachment Points to initiated NanoAttachment struct. +/// @returns NanoCommunicationResult +/// - #NANO_OK +/// - #NANO_ERROR +/// - #NANO_ABORT +/// +NanoCommunicationResult nano_attachment_init_process(NanoAttachment *attachment); + +/// +/// @brief Preforms send information to the service via a socket. +/// +/// This function writes data to the socket associated with the given NanoAttachment. +/// It writes data in parts if necessary, keeping track of the remaining data to be written. +/// If the write operation fails or exceeds the allowed timeout, it returns NANO_ERROR; +/// otherwise, it returns NANO_OK. +/// +/// @param Points to the NanoAttachment struct. +/// @param socket The socket to write to. +/// @param data The pointer to the data to be written. +/// @param size The size of the data to be written, excluding the null terminator. +/// @param absolute_end_time The absolute till the writing is allowed. +/// @return NanoCommunicationResult Returns NANO_OK if the write operation is successful, otherwise NANO_ERROR. +/// +NanoCommunicationResult write_to_service( + NanoAttachment *attachment, + int *socket, + void *data, + uint32_t size, + struct timeval *absolute_end_time); + +/// +/// @brief Preforms receive information from the service via a socket. +/// +/// This function reads data from the socket associated with the given NanoAttachment. +/// It reads data in parts if necessary, keeping track of the remaining data to be read. +/// It checks if the socket has data to be read prior to the read operation to avoid blocking indefinitely. +/// If the read operation fails or exceeds the allowed timeout, it returns NANO_ERROR; +/// otherwise, it returns NANO_OK. +/// +/// @param attachment Points to the NanoAttachment struct. +/// @param socket The socket to read from. +/// @param data The pointer to the buffer where the read data will be stored. +/// @param size The size of the data to be read. +/// @param absolute_end_time The absolute till the reading is allowed. +/// @return NanoCommunicationResult Returns NANO_OK if the read operation is successful, otherwise NANO_ERROR. +/// +NanoCommunicationResult read_from_service( + NanoAttachment *attachment, + int *socket, + void *data, + uint32_t size, + struct timeval *absolute_end_time); + +/// +/// @brief Sets a unique identifier for the NanoAttachment based on its container ID and worker ID. +/// +/// @param attachment Pointer to the NanoAttachment structure for which to set the unique ID. +/// @return NANO_OK if the unique ID was successfully set, otherwise an error code. +/// +NanoCommunicationResult set_unique_id(NanoAttachment *attachment); + +/// +/// @brief Sets the container ID for the NanoAttachment by reading it from CONTAINER_ID_FILE_PATH value. +/// +/// @param attachment Pointer to the NanoAttachment structure for which to set the container ID. +/// @return NANO_OK if the container ID was successfully set, otherwise an error code. +/// +NanoCommunicationResult set_docker_id(NanoAttachment *attachment); + +/// +/// @brief Sets the file descriptor for logging in the NanoAttachment structure. +/// +/// If an invalid fd passed, the default logging file descriptor is set to write to LOGGING_FILE_PATH variable. +/// +/// @param attachment Pointer to the NanoAttachment struct to set the logging file descriptor. +/// @param logging_fd The file descriptor to set. +/// @return NANO_OK if the logging file descriptor is set successfully, NANO_ERROR otherwise. +/// +NanoCommunicationResult set_logging_fd(NanoAttachment *attachment, int logging_fd); + +/// +/// @brief Closes the logging file descriptor in the NanoAttachment structure. +/// +/// @param attachment Pointer to the NanoAttachment struct to close the logging file descriptor. +/// +void close_logging_fd(NanoAttachment *attachment); + +/// +/// @brief Closes any existing communication to the service and tries to open a new one. +/// @param[in] attachment Points to initiated NanoAttachment struct. +/// @returns NanoCommunicationResult +/// - #NANO_OK +/// - #NANO_ERROR +/// +NanoCommunicationResult restart_communication(NanoAttachment *attachment); + +/// +/// @brief Checks that the shared memory with the service isn't corrupted, disconnect if it is. +/// @param[in] attachment Points to initiated NanoAttachment struct. +/// @returns NanoCommunicationResult +/// - #NANO_OK +/// - #NANO_ERROR +/// +NanoCommunicationResult handle_shmem_corruption(NanoAttachment *attachment); + +/// +/// @brief Closes all the communication channels with the service. +/// @param[in] attachment Points to initiated NanoAttachment struct. +/// +void disconnect_communication(NanoAttachment *attachment); + +/// +/// @brief Checks if communication with the service is up and running. +/// @param[in] attachment Points to initiated NanoAttachment struct. +/// @returns NanoCommunicationResult - 1 if communication is active, otherwise 0. +/// +int isIpcReady(NanoAttachment *attachment); + +/// +/// @brief Register the attachment instance with the attachment manager to associate it with a service. +/// @param[in] attachment Points to initiated NanoAttachment struct. +/// @returns NanoCommunicationResult +/// - #NANO_OK +/// - #NANO_ERROR +/// +NanoCommunicationResult register_to_attachments_manager(NanoAttachment *attachment); + +#endif // __NANO_INITIALIZER_H__ diff --git a/attachments/nano_attachment/nano_utils.c b/attachments/nano_attachment/nano_utils.c new file mode 100644 index 0000000..e0bdec1 --- /dev/null +++ b/attachments/nano_attachment/nano_utils.c @@ -0,0 +1,131 @@ +// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// @file nano_utils.c +#include "nano_utils.h" + +#include "nano_attachment_common.h" + +#include +#include +#include +#include +#include + + +/// +/// @brief Gets the current time using a fast, coarse-grained clock. +/// +/// This function uses CLOCK_MONOTONIC_COARSE to retrieve the current time, +/// which provides a fast timestamp. The function returns +/// the current time as a struct timeval, which represents seconds and microseconds. +/// +/// @return struct timeval The current time as seconds and microseconds. +/// +static struct timeval +getCurrTimeFast() +{ + struct timeval curr_time; + struct timespec curr_time_mono; + + clock_gettime(CLOCK_MONOTONIC_COARSE, &curr_time_mono); + + curr_time.tv_sec = curr_time_mono.tv_sec; + curr_time.tv_usec = curr_time_mono.tv_nsec/1000.0; + return curr_time; +} + +void +write_dbg_impl( + const LoggingData *logging_data, + uint32_t session_id, + int _dbg_level, + const char *func, + const char *file, + int line_num, + const char *fmt, + ... +) +{ + if (logging_data == NULL) return; + + if (_dbg_level < logging_data->dbg_level) return; + + char debug_str[2048] = {0}; + char session_id_str[32] = {0}; + char unique_id[32] = "uniqueId"; + va_list args; + time_t ttime; + int millisec; + struct timeval tv; + char time_stamp[64]; + char str_uid[140]; + int pid = 0; + + time(&ttime); + tv = getCurrTimeFast(); + millisec = lrint(tv.tv_usec/1000.0); + if (millisec>=1000) { + // Allow for rounding up to nearest second + millisec -=1000; + tv.tv_sec++; + } + strftime(time_stamp, sizeof(time_stamp), "%FT%T", localtime(&ttime)); + + if (!pid) pid = getpid(); + + if (session_id > 0) { + snprintf(session_id_str, sizeof(session_id_str) - 1, " ", session_id); + } + + // Prints the debug given all the data and a formatter. + snprintf( + str_uid, + sizeof(str_uid) - 1, + "|%s.%03d: %s@%s:%d [uid %s | pid %d] %s| ", + time_stamp, + millisec, + func, + file, + line_num, + unique_id, + pid, + session_id_str + ); + + va_start(args, fmt); + vsnprintf(debug_str, sizeof(debug_str) - 1, fmt, args); + + va_end(args); + dprintf(logging_data->fd, "%s%s\n", str_uid, debug_str); +} + +struct timeval +get_absolute_timeout_val_sec(const int delta_time_in_sec) +{ + struct timeval time; + + time = getCurrTimeFast(); + time.tv_sec += delta_time_in_sec; + return time; +} + +int +is_absolute_timeout_reached(struct timeval *timeout) +{ + struct timeval curr_time; + + curr_time = getCurrTimeFast(); + return (timercmp(timeout, &curr_time, <)); +} diff --git a/attachments/nano_attachment/nano_utils.h b/attachments/nano_attachment/nano_utils.h new file mode 100644 index 0000000..ce4485f --- /dev/null +++ b/attachments/nano_attachment/nano_utils.h @@ -0,0 +1,90 @@ +// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// @file nano_utils.h +#ifndef __NANO_UTILS_H__ +#define __NANO_UTILS_H__ + +#include +#include + +#include "nano_initializer.h" + +typedef struct LoggingData { + int dbg_level; + int worker_id; + int fd; +} LoggingData; + +#ifndef __FILENAME__ +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) +#endif + +#define write_dbg(attachment, session_id, _dbg_level, fmt, ...) \ + { \ + write_dbg_impl( \ + attachment->logging_data, \ + session_id, \ + _dbg_level, \ + __func__, \ + __FILENAME__, \ + __LINE__, \ + fmt, \ + ##__VA_ARGS__ \ + ); \ + if ((_dbg_level) == DBG_LEVEL_ASSERT) assert(0); \ + } + +/// +/// @brief Writing into debug implementation. +/// @param[in] LoggingData Logging data. +/// @param[in] session_id Session ID. +/// @param[in] _dbg_level Debug level to write into. +/// @param[in] func Function name from which the write debug was called from. +/// @param[in] file File from which the debug function was called from. +/// @param[in] line_num Line number of the write debug was called on. +/// @param[in] fmt Debug formatter. +/// @param[in] ... Extra values to write into the debug using the formatter. +/// +void +write_dbg_impl( + const LoggingData *logging_data, + uint32_t session_id, + int _dbg_level, + const char *func, + const char *file, + int line_num, + const char *fmt, + ... +); + +/// +/// @brief Get delta current time + delta_time_in_sec value in seconds. +/// @param[in] delta_time_in_sec Delta time to return +/// @returns timeval struct with tv_sec value of += delta_time_in_sec. +/// +struct timeval get_absolute_timeout_val_sec(const int delta_time_in_sec); + +/// +/// @brief Check if a timeout has been reached. +/// +/// This function compares the specified timeout value with the current time +/// to determine if the timeout has been reached. +/// +/// @param[in] timeout A pointer to a struct timeval representing the timeout value. +/// @return 1 if the timeout has been reached, 0 otherwise. +/// +int is_absolute_timeout_reached(struct timeval *timeout); + +#endif // __NANO_UTILS_H__ diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 3c75a10..8285065 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(shmem_ipc) +add_subdirectory(shmem_ipc_2) add_subdirectory(compression) add_subdirectory(attachments) diff --git a/core/include/attachments/nano_attachment.h b/core/include/attachments/nano_attachment.h new file mode 100755 index 0000000..ec78849 --- /dev/null +++ b/core/include/attachments/nano_attachment.h @@ -0,0 +1,258 @@ +#ifndef __NANO_ATTACHMENT_H__ +#define __NANO_ATTACHMENT_H__ + +#include "nano_attachment_common.h" + +typedef struct NanoAttachment NanoAttachment; + +/// +/// @brief Initializes a NanoAttachment structure. +/// +/// This function initializes a NanoAttachment structure with the specified parameters and default values. +/// +/// @param attachment_type The type of attachment to initialize. +/// @param worker_id The ID of the worker associated with the attachment. +/// @param num_of_workers The total number of workers. +/// @param logging_fd The file descriptor for logging. +/// +/// @return A pointer to the initialized NanoAttachment structure if the function completes, NULL otherwise. +/// +NanoAttachment * InitNanoAttachment(uint8_t attachment_type, int worker_id, int num_of_workers, int logging_fd); + +/// +/// @brief Cleans up resources associated with a NanoAttachment structure and deallocates memory. +/// +/// This function performs cleanup operations on a NanoAttachment structure and deallocates +/// the memory associated with it. +/// The function closes the logging file descriptor associated with the NanoAttachment +/// and frees the memory allocated for the structure. +/// +/// @param attachment A pointer to the NanoAttachment structure to be cleaned up. +/// +void FiniNanoAttachment(NanoAttachment *attachment); + +/// +/// @brief Restarts the configuration of a NanoAttachment. +/// +/// @param attachment A pointer to the NanoAttachment whose configuration is to be restarted. +/// +/// @return A NanoCommunicationResult indicating the success or failure of the operation. + +NanoCommunicationResult RestartAttachmentConfiguration(NanoAttachment *attachment); + +/// +/// @brief Initializes a HttpSessionData structure with default values. +/// +/// This function dynamically allocates memory for a HttpSessionData structure +/// and initializes its fields with default values. +/// +/// @param attachment A pointer to the NanoAttachment structure associated with the session. +/// @param session_id The ID of the session to be initialized. +/// +/// @return A pointer to the initialized HttpSessionData structure if the function completes, NULL otherwise. +/// +HttpSessionData * InitSessionData(NanoAttachment *attachment, SessionID session_id); + +/// +/// @brief Cleans up and deallocates resources associated with a HttpSessionData structure. +/// +/// This function performs cleanup operations on a HttpSessionData structure and deallocates +/// the memory associated with it. It writes a debug message indicating the session ID being +/// freed, and then frees the memory allocated for the HttpSessionData structure. +/// +/// @param attachment A pointer to the NanoAttachment structure associated with the session. +/// @param session_data A pointer to the HttpSessionData structure to be cleaned up. +/// +void FiniSessionData(NanoAttachment *attachment, HttpSessionData *session_data); + +/// +/// @brief Updates a metric associated with a NanoAttachment. +/// +/// This function updates a metric associated with a NanoAttachment structure +/// based on the provided metric type and value. It delegates the actual updating +/// of the metric to the helper function updateMetricField. +/// +/// @param attachment A pointer to the NanoAttachment structure associated with the metric. +/// @param metric The type of metric to be updated. +/// @param value The value to be incorporated into the metric calculation. +/// +void UpdateMetric(NanoAttachment *attachment, AttachmentMetricType metric, uint64_t value); + +/// +/// @brief Sends metric data that been accumulated in the attachment to the service. +/// +/// @param attachment A pointer to the NanoAttachment structure associated with the metric. +/// +void SendAccumulatedMetricData(NanoAttachment *attachment); + +/// +/// @brief Processes and sends attachment data to the appropriate handlers. +/// +/// This function processes the attachment data based on its chunk type and sends +/// it to the appropriate handler functions. If the chunk type is not recognized, +/// it sets a default verdict of ATTACHMENT_VERDICT_INSPECT and returns an AttachmentVerdictResponse +/// structure containing the default verdict and the session ID from the provided AttachmentData. +/// +/// @param attachment A pointer to the NanoAttachment structure associated with the data. +/// @param data A pointer to the AttachmentData structure containing the data to be processed. +/// +/// @return An AttachmentVerdictResponse structure containing the verdict and session ID. +/// +AttachmentVerdictResponse SendDataNanoAttachment(NanoAttachment *attachment, AttachmentData *data); + +/// +/// @brief Sends a keep-alive signal using a socket connection. +/// +/// @param attachment A pointer to a NanoAttachment struct containing attachment information. +/// +void SendKeepAlive(NanoAttachment *attachment); + +/// +/// @brief Checks if a session is finalized based on the session's verdict. +/// +/// @param attachment The NanoAttachment object associated with the session. +/// @param session_data The HttpSessionData object representing the session. +/// +/// @return Returns 0 if the session is not finalized, 1 otherwise. +/// +int IsSessionFinalized(NanoAttachment *attachment, HttpSessionData *session_data); + +/// +/// @brief Checks if the response contains modifications. +/// +/// This function determines whether the provided response contains modifications. +/// +/// @param attachment A pointer to a NanoAttachment structure representing the attachment. +/// @param session_data A pointer to a HttpSessionData structure containing session data. +/// @param response A pointer to an AttachmentVerdictResponse structure representing the response. +/// +/// @return 1 if the response contains modifications, 0 otherwise. +/// +int IsResponseWithModification( + NanoAttachment *attachment, + HttpSessionData *session_data, + AttachmentVerdictResponse *response +); + +/// +/// @brief Retrieves response modifications from the given attachment and session data. +/// +/// @param attachment Pointer to a NanoAttachment object. +/// @param session_data Pointer to HttpSessionData object containing session information. +/// @param response Pointer to an AttachmentVerdictResponse object. +/// +/// @return NanoResponseModifications structure containing response modifications. +/// +NanoResponseModifications GetResponseModifications( + NanoAttachment *attachment, + HttpSessionData *session_data, + AttachmentVerdictResponse *response +); + +/// +/// @brief Retrieves the type of web response associated with the given attachment and session data. +/// +/// This function checks if the provided response object contains valid web response data. +/// If the response object is null, it logs a warning and returns NO_WEB_RESPONSE. +/// Otherwise, it returns the type of web response contained in the response object. +/// +/// @param attachment Pointer to the NanoAttachment structure associated with the request. +/// @param session_data Pointer to the HttpSessionData structure containing session-related data. +/// @param response Pointer to the AttachmentVerdictResponse structure containing response data. +/// +/// @return The type of web response, or NO_WEB_RESPONSE if no response object is provided. +/// +NanoWebResponseType GetWebResponseType( + NanoAttachment *attachment, + HttpSessionData *session_data, + AttachmentVerdictResponse *response +); + +/// +/// @brief Retrieves the block page data for a response. +/// +/// @param attachment The NanoAttachment object associated with the session. +/// @param session_data The HttpSessionData object representing the session. +/// @param response The AttachmentVerdictResponse object containing the verdict. +/// +/// @return +/// +BlockPageData GetBlockPage( + NanoAttachment *attachment, + HttpSessionData *session_data, + AttachmentVerdictResponse *response +); + +/// +/// @brief Retrieves the redict page data for a response. +/// +/// @param attachment The NanoAttachment object associated with the session. +/// @param session_data The HttpSessionData object representing the session. +/// @param response The AttachmentVerdictResponse object containing the verdict. +/// +/// @return +/// +RedirectPageData GetRedirectPage( + NanoAttachment *attachment, + HttpSessionData *session_data, + AttachmentVerdictResponse *response +); + +/// +/// @brief Free allocated resources of an AttachmentVerdictResponse. +/// +/// This function frees the allocated resources of an AttachmentVerdictResponse. +/// +/// @param attachment The NanoAttachment object associated with the session. +/// @param session_data The HttpSessionData object representing the session. +/// @param response The AttachmentVerdictResponse object to be freed. +/// +void FreeAttachmentResponseContent( + NanoAttachment *attachment, + HttpSessionData *session_data, + AttachmentVerdictResponse *response +); + +/// +/// @brief Compresses HttpBody and return allocated compressed body. +/// +/// @param attachment The NanoAttachment object associated with the session. +/// @param session_data The HttpSessionData object representing the session. +/// @param bodies The bodies pointer to be compressed. +/// +HttpBody * compressBody( + NanoAttachment *attachment, + HttpSessionData *session_data, + HttpBody *bodies +); + +/// +/// @brief Compresses HttpBody and return allocated compressed body. +/// +/// @param attachment The NanoAttachment object associated with the session. +/// @param session_data The HttpSessionData object representing the session. +/// @param bodies The bodies pointer to be decompressed. +/// +HttpBody * decompressBody( + NanoAttachment *attachment, + HttpSessionData *session_data, + HttpBody *bodies +); + +/// +/// @brief Free allocated compressed body. +/// +/// This function frees the allocated resources of HttpBody object. +/// +/// @param attachment The NanoAttachment object associated with the session. +/// @param session_data The HttpSessionData object representing the session. +/// @param bodies The bodies pointer to be freed. +/// +void +freeCompressedBody( + NanoAttachment *attachment, + HttpSessionData *session_data, + HttpBody *bodies +); + +#endif // __NANO_ATTACHMENT_H__ diff --git a/core/include/attachments/nano_attachment_common.h b/core/include/attachments/nano_attachment_common.h new file mode 100644 index 0000000..c043965 --- /dev/null +++ b/core/include/attachments/nano_attachment_common.h @@ -0,0 +1,489 @@ +#ifndef __NANO_ATTACHMENT_COMMON_H__ +#define __NANO_ATTACHMENT_COMMON_H__ + +#include +#include +#include +#include +#include + +#include "compression_utils.h" + +typedef uint32_t SessionID; +typedef void* DataBuffer; + +#define MAX_NGINX_UID_LEN 32 +#define MAX_SHARED_MEM_PATH_LEN 128 +#define NUM_OF_NGINX_IPC_ELEMENTS 200 +#define DEFAULT_KEEP_ALIVE_INTERVAL_MSEC 300000u +#define SHARED_MEM_PATH "/dev/shm/" +#define SHARED_REGISTRATION_SIGNAL_PATH SHARED_MEM_PATH "check-point/cp-nano-attachment-registration" +#define SHARED_KEEP_ALIVE_PATH SHARED_MEM_PATH "check-point/cp-nano-attachment-registration-expiration-socket" +#define SHARED_VERDICT_SIGNAL_PATH SHARED_MEM_PATH "check-point/cp-nano-http-transaction-handler" +#define SHARED_ATTACHMENT_CONF_PATH SHARED_MEM_PATH "cp_nano_http_attachment_conf" +#define DEFAULT_STATIC_RESOURCES_PATH SHARED_MEM_PATH "static_resources" +#define INJECT_POS_IRRELEVANT -1 +#define CORRUPTED_SESSION_ID 0 +#define METRIC_PERIODIC_TIMEOUT 600 +#define MAX_CONTAINER_ID_LEN 12 +#define CONTAINER_ID_FILE_PATH "/proc/self/cgroup" +#define RESPONSE_PAGE_PARTS 4 +#define UUID_SIZE 64 +#define CUSTOM_RESPONSE_TITLE_SIZE 64 +#define CUSTOM_RESPONSE_BODY_SIZE 128 +#define REDIRECT_RESPONSE_LOCATION_SIZE 512 + +#ifdef __cplusplus +typedef enum class NanoWebResponseType +#else +typedef enum NanoWebResponseType +#endif +{ + CUSTOM_WEB_RESPONSE, + REDIRECT_WEB_RESPONSE, + + NO_WEB_RESPONSE +} NanoWebResponseType; + +#ifdef __cplusplus +typedef enum class NanoHttpInspectionMode +#else +typedef enum NanoHttpInspectionMode +#endif +{ + NON_BLOCKING_THREAD, + BLOCKING_THREAD, + NO_THREAD, + + INSPECTION_MODE_COUNT +} NanoHttpInspectionMode; + +#ifdef __cplusplus +typedef enum class NanoCommunicationResult +#else +typedef enum NanoCommunicationResult +#endif +{ + NANO_OK, + NANO_ERROR, + NANO_ABORT, + NANO_AGAIN, + NANO_HTTP_FORBIDDEN, + NANO_DECLINED, + NANO_TIMEOUT +} NanoCommunicationResult; + +#ifdef __cplusplus +typedef enum class nano_http_cp_debug_level +#else +typedef enum nano_http_cp_debug_level +#endif +{ + DBG_LEVEL_TRACE, + DBG_LEVEL_DEBUG, + DBG_LEVEL_INFO, + DBG_LEVEL_WARNING, + DBG_LEVEL_ERROR, +#ifndef __cplusplus + DBG_LEVEL_ASSERT, +#endif + DBG_LEVEL_COUNT +} nano_http_cp_debug_level_e; + +#ifdef __cplusplus +typedef enum class AttachmentMetricType +#else +typedef enum AttachmentMetricType +#endif +{ + TRANSPARENTS_COUNT, + TOTAL_TRANSPARENTS_TIME, + INSPECTION_OPEN_FAILURES_COUNT, + INSPECTION_CLOSE_FAILURES_COUNT, + INSPECTION_SUCCESSES_COUNT, + INJECT_VERDICTS_COUNT, + DROP_VERDICTS_COUNT, + ACCEPT_VERDICTS_COUNT, + IRRELEVANT_VERDICTS_COUNT, + RECONF_VERDICTS_COUNT, + INSPECT_VERDICTS_COUNT, + HOLD_VERDICTS_COUNT, + AVERAGE_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT, + MAX_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT, + MIN_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT, + AVERAGE_REQ_PPROCESSING_TIME_UNTIL_VERDICT, + MAX_REQ_PPROCESSING_TIME_UNTIL_VERDICT, + MIN_REQ_PPROCESSING_TIME_UNTIL_VERDICT, + AVERAGE_RES_PPROCESSING_TIME_UNTIL_VERDICT, + MAX_RES_PPROCESSING_TIME_UNTIL_VERDICT, + MIN_RES_PPROCESSING_TIME_UNTIL_VERDICT, + THREAD_TIMEOUT, + REG_THREAD_TIMEOUT, + REQ_METADATA_THREAD_TIMEOUT, + REQ_HEADER_THREAD_TIMEOUT, + REQ_BODY_THREAD_TIMEOUT, + REQ_END_THREAD_TIMEOUT, + AVERAGE_REQ_BODY_SIZE_UPON_TIMEOUT, + MAX_REQ_BODY_SIZE_UPON_TIMEOUT, + MIN_REQ_BODY_SIZE_UPON_TIMEOUT, + RES_HEADER_THREAD_TIMEOUT, + RES_BODY_THREAD_TIMEOUT, + RES_END_THREAD_TIMEOUT, + HOLD_THREAD_TIMEOUT, + AVERAGE_RES_BODY_SIZE_UPON_TIMEOUT, + MAX_RES_BODY_SIZE_UPON_TIMEOUT, + MIN_RES_BODY_SIZE_UPON_TIMEOUT, + THREAD_FAILURE, + REQ_PROCCESSING_TIMEOUT, + RES_PROCCESSING_TIMEOUT, + REQ_FAILED_TO_REACH_UPSTREAM, + REQ_FAILED_COMPRESSION_COUNT, + RES_FAILED_COMPRESSION_COUNT, + REQ_FAILED_DECOMPRESSION_COUNT, + RES_FAILED_DECOMPRESSION_COUNT, + REQ_SUCCESSFUL_COMPRESSION_COUNT, + RES_SUCCESSFUL_COMPRESSION_COUNT, + REQ_SUCCESSFUL_DECOMPRESSION_COUNT, + RES_SUCCESSFUL_DECOMPRESSION_COUNT, + CORRUPTED_ZIP_SKIPPED_SESSION_COUNT, + CPU_USAGE, + AVERAGE_VM_MEMORY_USAGE, + AVERAGE_RSS_MEMORY_USAGE, + MAX_VM_MEMORY_USAGE, + MAX_RSS_MEMORY_USAGE, + REQUEST_OVERALL_SIZE_COUNT, + RESPONSE_OVERALL_SIZE_COUNT, + + METRIC_TYPES_COUNT +} AttachmentMetricType; + +#ifdef __cplusplus +typedef enum class AttachmentDataType +#else +typedef enum AttachmentDataType +#endif +{ + REQUEST_START, + REQUEST_HEADER, + REQUEST_BODY, + REQUEST_END, + RESPONSE_CODE, + RESPONSE_HEADER, + RESPONSE_BODY, + RESPONSE_END, + CONTENT_LENGTH, + METRIC_DATA_FROM_PLUGIN, + REQUEST_DELAYED_VERDICT +} AttachmentDataType; + +#ifdef __cplusplus +typedef enum class HttpChunkType +#else +typedef enum HttpChunkType +#endif +{ + HTTP_REQUEST_FILTER, + HTTP_REQUEST_METADATA, + HTTP_REQUEST_HEADER, + HTTP_REQUEST_BODY, + HTTP_REQUEST_END, + HTTP_RESPONSE_HEADER, + HTTP_RESPONSE_BODY, + HTTP_RESPONSE_END, + HOLD_DATA +} HttpChunkType; + +#ifdef __cplusplus +typedef enum class ServiceVerdict +#else +typedef enum ServiceVerdict +#endif +{ + TRAFFIC_VERDICT_INSPECT, + TRAFFIC_VERDICT_ACCEPT, + TRAFFIC_VERDICT_DROP, + TRAFFIC_VERDICT_INJECT, + TRAFFIC_VERDICT_IRRELEVANT, + TRAFFIC_VERDICT_RECONF, + TRAFFIC_VERDICT_DELAYED +} ServiceVerdict; + +#ifdef __cplusplus +typedef enum class AttachmentVerdict +#else +typedef enum AttachmentVerdict +#endif +{ + ATTACHMENT_VERDICT_INSPECT, + ATTACHMENT_VERDICT_ACCEPT, + ATTACHMENT_VERDICT_DROP, + ATTACHMENT_VERDICT_INJECT +} AttachmentVerdict; + +#ifdef __cplusplus +typedef enum class HttpModificationType +#else +typedef enum HttpModificationType +#endif +{ + APPEND, + INJECT, + REPLACE +} HttpModificationType; + +typedef struct __attribute__((__packed__)) HttpInjectData { + int64_t injection_pos; + HttpModificationType mod_type; + uint16_t injection_size; + uint8_t is_header; + uint8_t orig_buff_index; + char data[0]; +} HttpInjectData; + +typedef struct __attribute__((__packed__)) HttpWebResponseData { + uint8_t web_response_type; + uint8_t uuid_size; + + union { + struct __attribute__((__packed__)) NanoHttpCpCustomWebResponseData { + uint16_t response_code; + uint8_t title_size; + uint8_t body_size; + char data[0]; + } custom_response_data; + + struct __attribute__((__packed__)) NanoHttpCpRedirectData { + uint8_t unused_dummy; + uint8_t add_event_id; + uint16_t redirect_location_size; + char redirect_location[0]; + } redirect_data; + } response_data; +} HttpWebResponseData; + +typedef struct { + size_t len; + unsigned char *data; +} nano_str_t; + +typedef struct CustomResponseData { + uint16_t response_code; + unsigned char title[CUSTOM_RESPONSE_TITLE_SIZE]; + unsigned char body[CUSTOM_RESPONSE_BODY_SIZE]; +} CustomResponseData; + +typedef struct RedirectData { + unsigned char redirect_location[REDIRECT_RESPONSE_LOCATION_SIZE]; +} RedirectData; + +typedef struct WebResponseData { + NanoWebResponseType web_response_type; + unsigned char uuid[UUID_SIZE]; + DataBuffer data; +} WebResponseData; + +#ifdef __cplusplus +typedef enum class HttpMetaDataType +#else +typedef enum HttpMetaDataType +#endif +{ + HTTP_PROTOCOL_SIZE, + HTTP_PROTOCOL_DATA, + HTTP_METHOD_SIZE, + HTTP_METHOD_DATA, + HOST_NAME_SIZE, + HOST_NAME_DATA, + LISTENING_ADDR_SIZE, + LISTENING_ADDR_DATA, + LISTENING_PORT, + URI_SIZE, + URI_DATA, + CLIENT_ADDR_SIZE, + CLIENT_ADDR_DATA, + CLIENT_PORT, + PARSED_HOST_SIZE, + PARSED_HOST_DATA, + PARSED_URI_SIZE, + PARSED_URI_DATA, + + META_DATA_COUNT +} HttpMetaDataType; + +#ifdef __cplusplus +typedef enum class HttpHeaderDataType +#else +typedef enum HttpHeaderDataType +#endif +{ + HEADER_KEY_SIZE, + HEADER_KEY_DATA, + HEADER_VAL_SIZE, + HEADER_VAL_DATA, + + HEADER_DATA_COUNT +} HttpHeaderDataType; + +/// @struct NanoHttpModificationList +/// @brief A node that holds all the information regarding modifications. +typedef struct NanoHttpModificationList { + struct NanoHttpModificationList *next; ///< Next node. + HttpInjectData modification; ///< Modification data. + char *modification_buffer; ///< Modification buffer used to store extra needed data. +} NanoHttpModificationList; + +/// @struct NanoHttpResponseData +/// Holds all the data for Compression in a session. +typedef struct { + + /// Original compression type, can hold the following values: + /// - #GZIP + /// - #ZLIB + CompressionType compression_type; + + /// Compression stream + CompressionStream *compression_stream; + + /// Decompression stream + CompressionStream *decompression_stream; +} NanoHttpResponseData; + +/// @struct HttpSessionData +/// @brief Holds all the session's information needed to communicate with the nano service. +/// @details Such as to save verdict and session ID between the request and the response +typedef struct HttpSessionData { + int was_request_fully_inspected; ///< Holds if the request fully inspected. + ServiceVerdict verdict; ///< Holds the session's verdict from the Nano Service. + uint32_t session_id; ///< Current session's Id. + unsigned int remaining_messages_to_reply; ///< Remaining messages left for the agent to respond to. + + NanoHttpResponseData response_data; ///< Holds session's response data. + + double req_proccesing_time; ///< Holds session's request processing time. + double res_proccesing_time; ///< Holds session's response processing time. + uint64_t processed_req_body_size; ///< Holds session's request body's size. + uint64_t processed_res_body_size; ///< Holds session's response body's size'. +} HttpSessionData; + +typedef struct HttpMetaData { + nano_str_t http_protocol; + nano_str_t method_name; + nano_str_t host; + nano_str_t listening_ip; + uint16_t listening_port; + nano_str_t uri; + nano_str_t client_ip; + uint16_t client_port; + nano_str_t parsed_host; + nano_str_t parsed_uri; +} HttpMetaData; + +typedef struct HttpHeaderData { + nano_str_t key; + nano_str_t value; +} HttpHeaderData; + +typedef struct HttpHeaders { + HttpHeaderData *data; + size_t headers_count; +} HttpHeaders; + +typedef struct HttpRequestFilterData { + HttpMetaData *meta_data; + HttpHeaders *req_headers; + bool contains_body; +} HttpRequestFilterData; + +typedef struct ResHttpHeaders { + HttpHeaders *headers; + uint16_t response_code; + uint64_t content_length; +} ResHttpHeaders; + +typedef struct HttpBody { + nano_str_t *data; + size_t bodies_count; +} HttpBody; + +typedef struct AttachmentData { + SessionID session_id; + HttpChunkType chunk_type; + HttpSessionData *session_data; + DataBuffer data; +} AttachmentData; + +typedef union __attribute__((__packed__)) HttpModifyData { + HttpInjectData inject_data[0]; + HttpWebResponseData web_response_data[0]; +} HttpModifyData; + +typedef struct __attribute__((__packed__)) HttpReplyFromService { + uint16_t verdict; + SessionID session_id; + uint8_t modification_count; + HttpModifyData modify_data[0]; +} HttpReplyFromService; + +typedef struct AttachmentVerdictResponse { + AttachmentVerdict verdict; + SessionID session_id; + WebResponseData *web_response_data; + NanoHttpModificationList *modifications; +} AttachmentVerdictResponse; + +typedef struct __attribute__((__packed__)) AttachmentRequest { + struct __attribute__((__packed__)) connection { + int sockaddr; + int local_sockaddr; + } connection; + + struct __attribute__((__packed__)) http_protocol { + int len; + int data; + } http_protocol; + + struct __attribute__((__packed__)) method { + int name; + int data; + } method; + + struct __attribute__((__packed__)) uri { + int len; + int data; + } uri; + + struct __attribute__((__packed__)) unparsed_uri { + int len; + int data; + } unparsed_uri; +} AttachmentRequest; + +typedef struct BlockPageData { + uint16_t response_code; + nano_str_t title_prefix; + nano_str_t title; + nano_str_t body_prefix; + nano_str_t body; + nano_str_t uuid_prefix; + nano_str_t uuid; + nano_str_t uuid_suffix; +} BlockPageData; + +typedef struct RedirectPageData { + nano_str_t redirect_location; +} RedirectPageData; + +typedef struct NanoResponseModifications { + NanoHttpModificationList *modifications; +} NanoResponseModifications; + +typedef struct __attribute__((__packed__)) NanoHttpMetricData { + uint16_t data_type; +#ifdef __cplusplus + uint64_t data[static_cast(AttachmentMetricType::METRIC_TYPES_COUNT)]; +#else + uint64_t data[METRIC_TYPES_COUNT]; +#endif +} NanoHttpMetricData; + +#endif // __NANO_ATTACHMENT_COMMON_H__ diff --git a/core/include/attachments/nano_attachment_util.h b/core/include/attachments/nano_attachment_util.h new file mode 100644 index 0000000..78135b8 --- /dev/null +++ b/core/include/attachments/nano_attachment_util.h @@ -0,0 +1,67 @@ +// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __NGINX_ATTACHMENT_UTIL__ +#define __NGINX_ATTACHMENT_UTIL__ + +#include + +#include "nano_attachment_common.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#define IP_STR_MAX_LEN 40 + +typedef const char * c_str; + +int initAttachmentConfig(c_str conf_file); + +NanoHttpInspectionMode getInspectionMode(); +unsigned int getNumOfNginxIpcElements(); +unsigned int getKeepAliveIntervalMsec(); +unsigned int getDbgLevel(); +int isDebugContext(c_str client, c_str server, unsigned int port, c_str method, c_str host, c_str uri); +c_str getStaticResourcesPath(); + +int isFailOpenMode(); +unsigned int getFailOpenTimeout(); + +int isFailOpenHoldMode(); +unsigned int getFailOpenHoldTimeout(); + +unsigned int getMaxSessionsPerMinute(); +int isFailOpenOnSessionLimit(); + +unsigned int getRegistrationThreadTimeout(); + +unsigned int getReqProccessingTimeout(); +unsigned int getReqHeaderThreadTimeout(); +unsigned int getReqBodyThreadTimeout(); + +unsigned int getResProccessingTimeout(); +unsigned int getResHeaderThreadTimeout(); +unsigned int getResBodyThreadTimeout(); + +unsigned int getWaitingForVerdictThreadTimeout(); + +int isIPAddress(c_str ip_str); +int isSkipSource(c_str ip_str); + +#ifdef __cplusplus +} +#endif + +#endif // __NGINX_ATTACHMENT_UTIL__ diff --git a/core/include/attachments/shmem_ipc_2.h b/core/include/attachments/shmem_ipc_2.h new file mode 100755 index 0000000..700c42d --- /dev/null +++ b/core/include/attachments/shmem_ipc_2.h @@ -0,0 +1,79 @@ +// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __SHMEM_IPC_H__ +#define __SHMEM_IPC_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +typedef struct LoggingData LoggingData; +typedef struct SharedMemoryIPC SharedMemoryIPC; +extern const int corrupted_shmem_error; + +LoggingData * initLoggingData(int dbg_level, int worker_id, int fd); + +SharedMemoryIPC * initIpc( + const char queue_name[32], + const uint32_t user_id, + const uint32_t group_id, + int is_owner, + uint16_t num_of_queue_elem, + const LoggingData *logging_data, + void (*debug_func)( + const LoggingData *loggin_data, + uint32_t worker_id, + int is_error, + const char *func, + const char *file, + int line_num, + const char *fmt, + ... + ) +); + +void destroyIpc(SharedMemoryIPC *ipc, int is_owner); + +int sendData(SharedMemoryIPC *ipc, const uint16_t data_to_send_size, const char *data_to_send); + +int +sendChunkedData( + SharedMemoryIPC *ipc, + const uint16_t *data_to_send_sizes, + const char **data_elem_to_send, + const uint8_t num_of_data_elem +); + +int receiveData(SharedMemoryIPC *ipc, uint16_t *received_data_size, const char **received_data); + +int popData(SharedMemoryIPC *ipc); + +int isDataAvailable(SharedMemoryIPC *ipc); + +void resetIpc(SharedMemoryIPC *ipc, uint16_t num_of_data_segments); + +void dumpIpcMemory(SharedMemoryIPC *ipc); + +int isCorruptedShmem(SharedMemoryIPC *ipc, int is_owner); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // __SHMEM_IPC_H__ diff --git a/core/shmem_ipc_2/CMakeLists.txt b/core/shmem_ipc_2/CMakeLists.txt new file mode 100755 index 0000000..2abc764 --- /dev/null +++ b/core/shmem_ipc_2/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library(shmem_ipc_2 SHARED shmem_ipc.c shared_ring_queue.c) + +target_link_libraries(shmem_ipc_2 -lrt) + +install(TARGETS shmem_ipc_2 DESTINATION lib) +install(TARGETS shmem_ipc_2 DESTINATION nginx_attachment/lib PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ) diff --git a/core/shmem_ipc_2/shared_ipc_debug.h b/core/shmem_ipc_2/shared_ipc_debug.h new file mode 100755 index 0000000..adee54a --- /dev/null +++ b/core/shmem_ipc_2/shared_ipc_debug.h @@ -0,0 +1,55 @@ +// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __SHARED_IPC_DEBUG_H__ +#define __SHARED_IPC_DEBUG_H__ + +typedef struct LoggingData { + int dbg_level; + int worker_id; + int fd; +} LoggingData; + +extern void (*debug_int)( + const LoggingData *loggin_data, + uint32_t worker_id, + int is_error, + const char *func, + const char *file, + int line_num, + const char *fmt, + ... +); + +#ifndef __FILENAME__ +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) +#endif + +enum debugLevel { TraceLevel = 0, DebugLevel = 1, WarningLevel = 3 }; + +#define writeDebug(logging_data, debug_level, fmt, ...) \ + { \ + debug_int( \ + logging_data, \ + (logging_data)->worker_id, \ + debug_level, \ + __func__, \ + __FILENAME__, \ + __LINE__, \ + fmt, \ + ##__VA_ARGS__ \ + ); \ + } + +#endif // __SHARED_IPC_DEBUG_H__ diff --git a/core/shmem_ipc_2/shared_ring_queue.c b/core/shmem_ipc_2/shared_ring_queue.c new file mode 100755 index 0000000..925460c --- /dev/null +++ b/core/shmem_ipc_2/shared_ring_queue.c @@ -0,0 +1,652 @@ +// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "shared_ring_queue.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shared_ipc_debug.h" + +static const uint16_t empty_buff_mgmt_magic = 0xfffe; +static const uint16_t skip_buff_mgmt_magic = 0xfffd; +static const uint32_t max_write_size = 0xfffc; +const uint16_t max_num_of_data_segments = sizeof(DataSegment)/sizeof(uint16_t); + +// LCOV_EXCL_START Reason: Handing it to Envoy prototype development + +static int +getNumOfDataSegmentsNeeded(LoggingData *logging_data, uint16_t data_size) +{ + int res = (data_size + SHARED_MEMORY_SEGMENT_ENTRY_SIZE - 1) / SHARED_MEMORY_SEGMENT_ENTRY_SIZE; + writeDebug( + logging_data, + TraceLevel, + "Checking amount of segments needed. Res: %d, data size: %u, shmem entry size: %u", + res, + data_size, + SHARED_MEMORY_SEGMENT_ENTRY_SIZE + ); + return res; +} + +static int +isThereEnoughMemoryInQueue( + LoggingData *logging_data, + SharedRingGlobalData *global_data, + uint16_t write_pos, + uint16_t read_pos, + uint8_t num_of_elem_to_push +) +{ + int res; + + writeDebug( + logging_data, + TraceLevel, "Checking if memory has space for new elements. " + "Num of elements to push: %u, write index: %u, read index: %u, amount of queue segments: %u", + num_of_elem_to_push, + write_pos, + read_pos, + global_data->g_num_of_data_segments + ); + if (num_of_elem_to_push >= global_data->g_num_of_data_segments) { + writeDebug( + logging_data, + TraceLevel, + "Amount of elements to push is larger then amount of available elements in the queue" + ); + return 0; + } + + // add skipped elements during write that does not fit from cur write position till end of queue + if (write_pos + num_of_elem_to_push > global_data->g_num_of_data_segments) { + num_of_elem_to_push += global_data->g_num_of_data_segments - write_pos; + } + + // removing the aspect of circularity in queue and simulating as if the queue continued at its end + if (write_pos + num_of_elem_to_push >= global_data->g_num_of_data_segments) { + read_pos += global_data->g_num_of_data_segments; + } + + res = write_pos + num_of_elem_to_push < read_pos || write_pos >= read_pos; + writeDebug(logging_data, TraceLevel, "Finished checking if there is enough place in shared memory. Res: %d", res); + return res; +} + +static int +isGetPossitionSucceccful( + SharedRingQueue *queue, + SharedRingGlobalData *global_data, + uint16_t *read_pos, + uint16_t *write_pos +) +{ + if (global_data->g_num_of_data_segments == 0) return 0; + + *read_pos = queue->read_pos; + *write_pos = queue->write_pos; + + if (queue->num_of_data_segments != global_data->g_num_of_data_segments) return 0; + if (queue->size_of_memory != global_data->g_memory_size) return 0; + if (*read_pos > global_data->g_num_of_data_segments) return 0; + if (*write_pos > global_data->g_num_of_data_segments) return 0; + + return 1; +} + +void +resetRingQueue(LoggingData *logging_data, SharedRingQueue *queue, uint16_t num_of_data_segments) +{ + (void)logging_data; + uint16_t *buffer_mgmt; + unsigned int idx; + + queue->read_pos = 0; + queue->write_pos = 0; + queue->num_of_data_segments = num_of_data_segments; + buffer_mgmt = (uint16_t *)queue->mgmt_segment.data; + for (idx = 0; idx < queue->num_of_data_segments; idx++) { + buffer_mgmt[idx] = empty_buff_mgmt_magic; + } +} + +SharedRingGlobalData * +createSharedRingGlobalData(LoggingData *logging_data) +{ + SharedRingGlobalData *global_data = (SharedRingGlobalData *)malloc(sizeof(SharedRingGlobalData)); + if (global_data == NULL) { + writeDebug(logging_data, WarningLevel, "Failed to allocate memory for global data\n"); + return NULL; + } + + global_data->g_rx_fd = -1; + global_data->g_tx_fd = -1; + global_data->g_memory_size = -1; + global_data->g_rx_location_name[0] = '\0'; + global_data->g_tx_location_name[0] = '\0'; + global_data->g_num_of_data_segments = 0; + + return global_data; +} + +SharedRingQueue * +createSharedRingQueue( + LoggingData *logging_data, + const char *shared_location_name, + uint16_t num_of_data_segments, + int is_owner, + int is_tx, + SharedRingGlobalData *global_data +) +{ + SharedRingQueue *queue = NULL; + uint16_t *buffer_mgmt; + uint16_t shmem_fd_flags = is_owner ? O_RDWR | O_CREAT : O_RDWR; + int32_t fd = -1; + uint32_t size_of_memory; + unsigned int idx; + + writeDebug(logging_data, TraceLevel, "Creating a new shared ring queue"); + + if (num_of_data_segments > max_num_of_data_segments) { + writeDebug( + logging_data, + WarningLevel, + "createSharedRingQueue: Cannot create data segment with %d elements (max number of elements is %u)\n", + num_of_data_segments, + max_num_of_data_segments + ); + return NULL; + } + + global_data->g_num_of_data_segments = num_of_data_segments; + + fd = shm_open(shared_location_name, shmem_fd_flags, S_IRWXU | S_IRWXG | S_IRWXO); + if (fd == -1) { + writeDebug( + logging_data, + WarningLevel, + "createSharedRingQueue: Failed to open shared memory for '%s'. Errno: %d\n", + shared_location_name, + errno + ); + return NULL; + } + + size_of_memory = sizeof(SharedRingQueue) + (num_of_data_segments * sizeof(DataSegment)); + if (is_owner && ftruncate(fd, size_of_memory + 1) != 0) { + writeDebug( + logging_data, + WarningLevel, + "createSharedRingQueue: Failed to ftruncate shared memory '%s' to size '%x'\n", + shared_location_name, + size_of_memory + ); + close(fd); + return NULL; + } + + queue = (SharedRingQueue *)mmap(0, size_of_memory, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (queue == NULL) { + writeDebug( + logging_data, + WarningLevel, + "createSharedRingQueue: Error allocating queue for '%s' of size=%x\n", + shared_location_name, + size_of_memory + ); + close(fd); + return NULL; + } + + if (is_owner) { + snprintf(queue->shared_location_name, MAX_ONE_WAY_QUEUE_NAME_LENGTH, "%s", shared_location_name); + queue->num_of_data_segments = num_of_data_segments; + queue->read_pos = 0; + queue->write_pos = 0; + queue->size_of_memory = size_of_memory; + buffer_mgmt = (uint16_t *)queue->mgmt_segment.data; + for (idx = 0; idx < queue->num_of_data_segments; idx++) { + buffer_mgmt[idx] = empty_buff_mgmt_magic; + } + queue->owner_fd = fd; + } else { + queue->user_fd = fd; + } + + global_data->g_memory_size = size_of_memory; + if (is_tx) { + global_data->g_tx_fd = fd; + snprintf(global_data->g_tx_location_name, MAX_ONE_WAY_QUEUE_NAME_LENGTH, "%s", shared_location_name); + } else { + global_data->g_rx_fd = fd; + snprintf(global_data->g_rx_location_name, MAX_ONE_WAY_QUEUE_NAME_LENGTH, "%s", shared_location_name); + } + + writeDebug( + logging_data, + TraceLevel, + "Successfully created a new shared ring queue. " + "Shared memory path: %s, number of segments: %u, is owner: %d, " + "fd flags: %u, fd: %d, memory size: %u, read index: %u, write index: %u", + shared_location_name, + queue->num_of_data_segments, + is_owner, + shmem_fd_flags, + fd, + queue->size_of_memory, + queue->read_pos, + queue->write_pos + ); + + return queue; +} + +void +destroySharedRingQueue( + LoggingData *logging_data, + SharedRingQueue *queue, + SharedRingGlobalData *global_data, + int is_owner, + int is_tx +) +{ + uint32_t size_of_memory = global_data->g_memory_size; + int32_t fd = 0; + + if(is_owner) { + queue->owner_fd = 0; + } else { + queue->user_fd = 0; + } + + if (is_tx) { + fd = global_data->g_tx_fd; + global_data->g_tx_fd = -1; + } else { + fd = global_data->g_rx_fd; + global_data->g_rx_fd = -1; + } + + if (munmap(queue, size_of_memory) != 0) { + writeDebug(logging_data, WarningLevel, "destroySharedRingQueue: Failed to unmap shared ring queue\n"); + } + if (fd > 0) close(fd); + fd = 0; + + // shm_open cleanup + if(is_owner) { + shm_unlink(is_tx ? global_data->g_tx_location_name : global_data->g_rx_location_name); + } + writeDebug(logging_data, TraceLevel, "Successfully destroyed shared ring queue. Is owner: %d", is_owner); +} + +void +dumpRingQueueShmem(LoggingData *logging_data, SharedRingQueue *queue) +{ + uint16_t segment_idx; + uint16_t data_idx; + uint16_t *buffer_mgmt = NULL; + char data_byte; + + writeDebug( + logging_data, + WarningLevel, + "owner_fd: %d, user_fd: %d, size_of_memory: %d, write_pos: %d, read_pos: %d, num_of_data_segments: %d\n", + queue->owner_fd, + queue->user_fd, + queue->size_of_memory, + queue->write_pos, + queue->read_pos, + queue->num_of_data_segments + ); + + writeDebug(logging_data, WarningLevel, "mgmt_segment:"); + buffer_mgmt = (uint16_t *)queue->mgmt_segment.data; + for (segment_idx = 0; segment_idx < queue->num_of_data_segments; segment_idx++) { + writeDebug(logging_data, WarningLevel, "%s%u", (segment_idx == 0 ? " " : ", "), buffer_mgmt[segment_idx]); + } + + writeDebug(logging_data, WarningLevel, "\ndata_segment: "); + for (segment_idx = 0; segment_idx < queue->num_of_data_segments; segment_idx++) { + writeDebug( + logging_data, + WarningLevel, + "\nMgmt index: %u, value: %u,\nactual data: ", + segment_idx, + buffer_mgmt[segment_idx] + ); + for (data_idx = 0; data_idx < SHARED_MEMORY_SEGMENT_ENTRY_SIZE; data_idx++) { + data_byte = queue->data_segment[segment_idx].data[data_idx]; + writeDebug(logging_data, WarningLevel, isprint(data_byte) ? "%c" : "%02X", data_byte); + } + } + writeDebug(logging_data, WarningLevel, "\nEnd of memory\n"); +} + +int +peekToQueue( + LoggingData *logging_data, + SharedRingQueue *queue, + SharedRingGlobalData *global_data, + const char **output_buffer, + uint16_t *output_buffer_size +) +{ + uint16_t read_pos; + uint16_t write_pos; + uint16_t *buffer_mgmt = (uint16_t *)queue->mgmt_segment.data; + + if (!isGetPossitionSucceccful(queue, global_data, &read_pos, &write_pos)) { + writeDebug(logging_data, WarningLevel, "Corrupted shared memory - cannot peek"); + return -1; + } + + writeDebug( + logging_data, + TraceLevel, + "Reading data from queue. Read index: %u, number of queue elements: %u", + read_pos, + global_data->g_num_of_data_segments + ); + + if (read_pos == write_pos) { + writeDebug(logging_data, WarningLevel, "peekToQueue: Failed to read from an empty queue\n"); + return -1; + } + + if (read_pos >= global_data->g_num_of_data_segments) { + writeDebug( + logging_data, + WarningLevel, + "peekToQueue: Failed to read from a corrupted queue! (read_pos= %d > num_of_data_segments=%d)\n", + read_pos, + global_data->g_num_of_data_segments + ); + return CORRUPTED_SHMEM_ERROR; + } + + if (buffer_mgmt[read_pos] == skip_buff_mgmt_magic) { + for ( ; read_pos < global_data->g_num_of_data_segments && + buffer_mgmt[read_pos] == skip_buff_mgmt_magic; + ++read_pos) { + buffer_mgmt[read_pos] = empty_buff_mgmt_magic; + } + } + + if (read_pos == global_data->g_num_of_data_segments) read_pos = 0; + + *output_buffer_size = buffer_mgmt[read_pos]; + *output_buffer = queue->data_segment[read_pos].data; + + queue->read_pos = read_pos; + + writeDebug( + logging_data, + TraceLevel, + "Successfully read data from queue. Data size: %u, new Read index: %u", + *output_buffer_size, + queue->read_pos + ); + return 0; +} + +int +pushBuffersToQueue( + LoggingData *logging_data, + SharedRingQueue *queue, + SharedRingGlobalData *global_data, + const char **input_buffers, + const uint16_t *input_buffers_sizes, + const uint8_t num_of_input_buffers +) +{ + int idx; + uint32_t large_total_elem_size = 0; + uint16_t read_pos; + uint16_t write_pos; + uint16_t total_elem_size; + uint16_t *buffer_mgmt = (uint16_t *)queue->mgmt_segment.data; + uint16_t end_pos; + uint16_t num_of_segments_to_write; + char *current_copy_pos; + + if (!isGetPossitionSucceccful(queue, global_data, &read_pos, &write_pos)) { + writeDebug(logging_data, WarningLevel, "Corrupted shared memory - cannot push new buffers"); + return -1; + } + + writeDebug( + logging_data, + TraceLevel, + "Writing new data to queue. write index: %u, number of queue elements: %u, number of elements to push: %u", + write_pos, + global_data->g_num_of_data_segments, + num_of_input_buffers + ); + + for (idx = 0; idx < num_of_input_buffers; idx++) { + large_total_elem_size += input_buffers_sizes[idx]; + + if (large_total_elem_size > max_write_size) { + writeDebug( + logging_data, + WarningLevel, + "Requested write size %u exceeds the %u write limit", + large_total_elem_size, + max_write_size + ); + return -2; + } + } + total_elem_size = (uint16_t)large_total_elem_size; + + num_of_segments_to_write = getNumOfDataSegmentsNeeded(logging_data, total_elem_size); + + writeDebug( + logging_data, + TraceLevel, + "Checking if there is enough space to push new data. Total new data size: %u, number of segments needed: %u", + total_elem_size, + num_of_segments_to_write + ); + + + if (!isThereEnoughMemoryInQueue(logging_data, global_data, write_pos, read_pos, num_of_segments_to_write)) { + writeDebug(logging_data, DebugLevel, "Cannot write to a full queue"); + return -3; + } + + if (write_pos >= global_data->g_num_of_data_segments) { + writeDebug( + logging_data, + DebugLevel, + "Cannot write to a location outside the queue. Write index: %u, number of queue elements: %u", + write_pos, + global_data->g_num_of_data_segments + ); + return -4; + } + + if (write_pos + num_of_segments_to_write > global_data->g_num_of_data_segments) { + for ( ; write_pos < global_data->g_num_of_data_segments; ++write_pos) { + buffer_mgmt[write_pos] = skip_buff_mgmt_magic; + } + write_pos = 0; + } + + writeDebug( + logging_data, + TraceLevel, + "Setting new management data. Write index: %u, total elements in index: %u", + write_pos, + total_elem_size + ); + + buffer_mgmt[write_pos] = total_elem_size; + current_copy_pos = queue->data_segment[write_pos].data; + for (idx = 0; idx < num_of_input_buffers; idx++) { + writeDebug( + logging_data, + TraceLevel, + "Writing data to queue. Data index: %u, data size: %u, copy destination: %p", + idx, + input_buffers_sizes[idx], + current_copy_pos + ); + memcpy(current_copy_pos, input_buffers[idx], input_buffers_sizes[idx]); + current_copy_pos += input_buffers_sizes[idx]; + } + write_pos++; + + end_pos = write_pos + num_of_segments_to_write - 1; + for ( ; write_pos < end_pos; ++write_pos) { + buffer_mgmt[write_pos] = skip_buff_mgmt_magic; + } + + if (write_pos >= global_data->g_num_of_data_segments) write_pos = 0; + queue->write_pos = write_pos; + writeDebug(logging_data, TraceLevel, "Successfully pushed data to queue. New write index: %u", write_pos); + + return 0; +} + +int +pushToQueue( + LoggingData *logging_data, + SharedRingQueue *queue, + SharedRingGlobalData *global_data, + const char *input_buffer, + const uint16_t input_buffer_size +) +{ + return pushBuffersToQueue(logging_data, queue, global_data, &input_buffer, &input_buffer_size, 1); +} + +int +popFromQueue(LoggingData *logging_data, SharedRingQueue *queue, SharedRingGlobalData *global_data) +{ + uint16_t num_of_read_segments; + uint16_t read_pos; + uint16_t write_pos; + uint16_t end_pos; + uint16_t *buffer_mgmt = (uint16_t *)queue->mgmt_segment.data; + + if (!isGetPossitionSucceccful(queue, global_data, &read_pos, &write_pos)) { + writeDebug(logging_data, WarningLevel, "Corrupted shared memory - cannot pop data"); + return -1; + } + + writeDebug( + logging_data, + TraceLevel, + "Removing data from queue. new data to queue. Read index: %u, number of queue elements: %u", + read_pos, + global_data->g_num_of_data_segments + ); + + if (read_pos == write_pos) { + writeDebug(logging_data, TraceLevel, "Cannot pop data from empty queue"); + return -1; + } + num_of_read_segments = getNumOfDataSegmentsNeeded(logging_data, buffer_mgmt[read_pos]); + + if (read_pos + num_of_read_segments > global_data->g_num_of_data_segments) { + for ( ; read_pos < global_data->g_num_of_data_segments; ++read_pos ) { + buffer_mgmt[read_pos] = empty_buff_mgmt_magic; + } + read_pos = 0; + } + + end_pos = read_pos + num_of_read_segments; + + for ( ; read_pos < end_pos; ++read_pos ) { + buffer_mgmt[read_pos] = empty_buff_mgmt_magic; + } + + if (read_pos < global_data->g_num_of_data_segments && buffer_mgmt[read_pos] == skip_buff_mgmt_magic) { + for ( ; read_pos < global_data->g_num_of_data_segments; ++read_pos ) { + buffer_mgmt[read_pos] = empty_buff_mgmt_magic; + } + } + + writeDebug( + logging_data, + TraceLevel, + "Size of data to remove: %u, number of queue elements to free: %u, current read index: %u, end index: %u", + buffer_mgmt[read_pos], + num_of_read_segments, + read_pos, + end_pos + ); + + if (read_pos == global_data->g_num_of_data_segments) read_pos = 0; + + queue->read_pos = read_pos; + writeDebug(logging_data, TraceLevel, "Successfully popped data from queue. New read index: %u", read_pos); + + return 0; +} + +int +isQueueEmpty(SharedRingQueue *queue) +{ + return queue->read_pos == queue->write_pos; +} + +int +isCorruptedQueue(LoggingData *logging_data, SharedRingQueue *queue, SharedRingGlobalData *global_data, int is_tx) +{ + writeDebug( + logging_data, + TraceLevel, + "Checking if shared ring queue is corrupted. " + "g_num_of_data_segments = %u, queue->num_of_data_segments = %u, queue->read_pos = %u, queue->write_pos = %u, " + "g_memory_size = %d, queue->size_of_memory = %d, " + "queue->shared_location_name = %s, g_tx_location_name = %s, g_rx_location_name = %s, is_tx = %d", + global_data->g_num_of_data_segments, + queue->num_of_data_segments, + queue->read_pos, + queue->write_pos, + global_data->g_memory_size, + queue->size_of_memory, + queue->shared_location_name, + global_data->g_tx_location_name, + global_data->g_rx_location_name, + is_tx + ); + + if (global_data->g_num_of_data_segments == 0) return 0; + + if (queue->num_of_data_segments != global_data->g_num_of_data_segments) return 1; + if (queue->size_of_memory != global_data->g_memory_size) return 1; + if (queue->read_pos > global_data->g_num_of_data_segments) return 1; + if (queue->write_pos > global_data->g_num_of_data_segments) return 1; + if (strcmp( + queue->shared_location_name, + is_tx ? global_data->g_tx_location_name : global_data->g_rx_location_name + ) != 0 + ) return 1; + + return 0; +} +// LCOV_EXCL_STOP diff --git a/core/shmem_ipc_2/shared_ring_queue.h b/core/shmem_ipc_2/shared_ring_queue.h new file mode 100755 index 0000000..69579ab --- /dev/null +++ b/core/shmem_ipc_2/shared_ring_queue.h @@ -0,0 +1,114 @@ +// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __SHARED_RING_QUEUE_H__ +#define __SHARED_RING_QUEUE_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +#define SHARED_MEMORY_SEGMENT_ENTRY_SIZE 1024 +#define MAX_ONE_WAY_QUEUE_NAME_LENGTH 64 +#define CORRUPTED_SHMEM_ERROR -2 + +typedef struct LoggingData LoggingData; + +typedef struct SharedRingGlobalData { + char g_rx_location_name[MAX_ONE_WAY_QUEUE_NAME_LENGTH]; + char g_tx_location_name[MAX_ONE_WAY_QUEUE_NAME_LENGTH]; + int32_t g_rx_fd; + int32_t g_tx_fd; + int32_t g_memory_size; + uint16_t g_num_of_data_segments; +} SharedRingGlobalData; + +typedef struct DataSegment { + char data[SHARED_MEMORY_SEGMENT_ENTRY_SIZE]; +} DataSegment; + +typedef struct __attribute__((__packed__)) SharedRingQueue { + char shared_location_name[MAX_ONE_WAY_QUEUE_NAME_LENGTH]; + int32_t owner_fd; + int32_t user_fd; + int32_t size_of_memory; + uint16_t write_pos; + uint16_t read_pos; + uint16_t num_of_data_segments; + DataSegment mgmt_segment; + DataSegment data_segment[0]; +} SharedRingQueue; + +SharedRingQueue * +createSharedRingQueue( + LoggingData *logging_data, + const char *shared_location_name, + uint16_t num_of_data_segments, + int is_owner, + int is_tx, + SharedRingGlobalData *global_data +); + +SharedRingGlobalData * createSharedRingGlobalData(LoggingData *logging_data); + +void destroySharedRingQueue( + LoggingData *logging_data, + SharedRingQueue *queue, + SharedRingGlobalData *global_data, + int is_owner, + int is_tx +); +int isQueueEmpty(SharedRingQueue *queue); +int isCorruptedQueue(LoggingData *logging_data, SharedRingQueue *queue, SharedRingGlobalData *global_data, int is_tx); + +int peekToQueue( + LoggingData *logging_data, + SharedRingQueue *queue, + SharedRingGlobalData *global_data, + const char **output_buffer, + uint16_t *output_buffer_size +); + +int popFromQueue(LoggingData *logging_data, SharedRingQueue *queue, SharedRingGlobalData *global_data); + +int pushToQueue( + LoggingData *logging_data, + SharedRingQueue *queue, + SharedRingGlobalData *global_data, + const char *input_buffer, + const uint16_t input_buffer_size +); + +void resetRingQueue(LoggingData *logging_data, SharedRingQueue *queue, uint16_t num_of_data_segments); +void dumpRingQueueShmem(LoggingData *logging_data, SharedRingQueue *queue); + +int +pushBuffersToQueue( + LoggingData *logging_data, + SharedRingQueue *queue, + SharedRingGlobalData *global_data, + const char **input_buffers, + const uint16_t *input_buffers_sizes, + const uint8_t num_of_input_buffers +); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // __SHARED_RING_QUEUE_H__ diff --git a/core/shmem_ipc_2/shmem_ipc.c b/core/shmem_ipc_2/shmem_ipc.c new file mode 100755 index 0000000..a1c5162 --- /dev/null +++ b/core/shmem_ipc_2/shmem_ipc.c @@ -0,0 +1,419 @@ +// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "shmem_ipc_2.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shared_ring_queue.h" +#include "shared_ipc_debug.h" + +#define UNUSED(x) (void)(x) + +const int corrupted_shmem_error = CORRUPTED_SHMEM_ERROR; +static const size_t max_one_way_queue_name_length = MAX_ONE_WAY_QUEUE_NAME_LENGTH; +static const size_t max_shmem_path_length = 72; + +struct SharedMemoryIPC { + char shm_name[32]; + SharedRingQueue *rx_queue; + SharedRingQueue *tx_queue; + SharedRingGlobalData *global_data; + LoggingData logging_data; +}; + +// LCOV_EXCL_START Reason: Handing it to Envoy prototype development + +void +debugInitial( + const LoggingData *loggin_data, + uint32_t worker_id, + int is_error, + const char *func, + const char *file, + int line_num, + const char *fmt, + ... +) +{ + UNUSED(is_error); + UNUSED(func); + UNUSED(file); + UNUSED(line_num); + UNUSED(loggin_data); + UNUSED(worker_id); + + // Temporarily disabled till Shmem debugging is properly fixed. + // va_list args; + // va_start(args, fmt); + // vprintf(fmt, args); + // va_end(args); + + UNUSED(fmt); +} + +void (*debug_int)( + const LoggingData *loggin_data, + uint32_t worker_id, + int is_error, + const char *func, + const char *file, + int line_num, + const char *fmt, + ... +) = debugInitial; + + +static int +isTowardsOwner(int is_owner, int is_tx) +{ + if (is_owner) return !is_tx; + return is_tx; +} + +static SharedRingQueue * +createOneWayIPCQueue( + LoggingData *logging_data, + const char *name, + const uint32_t user_id, + const uint32_t group_id, + int is_tx_queue, + int is_owner, + uint16_t num_of_queue_elem, + SharedRingGlobalData *global_data +) +{ + SharedRingQueue *ring_queue = NULL; + char queue_name[max_one_way_queue_name_length]; + char shmem_path[max_shmem_path_length]; + const char *direction = isTowardsOwner(is_owner, is_tx_queue) ? "rx" : "tx"; + snprintf(queue_name, sizeof(queue_name) - 1, "__cp_nano_%s_shared_memory_%s__", direction, name); + + writeDebug( + logging_data, + TraceLevel, + "Creating one way IPC queue. Name: %s, direction: %s, size: %d", + name, + direction, + num_of_queue_elem + ); + ring_queue = createSharedRingQueue( + logging_data, + queue_name, + num_of_queue_elem, + is_owner, + isTowardsOwner(is_owner, is_tx_queue), + global_data + ); + if (ring_queue == NULL) { + writeDebug( + logging_data, + WarningLevel, + "Failed to create %s shared ring queue of size=%d for '%s'\n", + direction, + num_of_queue_elem, + queue_name + ); + return NULL; + } + int ret = snprintf(shmem_path, sizeof(shmem_path) - 1, "/dev/shm/%s", queue_name); + if (ret < 0 || (size_t)ret < (strlen(direction) + strlen(name))) { + return NULL; + } + + if (is_owner && chmod(shmem_path, 0666) == -1) { + writeDebug(logging_data, WarningLevel, "Failed to set the permissions"); + destroySharedRingQueue(logging_data, ring_queue, global_data, is_owner, isTowardsOwner(is_owner, is_tx_queue)); + return NULL; + } + + writeDebug( + logging_data, + TraceLevel, + "Successfully created one way IPC queue. " + "Name: %s, user id: %u, group id: %u, is owner: %d, number of queue elements: %u, direction: %s, path: %s", + queue_name, + user_id, + group_id, + is_owner, + num_of_queue_elem, + direction, + shmem_path + ); + return ring_queue; +} + +LoggingData * +initLoggingData(int dbg_level, int worker_id, int fd) +{ + LoggingData *logging_data = malloc(sizeof(LoggingData)); + if (logging_data == NULL) { + return NULL; + } + logging_data->dbg_level = dbg_level; + logging_data->worker_id = worker_id; + logging_data->fd = fd; + return logging_data; +} + +SharedMemoryIPC * +initIpc( + const char queue_name[32], + uint32_t user_id, + uint32_t group_id, + int is_owner, + uint16_t num_of_queue_elem, + const LoggingData *logging_data, + void (*debug_func)( + const LoggingData *loggin_data, + uint32_t worker_id, + int is_error, + const char *func, + const char *file, + int line_num, + const char *fmt, + ... + ) +) +{ + UNUSED(debug_func); + SharedMemoryIPC *ipc = NULL; + // debug_int = debug_func; + debug_int = debugInitial; + + writeDebug( + logging_data, + TraceLevel, + "Initializing new IPC. " + "Queue name: %s, user id: %u, group id: %u, is owner: %d, number of queue elements: %u\n", + queue_name, + user_id, + group_id, + is_owner, + num_of_queue_elem + ); + + ipc = malloc(sizeof(SharedMemoryIPC)); + if (ipc == NULL) { + writeDebug(logging_data, WarningLevel, "Failed to allocate Shared Memory IPC for '%s'\n", queue_name); + debug_int = debugInitial; + return NULL; + } + + ipc->logging_data.dbg_level = logging_data->dbg_level; + ipc->logging_data.worker_id = logging_data->worker_id; + ipc->logging_data.fd = logging_data->fd; + + ipc->global_data = createSharedRingGlobalData(&(ipc->logging_data)); + if (ipc->global_data == NULL) { + writeDebug(logging_data, WarningLevel, "Failed to allocate global data for '%s'\n", queue_name); + debug_int = debugInitial; + free(ipc); + return NULL; + } + + ipc->rx_queue = NULL; + ipc->tx_queue = NULL; + + ipc->rx_queue = createOneWayIPCQueue( + &(ipc->logging_data), + queue_name, + user_id, + group_id, + 0, + is_owner, + num_of_queue_elem, + ipc->global_data + ); + if (ipc->rx_queue == NULL) { + writeDebug( + &(ipc->logging_data), + WarningLevel, + "Failed to allocate rx queue. " + "Queue name: %s, user id: %u, group id: %u, is owner: %d, number of queue elements: %u", + queue_name, + user_id, + group_id, + is_owner, + num_of_queue_elem + ); + + destroyIpc(ipc, is_owner); + debug_int = debugInitial; + return NULL; + } + + ipc->tx_queue = createOneWayIPCQueue( + &(ipc->logging_data), + queue_name, + user_id, + group_id, + 1, + is_owner, + num_of_queue_elem, + ipc->global_data + ); + if (ipc->tx_queue == NULL) { + writeDebug( + &(ipc->logging_data), + WarningLevel, + "Failed to allocate rx queue. " + "Queue name: %s, user id: %u, group id: %u, is owner: %d, number of queue elements: %u", + queue_name, + user_id, + group_id, + is_owner, + num_of_queue_elem + ); + destroyIpc(ipc, is_owner); + debug_int = debugInitial; + return NULL; + } + + writeDebug(&(ipc->logging_data), TraceLevel, "Successfully allocated IPC"); + + strncpy(ipc->shm_name, queue_name, sizeof(ipc->shm_name)); + return ipc; +} + +void +resetIpc(SharedMemoryIPC *ipc, uint16_t num_of_data_segments) +{ + writeDebug(&(ipc->logging_data), TraceLevel, "Reseting IPC queues\n"); + resetRingQueue(&(ipc->logging_data), ipc->rx_queue, num_of_data_segments); + resetRingQueue(&(ipc->logging_data), ipc->tx_queue, num_of_data_segments); +} + +void +destroyIpc(SharedMemoryIPC *shmem, int is_owner) +{ + writeDebug(&(shmem->logging_data), TraceLevel, "Destroying IPC queues\n"); + + if (shmem->rx_queue != NULL) { + destroySharedRingQueue( + &(shmem->logging_data), + shmem->rx_queue, + shmem->global_data, + is_owner, + isTowardsOwner(is_owner, 0) + ); + shmem->rx_queue = NULL; + } + if (shmem->tx_queue != NULL) { + destroySharedRingQueue( + &(shmem->logging_data), + shmem->tx_queue, + shmem->global_data, + is_owner, + isTowardsOwner(is_owner, 1) + ); + shmem->tx_queue = NULL; + } + free(shmem->global_data); + debug_int = debugInitial; + free(shmem); +} + +void +dumpIpcMemory(SharedMemoryIPC *ipc) +{ + writeDebug(&(ipc->logging_data), WarningLevel, "Ipc memory dump:\n"); + writeDebug(&(ipc->logging_data), WarningLevel, "RX queue:\n"); + dumpRingQueueShmem(&(ipc->logging_data), ipc->rx_queue); + writeDebug(&(ipc->logging_data), WarningLevel, "TX queue:\n"); + dumpRingQueueShmem(&(ipc->logging_data), ipc->tx_queue); +} + +int +sendData(SharedMemoryIPC *ipc, const uint16_t data_to_send_size, const char *data_to_send) +{ + writeDebug(&(ipc->logging_data), TraceLevel, "Sending data of size %u\n", data_to_send_size); + return pushToQueue(&(ipc->logging_data), ipc->tx_queue, ipc->global_data, data_to_send, data_to_send_size); +} + +int +sendChunkedData( + SharedMemoryIPC *ipc, + const uint16_t *data_to_send_sizes, + const char **data_elem_to_send, + const uint8_t num_of_data_elem +) +{ + writeDebug(&(ipc->logging_data), TraceLevel, "Sending %u chunks of data\n", num_of_data_elem); + + return pushBuffersToQueue( + &(ipc->logging_data), + ipc->tx_queue, + ipc->global_data, + data_elem_to_send, + data_to_send_sizes, + num_of_data_elem + ); +} + +int +receiveData(SharedMemoryIPC *ipc, uint16_t *received_data_size, const char **received_data) +{ + int res = peekToQueue(&(ipc->logging_data), ipc->rx_queue, ipc->global_data, received_data, received_data_size); + writeDebug( + &(ipc->logging_data), + TraceLevel, + "Received data from queue. Res: %d, data size: %u\n", + res, + *received_data_size + ); + return res; +} + +int +popData(SharedMemoryIPC *ipc) +{ + int res = popFromQueue(&(ipc->logging_data), ipc->rx_queue, ipc->global_data); + writeDebug(&(ipc->logging_data), TraceLevel, "Popped data from queue. Res: %d\n", res); + return res; +} + +int +isDataAvailable(SharedMemoryIPC *ipc) +{ + int res = !isQueueEmpty(ipc->rx_queue); + writeDebug(&(ipc->logging_data), TraceLevel, "Checking if there is data pending to be read. Res: %d\n", res); + return res; +} + +int +isCorruptedShmem(SharedMemoryIPC *ipc, int is_owner) +{ + if (isCorruptedQueue(&(ipc->logging_data), ipc->rx_queue, ipc->global_data, isTowardsOwner(is_owner, 0)) || + isCorruptedQueue(&(ipc->logging_data), ipc->tx_queue, ipc->global_data, isTowardsOwner(is_owner, 1)) + ) { + writeDebug( + &(ipc->logging_data), + WarningLevel, + "Detected corrupted shared memory queue. Shared memory name: %s", + ipc->shm_name + ); + return 1; + } + + return 0; +} +// LCOV_EXCL_STOP diff --git a/docker/openappsec-waf-webhook/Dockerfile b/docker/openappsec-waf-webhook/Dockerfile new file mode 100755 index 0000000..101886d --- /dev/null +++ b/docker/openappsec-waf-webhook/Dockerfile @@ -0,0 +1,26 @@ +# Use Python 3.9 slim image as the base +FROM python:3.9-slim + +# Install dependencies +RUN pip install kubernetes cryptography flask --progress-bar off + +# Create directory for certs +RUN mkdir -p /certs + +# Copy the Python scripts +COPY keygen.py /app/keygen.py +COPY webhook_server.py /app/webhook_server.py +COPY secretgen.py /app/secretgen.py +COPY run.sh /app/run.sh + +# Make the script executable +RUN chmod +x /app/run.sh + +# Set the working directory +WORKDIR /app + +# Expose port 443 +EXPOSE 443 + +# Run the webhook server +CMD ["/app/run.sh"] \ No newline at end of file diff --git a/docker/openappsec-waf-webhook/keygen.py b/docker/openappsec-waf-webhook/keygen.py new file mode 100755 index 0000000..6d2c43d --- /dev/null +++ b/docker/openappsec-waf-webhook/keygen.py @@ -0,0 +1,85 @@ +#!/usr/bin/python3 +import base64 +import os +import argparse +import subprocess +from shutil import which +from pathlib import Path + +def generate_keys(service, namespace, directory="generated"): + "Generate key material and configuration for Kubernetes admission controllers" + + if not which("openssl"): + raise click.UsageError("Unable to detect the openssl CLI tool on the path") + + if not os.path.exists(directory): + os.makedirs(directory) + + print("==> Generating CA") + + command = """openssl genrsa -out ca.key 2048 +openssl req -x509 -new -nodes -key ca.key -days 100000 -out ca.crt -subj '/CN=admission_ca'""" + + subprocess.run(command, cwd=directory, shell=True, stderr=subprocess.DEVNULL) + + print("==> Creating configuration") + + with open(os.path.sep.join((directory, "server.conf")), "w") as f: + f.write( + """[req] +default_bits = 2048 +req_extensions = v3_req +distinguished_name = req_distinguished_name +req_extensions = req_ext +prompt = no +[ req_distinguished_name ] +CN = {service}.{namespace}.svc +[ v3_req ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, serverAuth +[ req_ext ] +subjectAltName = @alt_names +[alt_names] +DNS.1 = {service} +DNS.2 = {service}.{namespace} +DNS.3 = {service}.{namespace}.svc +""".format(service = service, namespace = namespace) + ) + + print("==> Generating private key and certificate") + + address = "{}.{}.svc".format(service, namespace) + + command = """openssl genrsa -out server.key 2048 +openssl req -out server.csr -newkey rsa:2048 -nodes -keyout server.key -config server.conf +openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 100000 -extensions req_ext -extfile server.conf""".format( + ADDRESS = address + ) + + subprocess.run(command, cwd=directory, shell=True, stderr=subprocess.DEVNULL) + + print("==> Key material generated") + + with open(os.path.sep.join((directory, "ca.crt")), "rb") as f: + ca_cert = f.read() + print("Use this as the caBundle:") + print(base64.b64encode(ca_cert).decode("ascii")) + + print("==> Command to create secret") + print("Run this to upload the key material to a Kubernetes secret") + print() + + print( + "kubectl --namespace={0} create secret tls {1}-certs --cert={2}/server.crt --key={2}/server.key".format( + namespace, service, directory + ) + ) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description = 'Check Point Webhooks K8s Webhook Keygen') + parser.add_argument("namespace", help = "Destination namespace") + + args = parser.parse_args() + + generate_keys("envoy-injector", args.namespace) \ No newline at end of file diff --git a/docker/openappsec-waf-webhook/run.sh b/docker/openappsec-waf-webhook/run.sh new file mode 100755 index 0000000..5dc7cc3 --- /dev/null +++ b/docker/openappsec-waf-webhook/run.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Start certificate manager in the background +# Start the webhook server +while [ true ]; do + echo "Starting Webhook Server" + python3 webhook_server.py + echo "Webhook Server crashed, restarting..." + sleep 5 # Pause before restarting +done \ No newline at end of file diff --git a/docker/openappsec-waf-webhook/secretgen.py b/docker/openappsec-waf-webhook/secretgen.py new file mode 100755 index 0000000..94e40d6 --- /dev/null +++ b/docker/openappsec-waf-webhook/secretgen.py @@ -0,0 +1,65 @@ +#!/usr/bin/python + +import os +import base64 +import kubernetes.client +from kubernetes.client.rest import ApiException +from kubernetes import client, config +from pprint import pprint + +# Key generation script +import keygen + +SERVICE_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/" +API_SERVER = "https://kubernetes.default.svc" + +GENERATED_CERTS_FOLDER = "/certs/" + +config.load_incluster_config() + +def getToken(): + with open(os.path.sep.join((SERVICE_PATH, "token")), "r") as f: + return f.read() + +def main(): + # First, generate keys + keygen.generate_keys("openappsec-waf-webhook-svc", os.environ["K8S_NAMESPACE"], GENERATED_CERTS_FOLDER) + + found = None + + api_instance = client.AdmissionregistrationV1Api() + + try: + api_response = api_instance.list_mutating_webhook_configuration() + + for result in api_response.items: + print(result.metadata.name) + if "openappsec-waf.injector" in result.metadata.name: + pprint(result) + found = result + break + + if found is None: + raise Exception("Could not find webhook") + + # Change the CA file + with open(os.path.sep.join((GENERATED_CERTS_FOLDER, "ca.crt")), "rb") as f: + cert = base64.b64encode(f.read()).decode("utf-8") + + print("CA Cert:", cert) + + # Update cert + for webhook in found.webhooks: + if "openappsec-waf.injector" in webhook.name: + webhook.client_config.ca_bundle = cert; + + + # Patch + response = api_instance.patch_mutating_webhook_configuration(found.metadata.name, found, pretty = "true") + + pprint(response) + except ApiException as e: + print("Exception when calling AdmissionregistrationApi->get_api_group: %s\n" % e) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/docker/openappsec-waf-webhook/webhook_server.py b/docker/openappsec-waf-webhook/webhook_server.py new file mode 100755 index 0000000..37b2bd3 --- /dev/null +++ b/docker/openappsec-waf-webhook/webhook_server.py @@ -0,0 +1,653 @@ +import os +import json +import logging +import base64 +import secretgen +import sys +from kubernetes import client, config +from flask import Flask, request, jsonify, Response + +app = Flask(__name__) + +# Read agent image and tag from environment variables +AGENT_IMAGE = os.getenv('AGENT_IMAGE', 'ghcr.io/openappsec/agent') +AGENT_TAG = os.getenv('AGENT_TAG', 'latest') +FULL_AGENT_IMAGE = f"{AGENT_IMAGE}:{AGENT_TAG}" + +config.load_incluster_config() + +def configure_logging(): + # Read the DEBUG_LEVEL from environment variables, defaulting to WARNING + DEBUG_LEVEL = os.getenv('DEBUG_LEVEL', 'WARNING').upper() + + # Map the string value of DEBUG_LEVEL to actual logging level + logging_levels = { + 'DEBUG': logging.DEBUG, + 'INFO': logging.INFO, + 'WARNING': logging.WARNING, + 'ERROR': logging.ERROR, + 'CRITICAL': logging.CRITICAL + } + + # Set the logging level based on the environment variable + log_level = logging_levels.get(DEBUG_LEVEL, logging.INFO) + + # Configure Flask's logger to handle the specified logging level + handler = logging.StreamHandler() + handler.setLevel(log_level) + formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + handler.setFormatter(formatter) + + # Remove any existing handlers + if app.logger.hasHandlers(): + app.logger.handlers.clear() + + app.logger.addHandler(handler) + app.logger.setLevel(log_level) + +# The sidecar container spec with configurable image +def get_sidecar_container(): + app.logger.debug("Entering get_sidecar_container()") + token = os.getenv("TOKEN") + sidecar = { + "name": "infinity-next-nano-agent", + "image": FULL_AGENT_IMAGE, + "imagePullPolicy": "Always", + "command": ["/cp-nano-agent"], + "args": [ + "--token", + token + ], + "env": [ + {"name": "registered_server", "value": "NGINX Server"} + ], + "volumeMounts": [ + {"name": "envoy-attachment-shared", "mountPath": "/envoy/attachment/shared/"} + ], + "resources": { + "requests": { + "cpu": "200m" + } + }, + "securityContext": { + "runAsNonRoot": False, + "runAsUser": 0 + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File" + } + app.logger.debug(f"Sidecar container spec: {sidecar}") + app.logger.debug("Exiting get_sidecar_container()") + return sidecar + +def get_init_container(): + # Define the initContainer you want to inject + init_container = { + "name": "prepare-attachment", + "image": FULL_AGENT_IMAGE, + "imagePullPolicy": "Always", + "command": [ + "sh", "-c", + "mkdir -p /envoy/attachment/shared && cp -r /envoy/attachment/lib* /envoy/attachment/shared" + ], + "volumeMounts": [ + { + "mountPath": "/envoy/attachment/shared", + "name": "envoy-attachment-shared" + } + ] + } + app.logger.debug(f"Init container spec: {init_container}") + app.logger.debug("Exiting get_init_container()") + return init_container + +# The volume mount configuration for both the original and sidecar containers +def get_volume_mount(): + app.logger.debug("Entering get_volume_mount()") + volume_mount = { + "name": "envoy-attachment-shared", + "mountPath": "/usr/lib/attachment/" + } + app.logger.debug(f"Volume mount spec: {volume_mount}") + app.logger.debug("Exiting get_volume_mount()") + return volume_mount + +# Volume definition for the pod +def get_volume_definition(): + app.logger.debug("Entering get_volume_definition()") + volume_def = { + "name": "envoy-attachment-shared", + "emptyDir": {} + } + app.logger.debug(f"Volume definition: {volume_def}") + app.logger.debug("Exiting get_volume_definition()") + return volume_def + +def add_env_if_not_exist(containers, container_name, patches): + # Find the container by name + container = next((c for c in containers if c.get('name') == container_name), None) + + if container: + # Get the existing environment variables (if any) + env_vars = container.get('env', None) + + if env_vars is None: + # If no env variables exist, add an empty env array first + patches.append({ + "op": "add", + "path": f"/spec/containers/{containers.index(container)}/env", + "value": [] + }) + +def add_env_variable_value_from(containers, container_name, env_var_name, env_value, patches, value_from): + """Adds or updates a specified environment variable in a given container.""" + container_index = next((i for i, container in enumerate(containers) if container['name'] == container_name), None) + + if container_index is not None: + env_vars = containers[container_index].get('env', []) + existing_env_var = next((env for env in env_vars if env['name'] == env_var_name), None) + + if existing_env_var: + env_var_patch = { + "op": "replace", + "path": f"/spec/containers/{container_index}/env/{env_vars.index(existing_env_var)}", + "value": {"name": env_var_name, "valueFrom": value_from} + } + patches.append(env_var_patch) + app.logger.debug(f"Updated {env_var_name} environment variable in {container_name} container to use valueFrom.") + else: + env_var_patch = { + "op": "add", + "path": f"/spec/containers/{container_index}/env/-", + "value": {"name": env_var_name, "valueFrom": value_from} + } + patches.append(env_var_patch) + app.logger.debug(f"Added {env_var_name} environment variable with valueFrom to {container_name} container.") + else: + app.logger.warning(f"{container_name} container not found; no environment variable modification applied.") + +def add_env_variable(containers, container_name, env_var_name, env_value, patches): + """Adds or updates a specified environment variable in a given container.""" + # Find the specified container by name + container_index = next((i for i, container in enumerate(containers) if container['name'] == container_name), None) + + if container_index is not None: + # Get the list of environment variables for the specified container + env_vars = containers[container_index].get('env', []) + + # Find the specified environment variable if it exists + existing_env_var = next((env for env in env_vars if env['name'] == env_var_name), None) + + # If the environment variable exists, handle it based on its name + if existing_env_var: + current_value = existing_env_var['value'] + + if env_var_name == 'LD_LIBRARY_PATH': + # For LD_LIBRARY_PATH, append env_value if not already present + if env_value not in current_value: + new_value = f"{current_value}:{env_value}" + env_var_patch = { + "op": "replace", + "path": f"/spec/containers/{container_index}/env/{env_vars.index(existing_env_var)}/value", + "value": new_value + } + patches.append(env_var_patch) + app.logger.debug(f"Updated {env_var_name} environment variable in {container_name} container to new value.") + else: + app.logger.debug(f"{env_var_name} already exists with the correct value; no changes made.") + else: + # For other environment variables, replace the value directly + env_var_patch = { + "op": "replace", + "path": f"/spec/containers/{container_index}/env/{env_vars.index(existing_env_var)}/value", + "value": env_value + } + patches.append(env_var_patch) + app.logger.debug(f"Replaced {env_var_name} environment variable in {container_name} container with new value.") + + else: + # Add the environment variable if it does not exist + env_var_patch = { + "op": "add", + "path": f"/spec/containers/{container_index}/env/-", + "value": { + "name": env_var_name, + "value": env_value + } + } + patches.append(env_var_patch) + app.logger.debug(f"Added {env_var_name} environment variable to {container_name} container.") + else: + app.logger.warning(f"{container_name} container not found; no environment variable modification applied.") + +def remove_env_variable(containers, container_name, env_var_name, patches): + """Removes a specified environment variable from a given container if it exists.""" + # Find the specified container by name + container_index = next((i for i, container in enumerate(containers) if container['name'] == container_name), None) + + if container_index is not None: + # Get the list of environment variables for the specified container + env_vars = containers[container_index].get('env', []) + + # Check if the specified environment variable exists + env_var_exists = any(env['name'] == env_var_name for env in env_vars) + + # Remove the environment variable if it exists + if env_var_exists: + # Find the index of the specified environment variable in the env array + env_var_index = next(i for i, env in enumerate(env_vars) if env['name'] == env_var_name) + patches.append({ + "op": "remove", + "path": f"/spec/containers/{container_index}/env/{env_var_index}" + }) + app.logger.debug(f"Removed {env_var_name} environment variable from {container_name} container.") + else: + app.logger.debug(f"{env_var_name} does not exist, nothing to remove.") + else: + app.logger.warning(f"{container_name} container not found; no environment variable modification applied.") + +def create_or_update_envoy_filter(name, namespace, selector_label_name, selector_label_value): + api = client.CustomObjectsApi() + # Define the EnvoyFilter specification + envoy_filter_spec = { + "apiVersion": "networking.istio.io/v1alpha3", + "kind": "EnvoyFilter", + "metadata": { + "name": name, + "namespace": namespace, + "labels": { + "owner": "waf" + } + }, + "spec": { + "workloadSelector": { + "labels": { + selector_label_name: selector_label_value + } + }, + "configPatches": [ + { + "applyTo": "HTTP_FILTER", + "match": { + "context": "GATEWAY", + "listener": { + "filterChain": { + "filter": { + "name": "envoy.filters.network.http_connection_manager" + } + } + } + }, + "patch": { + "operation": "INSERT_BEFORE", + "value": { + "name": "envoy.filters.http.golang", + "typed_config": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config", + "library_id": "cp_nano_filter", + "library_path": "/usr/lib/attachment/libenvoy_attachment.so", + "plugin_name": "cp_nano_filter", + "plugin_config": { + "@type": "type.googleapis.com/xds.type.v3.TypedStruct", + "type_url": "type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.PluginConfig", + "value": { + "prefix_localreply_body": "Configured local reply from go" + } + } + } + } + } + } + ] + } + } + + # Check if the EnvoyFilter exists + try: + existing_envoy_filter = api.get_namespaced_custom_object( + group="networking.istio.io", + version="v1alpha3", + namespace=namespace, + plural="envoyfilters", + name=name + ) + + # Compare workloadSelector labels + existing_labels = existing_envoy_filter.get("spec", {}).get("workloadSelector", {}).get("labels", {}) + new_labels = envoy_filter_spec["spec"]["workloadSelector"]["labels"] + + if existing_labels == new_labels: + app.logger.info(f"EnvoyFilter '{name}' already exists with matching selector labels.") + return + else: + # Update the existing EnvoyFilter's workloadSelector labels + existing_envoy_filter["spec"]["workloadSelector"]["labels"] = new_labels + api.replace_namespaced_custom_object( + group="networking.istio.io", + version="v1alpha3", + namespace=namespace, + plural="envoyfilters", + name=name, + body=existing_envoy_filter + ) + app.logger.info(f"EnvoyFilter '{name}' updated successfully with new selector labels.") + return + + except client.exceptions.ApiException as e: + if e.status == 404: + # EnvoyFilter doesn't exist, proceed with creation + api.create_namespaced_custom_object( + group="networking.istio.io", + version="v1alpha3", + namespace=namespace, + plural="envoyfilters", + body=envoy_filter_spec + ) + app.logger.info(f"EnvoyFilter '{name}' created successfully.") + +def remove_envoy_filter_by_selector(namespace, selector_label_name, selector_label_value): + api = client.CustomObjectsApi() + try: + # List all EnvoyFilters in the namespace + existing_envoy_filters = api.list_namespaced_custom_object( + group="networking.istio.io", + version="v1alpha3", + namespace=namespace, + plural="envoyfilters" + ) + + # Check if there is any EnvoyFilter with the same selector labels + for item in existing_envoy_filters.get("items", []): + workload_selector = item["spec"].get("workloadSelector", {}).get("labels", {}) + if workload_selector.get(selector_label_name) == selector_label_value: + # Delete the matching EnvoyFilter + api.delete_namespaced_custom_object( + group="networking.istio.io", + version="v1alpha3", + namespace=namespace, + plural="envoyfilters", + name=item["metadata"]["name"], + body=client.V1DeleteOptions() + ) + print(f"EnvoyFilter '{item['metadata']['name']}' with matching selector labels deleted successfully.") + return + print("No EnvoyFilter found with the specified selector labels.") + + except client.exceptions.ApiException as e: + print(f"Failed to delete EnvoyFilter: {e}") + +@app.route('/mutate', methods=['POST']) +def mutate(): + app.logger.debug("Received request to mutate deployment.") + + try: + request_data = request.get_json() + app.logger.debug("Admission Review Request: %s", json.dumps(request_data, indent=2)) + except Exception as e: + app.logger.error("Failed to parse request JSON: %s", str(e)) + return Response(status=400) + + # Extract the UID and the object from the request + uid = request_data.get('request', {}).get('uid', '') + obj = request_data.get('request', {}).get('object', {}) + namespace = request_data.get("request", {}).get("namespace") + app.logger.debug("Extracted UID: %s", uid) + app.logger.debug("Extracted Object: %s", json.dumps(obj, indent=2)) + + # Initialize patches + patches = [] + + # Extract deployment annotations and spec + annotations = obj.get('metadata', {}).get('annotations', {}) + spec = obj.get('spec', {}) + app.logger.debug("Current annotations: %s", json.dumps(annotations, indent=2)) + app.logger.debug("Deployment spec: %s", json.dumps(spec, indent=2)) + + # Check if the 'original-configuration' annotation already exists + if 'original-configuration' not in annotations: + app.logger.debug("Original configuration annotation not found, storing original spec.") + # Store the original spec in an annotation as a JSON string + original_spec_json = json.dumps(spec) + patches.append({ + "op": "add", + "path": "/metadata/annotations/original-configuration", + "value": original_spec_json + }) + app.logger.debug("Added original-configuration annotation patch: %s", patches[-1]) + + # Extract containers and check if sidecar exists + containers = obj.get('spec', {}).get('containers', []) + init_containers = obj.get('spec', {}).get('initContainers', []) + volumes = obj.get('spec', {}).get('volumes', []) + app.logger.debug("Current containers in the pod: %s", json.dumps(containers, indent=2)) + sidecar_exists = any(container['name'] == 'infinity-next-nano-agent' for container in containers) + init_container_exist = any(init_container['name'] == 'prepare-attachment' for init_container in init_containers) + volume_exist = any(volume['name'] == 'envoy-attachment-shared' for volume in volumes) + app.logger.debug("Does sidecar 'infinity-next-nano-agent' exist? %s", sidecar_exists) + + # Determine if we should remove the injected data + REMOVE_WAF = os.getenv('REMOVE_INJECTED_DATA', 'false').lower() == 'true' + DEPLOY_FILTER = os.getenv('DEPLOY_ENVOY_FILTER', 'false').lower() == 'true' + + ISTIO_CONTAINER_NAME = os.getenv('ISTIO_CONTAINER_NAME', 'istio-proxy') + LIBRARY_PATH_VALUE = os.getenv('LIBRARY_PATH_VALUE', '/usr/lib/attachment') + SELECTOR_LABEL_NAME = os.getenv("SELECTOR_LABEL_NAME") + SELECTOR_LABEL_VALUE = os.getenv("SELECTOR_LABEL_VALUE") + CONCURRENCY_CALC_VALUE = os.getenv('CONCURRENCY_CALC') + CONFIG_PORT_VALUE = os.getenv('CONFIG_PORT') + CONCURRENCY_NUMBER_VALUE = os.getenv('CONCURRENCY_NUMBER') + if REMOVE_WAF: + if DEPLOY_FILTER and SELECTOR_LABEL_NAME and SELECTOR_LABEL_VALUE: + remove_envoy_filter_by_selector(namespace, SELECTOR_LABEL_NAME, SELECTOR_LABEL_VALUE) + + app.logger.debug("Removing injected sidecar and associated resources.") + + # Remove ld library path env variable + if ISTIO_CONTAINER_NAME: + if CONCURRENCY_NUMBER_VALUE: + remove_env_variable(containers, ISTIO_CONTAINER_NAME, 'CONCURRENCY_NUMBER', patches) + if CONFIG_PORT_VALUE: + remove_env_variable(containers, ISTIO_CONTAINER_NAME, 'CONFIG_PORT', patches) + if CONCURRENCY_CALC_VALUE: + remove_env_variable(containers, ISTIO_CONTAINER_NAME, 'CONCURRENCY_CALC', patches) + if LIBRARY_PATH_VALUE: + remove_env_variable(containers, ISTIO_CONTAINER_NAME, 'LD_LIBRARY_PATH', patches) + + if 'shareProcessNamespace' in obj.get('spec', {}): + patches.append({ + "op": "remove", + "path": "/spec/shareProcessNamespace" + }) + app.logger.debug("Removed shareProcessNamespace patch") + else: + app.logger.debug("shareProcessNamespace not found; no patch to remove it") + + # Remove the init container if it exists + if init_container_exist: + for idx, init_container in enumerate(init_containers): + if init_container['name'] == 'prepare-attachment': + patches.append({ + "op": "remove", + "path": f"/spec/initContainers/{idx}" + }) + app.logger.debug(f"Removed init container patch: {patches[-1]}") + break # Stop once we find and remove the target container + + # Remove the sidecar container if it exists + if sidecar_exists: + for idx, container in enumerate(containers): + volume_mounts = container.get('volumeMounts', []) + for idx_v, volume_mount in enumerate(volume_mounts): + if volume_mount['name'] == 'envoy-attachment-shared': + patches.append({ + "op": "remove", + "path": f"/spec/containers/{idx}/volumeMounts/{idx_v}" + }) + app.logger.debug(f"Removed volumeMount: {patches[-1]}") + if container['name'] == 'infinity-next-nano-agent': + patches.append({ + "op": "remove", + "path": f"/spec/containers/{idx}" + }) + app.logger.debug(f"Removed sidecar container patch: {patches[-1]}") + + # Remove the volume if it exists + if volume_exist: + for idx, volume in enumerate(volumes): + if volume['name'] == 'envoy-attachment-shared': + patches.append({ + "op": "remove", + "path": f"/spec/volumes/{idx}" + }) + app.logger.debug(f"Removed volume patch: {patches[-1]}") + break # Stop once we find and remove the target container + + else: + app.logger.debug("Before if: Sidecar 'infinity-next-nano-agent' does not exist. Preparing to add it.") + + # Define the sidecar container + sidecar = get_sidecar_container() + + # Define the init container() + init_container = get_init_container() + + # Define the volume mount for istio-proxy + volume_mount = get_volume_mount() + + # Define the volume + volume_def = get_volume_definition() + + if ISTIO_CONTAINER_NAME: + add_env_if_not_exist(containers, ISTIO_CONTAINER_NAME, patches) + add_env_variable_value_from(containers, ISTIO_CONTAINER_NAME, 'OPENAPPSEC_UID', None, patches, value_from={"fieldRef": {"fieldPath": "metadata.uid"}}) + if LIBRARY_PATH_VALUE: + add_env_variable(containers, ISTIO_CONTAINER_NAME, 'LD_LIBRARY_PATH', LIBRARY_PATH_VALUE, patches) + if CONCURRENCY_CALC_VALUE: + add_env_variable(containers, ISTIO_CONTAINER_NAME, 'CONCURRENCY_CALC', CONCURRENCY_CALC_VALUE, patches) + if CONFIG_PORT_VALUE: + add_env_variable(containers, ISTIO_CONTAINER_NAME, 'CONFIG_PORT', CONFIG_PORT_VALUE, patches) + if CONCURRENCY_NUMBER_VALUE: + add_env_variable(containers, ISTIO_CONTAINER_NAME, 'CONCURRENCY_NUMBER', CONCURRENCY_NUMBER_VALUE, patches) + else: + app.logger.debug("ISTIO_CONTAINER_NAME skipping environment variable addition") + + # Add the sidecar container + if not sidecar_exists: + + # Add shareProcessNamespace if not already set + patches.append({ + "op": "add", + "path": "/spec/shareProcessNamespace", + "value": True + }) + app.logger.debug("Added shareProcessNamespace patch") + + patches.append({ + "op": "add", + "path": "/spec/containers/-", + "value": sidecar + }) + app.logger.debug("Added sidecar container patch: %s", patches[-1]) + + # Add the volume mount to istio-proxy container (assumes istio-proxy is first container) + patches.append({ + "op": "add", + "path": "/spec/containers/0/volumeMounts/-", + "value": volume_mount + }) + app.logger.debug("Added volume mount patch to istio-proxy: %s", patches[-1]) + + # Add the new volume definition + patches.append({ + "op": "add", + "path": "/spec/volumes/-", + "value": volume_def + }) + app.logger.debug("Added volume definition patch: %s", patches[-1]) + + if DEPLOY_FILTER and SELECTOR_LABEL_NAME and SELECTOR_LABEL_VALUE: + RELEASE_NAME = os.getenv('RELEASE_NAME', 'openappsec-waf-injected') + envoy_filter_name = RELEASE_NAME + "-waf-filter" + create_or_update_envoy_filter(envoy_filter_name, namespace, SELECTOR_LABEL_NAME, SELECTOR_LABEL_VALUE) + else: + app.logger.debug("Before else: Sidecar 'infinity-next-nano-agent' already exists. Checking for image updates.") + + # Optionally, update the sidecar image and tag if necessary + for idx, container in enumerate(containers): + if container['name'] == 'infinity-next-nano-agent': + current_image = container.get('image', '') + app.logger.debug("Current sidecar image: %s", current_image) + app.logger.debug("Desired sidecar image: %s", FULL_AGENT_IMAGE) + if current_image != FULL_AGENT_IMAGE: + patches.append({ + "op": "replace", + "path": f"/spec/containers/{idx}/image", + "value": FULL_AGENT_IMAGE + }) + app.logger.debug(f"Updated sidecar image patch: {patches[-1]}") + break # Sidecar found and handled + + if not init_container_exist: + # Add the initContainer to the pod spec in the deployment + if 'initContainers' in obj['spec']: + obj['spec']['initContainers'].append(init_container) + else: + obj['spec']['initContainers'] = [init_container] + + patches.append({ + "op": "add", + "path": "/spec/initContainers", + "value": obj['spec']['initContainers'] + }) + else: + app.logger.debug("Before else: init-container 'prepare-attachment' already exists. Checking for image updates.") + + # Optionally, update the sidecar image and tag if necessary + for idx, container in enumerate(containers): + if container['name'] == 'prepare-attachment': + current_image = container.get('image', '') + app.logger.debug("Current init container image: %s", current_image) + app.logger.debug("Desired init container image: %s", FULL_AGENT_IMAGE) + if current_image != FULL_AGENT_IMAGE: + patches.append({ + "op": "replace", + "path": f"/spec/containers/{idx}/image", + "value": FULL_AGENT_IMAGE + }) + app.logger.debug(f"Updated sidecar image patch: {patches[-1]}") + break # Sidecar found and handled + + + app.logger.info("Total patches: %s", json.dumps(patches, indent=2)) + + # Prepare the AdmissionReview response + admission_response = { + "kind": "AdmissionReview", + "apiVersion": "admission.k8s.io/v1", + "response": { + "uid": uid, + "allowed": True, + "patchType": "JSONPatch", + "patch": base64.b64encode(json.dumps(patches).encode('utf-8')).decode('utf-8') + } + } + app.logger.debug("Sending admission response: %s", json.dumps(admission_response, indent=2)) + + return jsonify(admission_response) + +if __name__ == '__main__': + # Configure logger + configure_logging() + + # Ensure certificates exist + secretgen.main() + if not os.path.exists("/certs/server.crt") or not os.path.exists("/certs/server.key"): + print("Error: Certificates not found. Exiting...") + exit(1) + + cli = sys.modules['flask.cli'] + cli.show_server_banner = lambda *x: None + + # Run the Flask app with the generated certificates + app.run(host='0.0.0.0', port=443, ssl_context=('/certs/server.crt', '/certs/server.key')) +