sync code

This commit is contained in:
Ned Wright
2026-01-04 12:04:07 +00:00
parent 7ce1fba437
commit 8ae64fa97d
27 changed files with 1649 additions and 625 deletions

View File

@@ -28,10 +28,10 @@ initAttachmentConfig(c_str conf_file)
return conf_data.init(conf_file); return conf_data.init(conf_file);
} }
ngx_http_inspection_mode_e NanoHttpInspectionMode
getInspectionMode() getInspectionMode()
{ {
return static_cast<ngx_http_inspection_mode_e>(conf_data.getNumericalValue("nginx_inspection_mode")); return static_cast<NanoHttpInspectionMode>(conf_data.getNumericalValue("nginx_inspection_mode"));
} }
unsigned int unsigned int
@@ -191,6 +191,24 @@ getRemoveResServerHeader()
return conf_data.getNumericalValue("remove_server_header"); return conf_data.getNumericalValue("remove_server_header");
} }
unsigned int
getDecompressionPoolSize()
{
return conf_data.getNumericalValue("decompression_pool_size");
}
unsigned int
getRecompressionPoolSize()
{
return conf_data.getNumericalValue("recompression_pool_size");
}
unsigned int
getIsBrotliInspectionEnabled()
{
return conf_data.getNumericalValue("is_brotli_inspection_enabled");
}
int int
isIPAddress(c_str ip_str) isIPAddress(c_str ip_str)
{ {
@@ -285,3 +303,15 @@ isSkipSource(c_str ip_str)
return 0; return 0;
} }
unsigned int
isPairedAffinityEnabled()
{
return conf_data.getNumericalValue("is_paired_affinity_enabled") != 0;
}
unsigned int
isAsyncModeEnabled()
{
return conf_data.getNumericalValue("is_async_mode_enabled") != 0;
}

View File

@@ -166,28 +166,39 @@ ngx_chain_remove_empty_chunks(ngx_chain_t **chain, ngx_pool_t *pool)
{ {
ngx_chain_t *prev = NULL; ngx_chain_t *prev = NULL;
ngx_chain_t *curr = *chain; ngx_chain_t *curr = *chain;
size_t chunk_num = 0; size_t chunk_index = 0;
while (curr != NULL) { while (curr) {
size_t size = curr->buf->last - curr->buf->pos; ngx_buf_t *b = curr->buf;
if (size == 0) {
write_dbg(DBG_LEVEL_WARNING, "Removing empty chunk from the chain, chunk number: %d", chunk_num); /* Keep special links (flush/last_buf/sync) even if size is 0 */
if (prev == NULL) { if (ngx_buf_special(b)) {
*chain = curr->next; prev = curr;
} else {
prev->next = curr->next;
}
ngx_chain_t *tmp = curr;
curr = curr->next; curr = curr->next;
chunk_index++;
continue;
}
off_t buf_size = curr->buf->last - curr->buf->pos;
if (buf_size <= 0) {
ngx_chain_t *tmp = curr;
write_dbg(DBG_LEVEL_WARNING,
"Removing empty chunk from the chain, index: %zu", chunk_index);
curr = curr->next;
if (prev == NULL) {
*chain = curr;
} else {
prev->next = curr;
}
ngx_free_chain(pool, tmp); ngx_free_chain(pool, tmp);
continue; continue;
} }
prev = curr; prev = curr;
curr = curr->next; curr = curr->next;
chunk_num++; chunk_index++;
} }
if (chunk_num == 0) { if (*chain == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Empty chain after removing empty chunks"); write_dbg(DBG_LEVEL_WARNING, "Empty chain after removing empty chunks");
return NGX_ERROR; return NGX_ERROR;
} }
@@ -426,16 +437,13 @@ compression_chain_filter(
return NGX_ERROR; return NGX_ERROR;
} }
if (curr_original_contents_link != NULL) { // Save ONLY first buffer of original body if requested (prevents memory spikes)
if (curr_original_contents_link != NULL && curr_original_contents_link->buf == NULL) {
// Only save the FIRST buffer, don't accumulate subsequent chunks
curr_original_contents_link->buf = ngx_calloc_buf(pool); curr_original_contents_link->buf = ngx_calloc_buf(pool);
ngx_memcpy(curr_original_contents_link->buf, curr_input_link->buf, sizeof(ngx_buf_t)); ngx_memcpy(curr_original_contents_link->buf, curr_input_link->buf, sizeof(ngx_buf_t));
curr_original_contents_link->next = NULL; // No accumulation
if (curr_input_link->next != NULL) { write_dbg(DBG_LEVEL_TRACE, "Saved first chunk of original body (no accumulation)");
// Allocates next chain.
curr_original_contents_link->next = ngx_alloc_chain_link(pool);
ngx_memset(curr_original_contents_link->next, 0, sizeof(ngx_chain_t));
curr_original_contents_link = curr_original_contents_link->next;
}
} }
ngx_memcpy(curr_input_link->buf, output_buffer, sizeof(ngx_buf_t)); ngx_memcpy(curr_input_link->buf, output_buffer, sizeof(ngx_buf_t));
@@ -522,7 +530,7 @@ decompress_chain(
ngx_int_t ngx_int_t
decompress_body( decompress_body(
CompressionStream *decompression_stream, CompressionStream *decompression_stream,
const ngx_http_chunk_type_e chunk_type, const AttachmentDataType chunk_type,
int *is_last_decompressed_part, int *is_last_decompressed_part,
ngx_chain_t **body, ngx_chain_t **body,
ngx_chain_t **original_body_contents, ngx_chain_t **original_body_contents,
@@ -563,7 +571,7 @@ ngx_int_t
compress_body( compress_body(
CompressionStream *compression_stream, CompressionStream *compression_stream,
const CompressionType compression_type, const CompressionType compression_type,
const ngx_http_chunk_type_e chunk_type, const AttachmentDataType chunk_type,
const int is_last_part, const int is_last_part,
ngx_chain_t **body, ngx_chain_t **body,
ngx_chain_t **original_body_contents, ngx_chain_t **original_body_contents,
@@ -581,21 +589,31 @@ compress_body(
return NGX_ERROR; return NGX_ERROR;
} }
if (compression_type == BROTLI) { body_type = chunk_type == REQUEST_BODY ? "request" : "response";
// Brotli compression is not supported.
// This if statement serves a case that the compression type is set to BROTLI const char* format_name = "unknown";
// For now, we should not reach inside this function with a compression type of BROTLI. switch (compression_type) {
write_dbg(DBG_LEVEL_WARNING, "Brotli compression is not supported"); case GZIP:
format_name = "gzip";
break;
case ZLIB:
format_name = "zlib";
break;
case BROTLI:
format_name = "brotli";
break;
default:
write_dbg(DBG_LEVEL_WARNING, "Unknown compression type: %d", compression_type);
return NGX_ERROR; return NGX_ERROR;
} }
body_type = chunk_type == REQUEST_BODY ? "request" : "response";
write_dbg( write_dbg(
DBG_LEVEL_TRACE, DBG_LEVEL_TRACE,
"Compressing plain-text %s body in the format \"%s\"", "Compressing plain-text %s body in the format \"%s\"",
body_type, body_type,
compression_type == GZIP ? "gzip" : "zlib" format_name
); );
// Checks if the compression was successful. // Checks if the compression was successful.
compress_res = compress_chain( compress_res = compress_chain(
compression_stream, compression_stream,

View File

@@ -18,7 +18,7 @@
#include <ngx_core.h> #include <ngx_core.h>
#include "nginx_attachment_common.h" #include "nano_attachment_common.h"
#include "compression_utils.h" #include "compression_utils.h"
/// @struct ngx_cp_http_compression_params /// @struct ngx_cp_http_compression_params
@@ -58,7 +58,7 @@ void initialize_compression_debug_printing();
ngx_int_t ngx_int_t
decompress_body( decompress_body(
CompressionStream *decompression_stream, CompressionStream *decompression_stream,
const ngx_http_chunk_type_e chunk_type, const AttachmentDataType chunk_type,
int *is_last_decompressed_part, int *is_last_decompressed_part,
ngx_chain_t **body, ngx_chain_t **body,
ngx_chain_t **original_body_contents, ngx_chain_t **original_body_contents,
@@ -91,7 +91,7 @@ ngx_int_t
compress_body( compress_body(
CompressionStream *compression_stream, CompressionStream *compression_stream,
const CompressionType compression_type, const CompressionType compression_type,
const ngx_http_chunk_type_e chunk_type, const AttachmentDataType chunk_type,
const int is_last_part, const int is_last_part,
ngx_chain_t **body, ngx_chain_t **body,
ngx_chain_t **original_body_contents, ngx_chain_t **original_body_contents,

View File

@@ -186,11 +186,31 @@ void
ngx_add_event_id_to_header(ngx_http_request_t *request) ngx_add_event_id_to_header(ngx_http_request_t *request)
{ {
u_char *uuid = (u_char *)get_web_response_uuid(); u_char *uuid = (u_char *)get_web_response_uuid();
if (uuid == NULL) {
write_dbg(DBG_LEVEL_WARNING, "web_response_uuid is NULL, skipping X-Event-ID header");
return;
}
ngx_uint_t uuid_size = get_web_response_uuid_size(); ngx_uint_t uuid_size = get_web_response_uuid_size();
if (uuid_size == 0) {
write_dbg(DBG_LEVEL_WARNING, "web_response_uuid_size is 0, skipping X-Event-ID header");
return;
}
// Validate that UUID contains actual data, not just null bytes
if (uuid[0] == '\0') {
write_dbg(
DBG_LEVEL_WARNING,
"web_response_uuid is empty (contains null bytes) despite size %d, skipping X-Event-ID header",
uuid_size
);
return;
}
static u_char uuid_key[] = { 'X', '-', 'E', 'v', 'e', 'n', 't', '-', 'I', 'D' }; static u_char uuid_key[] = { 'X', '-', 'E', 'v', 'e', 'n', 't', '-', 'I', 'D' };
write_dbg( write_dbg(
DBG_LEVEL_WARNING, DBG_LEVEL_TRACE,
"Adding instance ID to header. Incident ID: %s, Incident ID size: %d", "Adding instance ID to header. Incident ID: %s, Incident ID size: %d",
uuid, uuid,
uuid_size uuid_size
@@ -204,6 +224,71 @@ ngx_add_event_id_to_header(ngx_http_request_t *request)
); );
} }
ngx_int_t
ngx_http_cp_finalize_custom_response_request(ngx_http_request_t *request)
{
ngx_chain_t out_chain[1];
ngx_int_t rc;
uint16_t response_code;
write_dbg(DBG_LEVEL_TRACE, "Finalizing Custom JSON Response request");
// Get JSON response data
response_code = get_response_code_json();
rc = get_response_page_json(request, &out_chain);
if (rc != NGX_OK) {
write_dbg(DBG_LEVEL_WARNING, "Failed to get JSON response page");
goto CUSTOM_RESPONSE_OUT;
}
request->keepalive = 0;
request->headers_out.status = response_code;
request->headers_out.status_line.len = 0;
delete_headers_list(&request->headers_out.headers);
if (get_response_content_type() == CONTENT_TYPE_TEXT_HTML) {
static u_char text_html[] = {'t', 'e', 'x', 't', '/', 'h', 't', 'm', 'l'};
request->headers_out.content_type.len = sizeof(text_html);
request->headers_out.content_type.data = text_html;
request->headers_out.content_type_len = request->headers_out.content_type.len;
} else {
static u_char json_content_type[] = {'a', 'p', 'p', 'l', 'i', 'c', 'a', 't', 'i', 'o', 'n', '/', 'j', 's', 'o', 'n'};
request->headers_out.content_type.len = sizeof(json_content_type);
request->headers_out.content_type.data = json_content_type;
request->headers_out.content_type_len = request->headers_out.content_type.len;
}
// Set content length
request->headers_out.content_length_n = get_response_page_length_json();
rc = ngx_http_send_header(request);
if (rc == NGX_ERROR || rc > NGX_OK) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to send Custom JSON Response headers (result: %d)",
rc
);
goto CUSTOM_RESPONSE_OUT;
}
write_dbg(DBG_LEVEL_TRACE, "Successfully sent Custom JSON Response headers");
// Send the JSON response body using the chain data
rc = ngx_http_output_filter(request, out_chain);
if (rc != NGX_OK && rc != NGX_AGAIN) {
write_dbg(DBG_LEVEL_WARNING, "Failed to send Custom JSON Response");
} else {
write_dbg(DBG_LEVEL_TRACE, "Custom JSON Response sent successfully");
}
CUSTOM_RESPONSE_OUT:
ngx_http_finalize_request(request, NGX_HTTP_CLOSE);
return NGX_HTTP_CLOSE;
}
ngx_int_t ngx_int_t
ngx_http_cp_finalize_rejected_request(ngx_http_request_t *request, int is_response_phase) ngx_http_cp_finalize_rejected_request(ngx_http_request_t *request, int is_response_phase)
{ {
@@ -241,6 +326,7 @@ ngx_http_cp_finalize_rejected_request(ngx_http_request_t *request, int is_respon
goto CUSTOM_RES_OUT; goto CUSTOM_RES_OUT;
} }
delete_headers_list(&request->headers_out.headers);
if (get_response_code() == NGX_HTTP_TEMPORARY_REDIRECT) { if (get_response_code() == NGX_HTTP_TEMPORARY_REDIRECT) {
// Handling redirect web response. // Handling redirect web response.
write_dbg( write_dbg(
@@ -275,7 +361,7 @@ ngx_http_cp_finalize_rejected_request(ngx_http_request_t *request, int is_respon
ngx_add_event_id_to_header(request); ngx_add_event_id_to_header(request);
if (get_response_page_length() == 0) { if (get_response_page_length_web_page() == 0) {
// Page details were not provided. // Page details were not provided.
write_dbg( write_dbg(
DBG_LEVEL_WARNING, DBG_LEVEL_WARNING,
@@ -289,9 +375,7 @@ ngx_http_cp_finalize_rejected_request(ngx_http_request_t *request, int is_respon
request->headers_out.content_type.len = size_of_text_html; request->headers_out.content_type.len = size_of_text_html;
request->headers_out.content_type_len = request->headers_out.content_type.len; request->headers_out.content_type_len = request->headers_out.content_type.len;
request->headers_out.content_type.data = text_html; request->headers_out.content_type.data = text_html;
request->headers_out.content_length_n = get_response_page_length(); request->headers_out.content_length_n = get_response_page_length_web_page();
delete_headers_list(&request->headers_out.headers);
write_dbg(DBG_LEVEL_TRACE, "Sending response headers for rejected request"); write_dbg(DBG_LEVEL_TRACE, "Sending response headers for rejected request");
rc = ngx_http_send_header(request); rc = ngx_http_send_header(request);
@@ -313,7 +397,7 @@ ngx_http_cp_finalize_rejected_request(ngx_http_request_t *request, int is_respon
if (send_response_custom_body) { if (send_response_custom_body) {
// Sending response custom body. // Sending response custom body.
if (get_response_page(request, &out_chain) != NGX_OK) { if (get_block_page_response(request, &out_chain) != NGX_OK) {
// Failed to generate custom response page. // Failed to generate custom response page.
write_dbg( write_dbg(
DBG_LEVEL_DEBUG, DBG_LEVEL_DEBUG,
@@ -611,7 +695,7 @@ perform_header_modification(
ngx_list_t *headers, ngx_list_t *headers,
ngx_http_cp_list_iterator *headers_iterator, ngx_http_cp_list_iterator *headers_iterator,
ngx_http_cp_modification_list *modification, ngx_http_cp_modification_list *modification,
ngx_http_modification_type_e type, HttpModificationType type,
ngx_flag_t is_content_length ngx_flag_t is_content_length
) )
{ {
@@ -674,7 +758,7 @@ perform_header_modification(
/// - #NULL if failed to get the next element. /// - #NULL if failed to get the next element.
/// ///
static ngx_http_cp_modification_list * static ngx_http_cp_modification_list *
get_next_header_modification(ngx_http_cp_modification_list *modification, ngx_http_modification_type_e type) get_next_header_modification(ngx_http_cp_modification_list *modification, HttpModificationType type)
{ {
switch (type) { switch (type) {
case APPEND: case APPEND:
@@ -698,7 +782,7 @@ get_next_header_modification(ngx_http_cp_modification_list *modification, ngx_ht
static void static void
free_header_modification( free_header_modification(
ngx_http_cp_modification_list *modification, ngx_http_cp_modification_list *modification,
ngx_http_modification_type_e type, HttpModificationType type,
ngx_pool_t *pool ngx_pool_t *pool
) )
{ {
@@ -731,7 +815,7 @@ ngx_http_cp_header_modifier(
ngx_flag_t is_content_length ngx_flag_t is_content_length
) )
{ {
ngx_http_modification_type_e type; HttpModificationType type;
ngx_http_cp_modification_list *next_modification; ngx_http_cp_modification_list *next_modification;
ngx_http_cp_list_iterator headers_iterator; ngx_http_cp_list_iterator headers_iterator;
init_list_iterator(headers, &headers_iterator); init_list_iterator(headers, &headers_iterator);
@@ -785,75 +869,106 @@ ngx_http_cp_body_modifier(
ngx_pool_t *pool ngx_pool_t *pool
) )
{ {
static const size_t MAX_MODIFICATIONS_PER_CHUNK = 64;
ngx_http_cp_modification_list *next_modification; ngx_http_cp_modification_list *next_modification;
ngx_uint_t cur_body_chunk = 0; ngx_uint_t cur_body_chunk = 0;
ngx_chain_t *chain_iter; ngx_chain_t *chain_iter;
ngx_chain_t *injected_chain_elem;
ngx_uint_t num_appended_elements;
size_t cur_chunk_size = 0; size_t cur_chunk_size = 0;
for (chain_iter = body_chain; chain_iter; chain_iter = chain_iter->next, cur_body_chunk++) { for (chain_iter = body_chain; chain_iter; chain_iter = chain_iter->next, cur_body_chunk++) {
// Iterates of the body chains
if (curr_modification == NULL) return NGX_OK; if (curr_modification == NULL) return NGX_OK;
if (curr_modification->modification.orig_buff_index != cur_body_chunk) continue;
cur_chunk_size = chain_iter->buf->last - chain_iter->buf->pos; cur_chunk_size = chain_iter->buf->last - chain_iter->buf->pos;
if (cur_chunk_size == 0) { if (cur_chunk_size == 0) {
write_dbg(DBG_LEVEL_TRACE, "No need to modify body chunk of size 0. Chunk index: %d", cur_body_chunk); write_dbg(DBG_LEVEL_TRACE, "No need to modify body chunk of size 0. Chunk index: %d", cur_body_chunk);
continue; continue;
} }
write_dbg( ngx_http_cp_modification_list *modifications_for_chunk[MAX_MODIFICATIONS_PER_CHUNK];
DBG_LEVEL_DEBUG, ngx_uint_t modification_count = 0;
"Handling current modification. " ngx_http_cp_modification_list *temp_mod = curr_modification;
"Injection position: %d, injection size: %d, original buffer index: %d, modification buffer: %s",
curr_modification->modification.injection_pos,
curr_modification->modification.injection_size,
curr_modification->modification.orig_buff_index,
curr_modification->modification_buffer
);
// Create a chain element.
injected_chain_elem = create_chain_elem(
curr_modification->modification.injection_size,
curr_modification->modification_buffer,
pool
);
if (injected_chain_elem == NULL) { while (temp_mod != NULL && temp_mod->modification.orig_buff_index == cur_body_chunk
&& modification_count < MAX_MODIFICATIONS_PER_CHUNK) {
modifications_for_chunk[modification_count] = temp_mod;
modification_count++;
temp_mod = temp_mod->next;
}
if (modification_count == 0) {
continue;
}
size_t original_size = chain_iter->buf->last - chain_iter->buf->pos;
size_t total_injection_size = 0;
for (ngx_uint_t i = 0; i < modification_count; i++) {
total_injection_size += modifications_for_chunk[i]->modification.injection_size;
}
size_t new_buffer_size = original_size + total_injection_size;
u_char *new_buffer = ngx_palloc(pool, new_buffer_size);
if (new_buffer == NULL) {
free_modifications_list(curr_modification, pool); free_modifications_list(curr_modification, pool);
return NGX_ERROR; return NGX_ERROR;
} }
write_dbg(DBG_LEVEL_DEBUG, "Handling modification of chain element number %d", cur_body_chunk); u_char *original_data = chain_iter->buf->pos;
// Handling modification of a chain element.
if (curr_modification->modification.injection_pos == 0) { // Sort modifications by injection position in DESCENDING order
// Pre appends chain element. for (ngx_uint_t i = 0; i < modification_count - 1; i++) {
prepend_chain_elem(chain_iter, injected_chain_elem); for (ngx_uint_t j = 0; j < modification_count - i - 1; j++) {
chain_iter = chain_iter->next; if (modifications_for_chunk[j]->modification.injection_pos <
num_appended_elements = 0; modifications_for_chunk[j + 1]->modification.injection_pos) {
} else if (curr_modification->modification.injection_pos == chain_iter->buf->last - chain_iter->buf->pos + 1) { ngx_http_cp_modification_list *temp = modifications_for_chunk[j];
// Prepend a chain element. modifications_for_chunk[j] = modifications_for_chunk[j + 1];
append_chain_elem(chain_iter, injected_chain_elem); modifications_for_chunk[j + 1] = temp;
chain_iter = chain_iter->next; }
num_appended_elements = 1; }
} else {
if (split_chain_elem(chain_iter, curr_modification->modification.injection_pos, pool) != NGX_OK) {
// Failed to iterate over the modification.
free_modifications_list(curr_modification, pool);
return NGX_ERROR;
} }
append_chain_elem(chain_iter, injected_chain_elem); // Start with original buffer and apply modifications from end to beginning
chain_iter = chain_iter->next->next; u_char *current_buffer = original_data;
num_appended_elements = 2; size_t current_size = original_size;
for (ngx_uint_t i = 0; i < modification_count; i++) {
ngx_http_cp_modification_list *mod = modifications_for_chunk[i];
size_t injection_pos = mod->modification.injection_pos;
if (injection_pos > current_size) {
continue;
} }
// Moves to the next modification element and frees the modifier. size_t new_size = current_size + mod->modification.injection_size;
next_modification = curr_modification->next;
ngx_pfree(pool, curr_modification); u_char *target_buffer = (i == 0) ? new_buffer : ngx_palloc(pool, new_size);
if (target_buffer == NULL) {
continue;
}
// Copy: [start...injection_pos] + [injection_data] + [injection_pos...end]
ngx_memcpy(target_buffer, current_buffer, injection_pos);
ngx_memcpy(target_buffer + injection_pos, mod->modification_buffer, mod->modification.injection_size);
ngx_memcpy(target_buffer + injection_pos + mod->modification.injection_size,
current_buffer + injection_pos, current_size - injection_pos);
current_buffer = target_buffer;
current_size = new_size;
}
chain_iter->buf->pos = current_buffer;
chain_iter->buf->last = current_buffer + current_size;
chain_iter->buf->start = current_buffer;
chain_iter->buf->end = current_buffer + current_size;
for (ngx_uint_t i = 0; i < modification_count; i++) {
next_modification = modifications_for_chunk[i]->next;
ngx_pfree(pool, modifications_for_chunk[i]->modification_buffer);
ngx_pfree(pool, modifications_for_chunk[i]);
curr_modification = next_modification; curr_modification = next_modification;
}
cur_body_chunk += num_appended_elements;
} }
return NGX_OK; return NGX_OK;
} }

View File

@@ -20,13 +20,13 @@
#include <ngx_core.h> #include <ngx_core.h>
#include <ngx_http.h> #include <ngx_http.h>
#include "nginx_attachment_common.h" #include "nano_attachment_common.h"
/// @struct ngx_http_cp_modification_list /// @struct ngx_http_cp_modification_list
/// @brief A node that holds all the information regarding modifications. /// @brief A node that holds all the information regarding modifications.
typedef struct ngx_http_cp_modification_list { typedef struct ngx_http_cp_modification_list {
struct ngx_http_cp_modification_list *next; ///< Next node. struct ngx_http_cp_modification_list *next; ///< Next node.
ngx_http_cp_inject_data_t modification; ///< Modification data. HttpInjectData modification; ///< Modification data.
char *modification_buffer; ///< Modification buffer used to store extra needed data. char *modification_buffer; ///< Modification buffer used to store extra needed data.
} ngx_http_cp_modification_list; } ngx_http_cp_modification_list;
@@ -83,6 +83,14 @@ ngx_http_cp_file_response_sender(
/// ///
ngx_int_t ngx_http_cp_finalize_rejected_request(ngx_http_request_t *request, int is_response_phase); ngx_int_t ngx_http_cp_finalize_rejected_request(ngx_http_request_t *request, int is_response_phase);
///
/// @brief Finalizing a Custom Response request with JSON success response.
/// @param[in, out] request NGINX request.
/// @return ngx_int_t
/// - #NGX_HTTP_CLOSE
///
ngx_int_t ngx_http_cp_finalize_custom_response_request(ngx_http_request_t *request);
/// ///
/// @brief Modifies headers with the provided modifiers. /// @brief Modifies headers with the provided modifiers.
/// @param[in, out] headers NGINX headers list. /// @param[in, out] headers NGINX headers list.

View File

@@ -19,7 +19,7 @@
#include <ngx_config.h> #include <ngx_config.h>
#include <ngx_core.h> #include <ngx_core.h>
#include "nginx_attachment_common.h" #include "nano_attachment_common.h"
#include "ngx_cp_hooks.h" #include "ngx_cp_hooks.h"
/// ///

View File

@@ -25,7 +25,7 @@
#include "nginx_attachment_util.h" #include "nginx_attachment_util.h"
#include "shmem_ipc.h" #include "shmem_ipc.h"
#include "compression_utils.h" #include "compression_utils.h"
#include "nginx_attachment_common.h" #include "nano_attachment_common.h"
#include "ngx_cp_io.h" #include "ngx_cp_io.h"
#include "ngx_cp_utils.h" #include "ngx_cp_utils.h"
#include "ngx_cp_initializer.h" #include "ngx_cp_initializer.h"
@@ -104,6 +104,7 @@ init_thread_ctx(
ctx->res = NGX_OK; ctx->res = NGX_OK;
ctx->should_return = 0; ctx->should_return = 0;
ctx->should_return_next_filter = 0; ctx->should_return_next_filter = 0;
ctx->chain_part_number = 0;
ctx->chain = chain; ctx->chain = chain;
ctx->modifications = NULL; ctx->modifications = NULL;
} }
@@ -253,11 +254,13 @@ ngx_http_cp_req_body_filter_thread(void *_ctx)
ngx_int_t is_last_part; ngx_int_t is_last_part;
ngx_int_t send_body_result; ngx_int_t send_body_result;
ngx_uint_t num_messages_sent = 0; ngx_uint_t num_messages_sent = 0;
ngx_int_t part_count = 0;
send_body_result = ngx_http_cp_body_sender( send_body_result = ngx_http_cp_body_sender(
ctx->chain, ctx->chain,
REQUEST_BODY, REQUEST_BODY,
session_data_p, session_data_p,
&part_count,
&is_last_part, &is_last_part,
&num_messages_sent, &num_messages_sent,
&ctx->chain &ctx->chain
@@ -318,7 +321,8 @@ ngx_http_cp_req_end_transaction_thread(void *_ctx)
session_data_p->remaining_messages_to_reply += num_messages_sent; session_data_p->remaining_messages_to_reply += num_messages_sent;
if (session_data_p->verdict != TRAFFIC_VERDICT_ACCEPT && if (session_data_p->verdict != TRAFFIC_VERDICT_ACCEPT &&
session_data_p->verdict != TRAFFIC_VERDICT_DROP) { session_data_p->verdict != TRAFFIC_VERDICT_DROP &&
session_data_p->verdict != TRAFFIC_VERDICT_CUSTOM_RESPONSE) {
// Fetch nano services' results. // Fetch nano services' results.
ctx->res = ngx_http_cp_reply_receiver( ctx->res = ngx_http_cp_reply_receiver(
&session_data_p->remaining_messages_to_reply, &session_data_p->remaining_messages_to_reply,
@@ -454,6 +458,7 @@ ngx_http_cp_res_body_filter_thread(void *_ctx)
{ {
struct ngx_http_cp_event_thread_ctx_t *ctx = (struct ngx_http_cp_event_thread_ctx_t *)_ctx; struct ngx_http_cp_event_thread_ctx_t *ctx = (struct ngx_http_cp_event_thread_ctx_t *)_ctx;
ngx_http_request_t *request = ctx->request; ngx_http_request_t *request = ctx->request;
ngx_int_t part_number = ctx->chain_part_number;
ngx_http_cp_session_data *session_data_p = ctx->session_data_p; ngx_http_cp_session_data *session_data_p = ctx->session_data_p;
ngx_int_t send_body_result; ngx_int_t send_body_result;
ngx_uint_t num_messages_sent = 0; ngx_uint_t num_messages_sent = 0;
@@ -470,6 +475,7 @@ ngx_http_cp_res_body_filter_thread(void *_ctx)
ctx->chain, ctx->chain,
RESPONSE_BODY, RESPONSE_BODY,
session_data_p, session_data_p,
&part_number,
&is_last_response_part, &is_last_response_part,
&num_messages_sent, &num_messages_sent,
&ctx->chain &ctx->chain
@@ -519,7 +525,7 @@ ngx_http_cp_res_body_filter_thread(void *_ctx)
); );
if (session_data_p->verdict == TRAFFIC_VERDICT_WAIT) { if (session_data_p->verdict == TRAFFIC_VERDICT_DELAYED) {
if (!ngx_http_cp_hold_verdict(ctx)) { if (!ngx_http_cp_hold_verdict(ctx)) {
session_data_p->verdict = fail_mode_hold_verdict == NGX_OK ? TRAFFIC_VERDICT_ACCEPT : TRAFFIC_VERDICT_DROP; session_data_p->verdict = fail_mode_hold_verdict == NGX_OK ? TRAFFIC_VERDICT_ACCEPT : TRAFFIC_VERDICT_DROP;
updateMetricField(HOLD_THREAD_TIMEOUT, 1); updateMetricField(HOLD_THREAD_TIMEOUT, 1);
@@ -585,7 +591,7 @@ ngx_http_cp_res_body_filter_thread(void *_ctx)
session_data_p->session_id session_data_p->session_id
); );
if (session_data_p->verdict == TRAFFIC_VERDICT_WAIT) { if (session_data_p->verdict == TRAFFIC_VERDICT_DELAYED) {
if (!ngx_http_cp_hold_verdict(ctx)) { if (!ngx_http_cp_hold_verdict(ctx)) {
session_data_p->verdict = fail_mode_hold_verdict == NGX_OK ? TRAFFIC_VERDICT_ACCEPT : TRAFFIC_VERDICT_DROP; session_data_p->verdict = fail_mode_hold_verdict == NGX_OK ? TRAFFIC_VERDICT_ACCEPT : TRAFFIC_VERDICT_DROP;
updateMetricField(HOLD_THREAD_TIMEOUT, 1); updateMetricField(HOLD_THREAD_TIMEOUT, 1);
@@ -632,7 +638,7 @@ ngx_http_cp_hold_verdict_thread(void *_ctx)
session_data_p->session_id, session_data_p->session_id,
request, request,
&ctx->modifications, &ctx->modifications,
HOLD_DATA, REQUEST_DELAYED_VERDICT,
0 0
); );

View File

@@ -42,6 +42,7 @@ struct ngx_http_cp_event_thread_ctx_t
/// Should context continue to the next filter. /// Should context continue to the next filter.
int should_return_next_filter; int should_return_next_filter;
int chain_part_number;
ngx_http_cp_modification_list *modifications; ///< Context's modification. ngx_http_cp_modification_list *modifications; ///< Context's modification.
ngx_str_t waf_tag; ///< WAF tag value for the location block. ngx_str_t waf_tag; ///< WAF tag value for the location block.
@@ -183,7 +184,7 @@ void * ngx_http_cp_res_body_filter_thread(void *_ctx);
/// ///
/// @brief Sends a request to the attachment's service to update the earlier provided "WAIT" verdict. /// @brief Sends a request to the attachment's service to update the earlier provided "WAIT" verdict.
/// @details Communicates with the attachment service by sending a HOLD_DATA request to the attachment's service /// @details Communicates with the attachment service by sending a REQUEST_DELAYED_VERDICT request to the attachment's service
/// and modifies _ctx by the received response. /// and modifies _ctx by the received response.
/// @note _ctx needs to be properly initialized by init_thread_ctx() and /// @note _ctx needs to be properly initialized by init_thread_ctx() and
/// be called after another call returned wait verdict. /// be called after another call returned wait verdict.

View File

@@ -25,7 +25,7 @@
#include "nginx_attachment_util.h" #include "nginx_attachment_util.h"
#include "shmem_ipc.h" #include "shmem_ipc.h"
#include "compression_utils.h" #include "compression_utils.h"
#include "nginx_attachment_common.h" #include "nano_attachment_common.h"
#include "ngx_cp_io.h" #include "ngx_cp_io.h"
#include "ngx_cp_utils.h" #include "ngx_cp_utils.h"
#include "ngx_cp_initializer.h" #include "ngx_cp_initializer.h"
@@ -38,6 +38,11 @@
#include "ngx_cp_metric.h" #include "ngx_cp_metric.h"
#include "ngx_cp_thread.h" #include "ngx_cp_thread.h"
#include "ngx_cp_hooks.h" #include "ngx_cp_hooks.h"
#ifdef NGINX_ASYNC_SUPPORTED
#include "async/ngx_cp_async_core.h"
#include "async/ngx_cp_async_headers.h"
#include "async/ngx_cp_async_body.h"
#endif
extern ngx_module_t ngx_http_cp_attachment_module; ///< CP Attachment module extern ngx_module_t ngx_http_cp_attachment_module; ///< CP Attachment module
@@ -54,7 +59,7 @@ static const uint one_minute = 60;
/// - #ngx_http_cp_session_data pointer if everything was initiated properly. /// - #ngx_http_cp_session_data pointer if everything was initiated properly.
/// - #NULL /// - #NULL
/// ///
static ngx_http_cp_session_data * ngx_http_cp_session_data *
init_cp_session_data(ngx_http_request_t *request) init_cp_session_data(ngx_http_request_t *request)
{ {
static uint32_t session_id = 1; static uint32_t session_id = 1;
@@ -88,6 +93,9 @@ init_cp_session_data(ngx_http_request_t *request)
session_data->processed_req_body_size = 0; session_data->processed_req_body_size = 0;
session_data->processed_res_body_size = 0; session_data->processed_res_body_size = 0;
session_data->is_res_body_inspected = 0; session_data->is_res_body_inspected = 0;
session_data->async_processing_needed = 0;
session_data->body_processed = 0;
session_data->initial_async_mode = -1;
ngx_http_set_ctx(request, session_data, ngx_http_cp_attachment_module); ngx_http_set_ctx(request, session_data, ngx_http_cp_attachment_module);
@@ -109,41 +117,118 @@ fini_cp_session_data(ngx_http_cp_session_data *session_data)
finiCompressionStream(session_data->response_data.decompression_stream); finiCompressionStream(session_data->response_data.decompression_stream);
session_data->response_data.decompression_stream = NULL; session_data->response_data.decompression_stream = NULL;
} }
if (session_data->response_data.decompression_pool != NULL) {
write_dbg(DBG_LEVEL_TRACE, "Destroying decompression pool for session ID %d", session_data->session_id);
ngx_destroy_pool(session_data->response_data.decompression_pool);
session_data->response_data.decompression_pool = NULL;
}
if (session_data->response_data.recompression_pool != NULL) {
write_dbg(DBG_LEVEL_TRACE, "Destroying recompression pool for session ID %d", session_data->session_id);
ngx_destroy_pool(session_data->response_data.recompression_pool);
session_data->response_data.recompression_pool = NULL;
}
} }
/// ///
/// @brief Cleans up session data. /// @brief Copies compressed data back into original Nginx buffers to avoid pool accumulation
/// @param[in] data Pointer to the session data to be cleaned up. /// \param original_chain The original Nginx buffer chain (will be modified to contain compressed data)
/// \param compressed_chain The compressed data chain from temporary pool
/// \param request_pool Pool to use for overflow buffers if needed
/// \return NGX_OK or NGX_ERROR
///
static ngx_int_t
copy_compressed_to_original_buffers(
ngx_chain_t *original_chain,
ngx_chain_t *compressed_chain,
ngx_pool_t *request_pool
)
{
ngx_chain_t *orig_cl = original_chain;
ngx_chain_t *comp_cl = compressed_chain;
u_char *comp_pos;
size_t comp_remaining;
while (comp_cl != NULL) {
comp_pos = comp_cl->buf->pos;
comp_remaining = comp_cl->buf->last - comp_cl->buf->pos;
while (comp_remaining > 0 && orig_cl != NULL) {
size_t orig_capacity = orig_cl->buf->end - orig_cl->buf->pos;
size_t copy_size = comp_remaining < orig_capacity ? comp_remaining : orig_capacity;
// Copy compressed data into original buffer
ngx_memcpy(orig_cl->buf->pos, comp_pos, copy_size);
orig_cl->buf->last = orig_cl->buf->pos + copy_size;
orig_cl->buf->memory = 1;
orig_cl->buf->temporary = 1;
comp_pos += copy_size;
comp_remaining -= copy_size;
if (comp_remaining > 0) {
// Need to allocate overflow buffer
ngx_chain_t *overflow = ngx_alloc_chain_link(request_pool);
if (overflow == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to allocate overflow chain link");
return NGX_ERROR;
}
overflow->buf = ngx_calloc_buf(request_pool);
if (overflow->buf == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to allocate overflow buffer");
return NGX_ERROR;
}
overflow->buf->memory = 1;
overflow->buf->temporary = 1;
overflow->next = orig_cl->next;
orig_cl->next = overflow;
orig_cl = overflow->next;
overflow->next = NULL;
write_dbg(DBG_LEVEL_TRACE, "Created overflow buffer of size %zu", comp_remaining);
break;
}
orig_cl = orig_cl->next;
}
comp_cl = comp_cl->next;
}
return NGX_OK;
}
///
/// @brief Cleanup handler for session data (called when request pool is destroyed)
/// \param data Pointer to session data
/// ///
static void static void
ngx_session_data_cleanup(void *data) ngx_session_data_cleanup(void *data)
{ {
if (data == NULL) return;
ngx_http_cp_session_data *session_data = (ngx_http_cp_session_data *)data; ngx_http_cp_session_data *session_data = (ngx_http_cp_session_data *)data;
write_dbg(DBG_LEVEL_DEBUG, "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 != NULL) {
if (session_data->response_data.original_compressed_body != NULL) {
free_chain(session_data->response_data.request_pool, session_data->response_data.original_compressed_body);
session_data->response_data.original_compressed_body = NULL;
}
fini_cp_session_data(session_data); fini_cp_session_data(session_data);
}
} }
/// ///
/// @brief initializes session data with response_data chain allocation and cleanup from given ngx pool /// @brief Initializes storage for the FIRST chunk only of original compressed body
/// This provides a reference without accumulating all chunks (prevents memory spikes)
/// \param session_data /// \param session_data
/// \param request /// \param pool Request pool
/// \return /// \return NGX_OK or NGX_ERROR
/// ///
static ngx_int_t static ngx_int_t
init_cp_session_original_body(ngx_http_cp_session_data *session_data, ngx_pool_t *pool) init_cp_session_original_body(ngx_http_cp_session_data *session_data, ngx_pool_t *request_pool)
{ {
ngx_pool_cleanup_t *cln; // Only initialize once (for first chunk only)
if (session_data->response_data.original_compressed_body != NULL) {
return NGX_OK;
}
write_dbg(DBG_LEVEL_TRACE, "Initializing original compressed body for session ID %d", session_data->session_id); write_dbg(DBG_LEVEL_TRACE, "Initializing original compressed body storage (first chunk only) for session ID %d", session_data->session_id);
session_data->response_data.original_compressed_body = ngx_alloc_chain_link(pool); session_data->response_data.original_compressed_body = ngx_alloc_chain_link(request_pool);
if (session_data->response_data.original_compressed_body == NULL) { if (session_data->response_data.original_compressed_body == NULL) {
write_dbg( write_dbg(
@@ -153,22 +238,14 @@ init_cp_session_original_body(ngx_http_cp_session_data *session_data, ngx_pool_t
); );
return NGX_ERROR; return NGX_ERROR;
} }
session_data->response_data.request_pool = pool; session_data->response_data.request_pool = request_pool;
ngx_memset(session_data->response_data.original_compressed_body, 0, sizeof(ngx_chain_t)); ngx_memset(session_data->response_data.original_compressed_body, 0, sizeof(ngx_chain_t));
cln = ngx_pool_cleanup_add(pool, 0); ngx_pool_cleanup_t *cln = ngx_pool_cleanup_add(request_pool, 0);
if (cln == NULL) { if (cln == NULL) {
write_dbg( write_dbg(DBG_LEVEL_WARNING, "Failed to add cleanup handler for session ID %d", session_data->session_id);
DBG_LEVEL_WARNING,
"Failed to allocate cleanup memory for original compressed body in session ID %d\n",
session_data->session_id
);
ngx_free_chain(session_data->response_data.request_pool, session_data->response_data.original_compressed_body);
session_data->response_data.original_compressed_body = NULL;
return NGX_ERROR; return NGX_ERROR;
} }
write_dbg(DBG_LEVEL_TRACE, "Adding session_data cleanup handler for session ID %d", session_data->session_id);
cln->handler = ngx_session_data_cleanup; cln->handler = ngx_session_data_cleanup;
cln->data = session_data; cln->data = session_data;
@@ -182,7 +259,7 @@ init_cp_session_original_body(ngx_http_cp_session_data *session_data, ngx_pool_t
/// - #ngx_http_cp_session_data pointer if everything was initiated properly. /// - #ngx_http_cp_session_data pointer if everything was initiated properly.
/// - #NULL /// - #NULL
/// ///
static ngx_http_cp_session_data * ngx_http_cp_session_data *
recover_cp_session_data(ngx_http_request_t *request) recover_cp_session_data(ngx_http_request_t *request)
{ {
return (ngx_http_cp_session_data *)ngx_http_get_module_ctx(request, ngx_http_cp_attachment_module); return (ngx_http_cp_session_data *)ngx_http_get_module_ctx(request, ngx_http_cp_attachment_module);
@@ -235,7 +312,7 @@ ngx_http_cp_hold_verdict(struct ngx_http_cp_event_thread_ctx_t *ctx)
continue; continue;
} }
if (session_data_p->verdict != TRAFFIC_VERDICT_WAIT) { if (session_data_p->verdict != TRAFFIC_VERDICT_DELAYED) {
// Verdict was updated. // Verdict was updated.
write_dbg( write_dbg(
DBG_LEVEL_DEBUG, DBG_LEVEL_DEBUG,
@@ -251,11 +328,11 @@ ngx_http_cp_hold_verdict(struct ngx_http_cp_event_thread_ctx_t *ctx)
return 0; return 0;
} }
ngx_http_cp_verdict_e ServiceVerdict
enforce_sessions_rate() enforce_sessions_rate()
{ {
ngx_http_cp_sessions_per_minute_limit *sessions_limit = get_periodic_sessions_limit_info(); ngx_http_cp_sessions_per_minute_limit *sessions_limit = get_periodic_sessions_limit_info();
ngx_http_cp_verdict_e verdict = get_sessions_per_minute_limit_verdict(); ServiceVerdict verdict = get_sessions_per_minute_limit_verdict();
unsigned int max_sessions = get_max_sessions_per_minute(); unsigned int max_sessions = get_max_sessions_per_minute();
unsigned int curr_real_second = (unsigned int)(time(NULL)); unsigned int curr_real_second = (unsigned int)(time(NULL));
@@ -404,11 +481,11 @@ ngx_http_cp_request_and_response_size_handler(ngx_http_request_t *request)
} }
ngx_int_t ngx_int_t
ngx_http_cp_req_header_handler(ngx_http_request_t *request) ngx_http_cp_req_header_handler_sync(ngx_http_request_t *request)
{ {
ngx_http_cp_session_data *session_data_p; ngx_http_cp_session_data *session_data_p;
ngx_int_t handle_static_resource_result; ngx_int_t handle_static_resource_result;
ngx_http_cp_verdict_e sessions_per_minute_verdict; ServiceVerdict sessions_per_minute_verdict;
ngx_cp_attachment_conf_t *conf; ngx_cp_attachment_conf_t *conf;
struct ngx_http_cp_event_thread_ctx_t ctx; struct ngx_http_cp_event_thread_ctx_t ctx;
struct timespec hook_time_begin; struct timespec hook_time_begin;
@@ -454,6 +531,11 @@ ngx_http_cp_req_header_handler(ngx_http_request_t *request)
set_current_session_id(session_data_p->session_id); 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); write_dbg(DBG_LEVEL_DEBUG, "Request header filter handling session ID: %d", session_data_p->session_id);
session_data_p->initial_async_mode = 0;
if (is_ngx_cp_async_mode_enabled_for_request(request)) {
write_dbg(DBG_LEVEL_WARNING, "Async mode detected in sync filter - passing through");
return NGX_DECLINED;
}
init_thread_ctx(&ctx, request, session_data_p, NULL); init_thread_ctx(&ctx, request, session_data_p, NULL);
ctx.waf_tag = conf->waf_tag; ctx.waf_tag = conf->waf_tag;
@@ -553,15 +635,25 @@ ngx_http_cp_req_header_handler(ngx_http_request_t *request)
ctx.res ctx.res
); );
if (session_data_p->verdict == TRAFFIC_VERDICT_WAIT) { if (session_data_p->verdict == TRAFFIC_VERDICT_DELAYED) {
res = ngx_http_cp_hold_verdict(&ctx); res = ngx_http_cp_hold_verdict(&ctx);
if (!res) { if (!res) {
session_data_p->verdict = fail_mode_hold_verdict == NGX_OK ? TRAFFIC_VERDICT_ACCEPT : TRAFFIC_VERDICT_DROP; session_data_p->verdict = fail_mode_hold_verdict == NGX_OK ? TRAFFIC_VERDICT_ACCEPT : TRAFFIC_VERDICT_DROP;
updateMetricField(HOLD_THREAD_TIMEOUT, 1); updateMetricField(HOLD_THREAD_TIMEOUT, 1);
return fail_mode_verdict == NGX_OK ? NGX_DECLINED : fail_mode_verdict; return fail_mode_hold_verdict == NGX_OK ? NGX_DECLINED : fail_mode_hold_verdict;
} }
} }
if (session_data_p->verdict == TRAFFIC_VERDICT_CUSTOM_RESPONSE)
{
write_dbg(
DBG_LEVEL_DEBUG,
"Received NGX_HTTP_FORBIDDEN with TRAFFIC_VERDICT_CUSTOM_RESPONSE for session ID: %d, returning Custom Response",
session_data_p->session_id
);
return ngx_http_cp_finalize_custom_response_request(request);
}
calcProcessingTime(session_data_p, &hook_time_begin, 1); calcProcessingTime(session_data_p, &hook_time_begin, 1);
if (ctx.should_return) { if (ctx.should_return) {
return ctx.res == NGX_OK ? NGX_DECLINED : ctx.res; return ctx.res == NGX_OK ? NGX_DECLINED : ctx.res;
@@ -578,7 +670,7 @@ ngx_http_cp_req_header_handler(ngx_http_request_t *request)
} }
ngx_int_t ngx_int_t
ngx_http_cp_req_body_filter(ngx_http_request_t *request, ngx_chain_t *request_body_chain) ngx_http_cp_req_body_filter_sync(ngx_http_request_t *request, ngx_chain_t *request_body_chain)
{ {
struct ngx_http_cp_event_thread_ctx_t ctx; struct ngx_http_cp_event_thread_ctx_t ctx;
ngx_http_cp_session_data *session_data_p = recover_cp_session_data(request); ngx_http_cp_session_data *session_data_p = recover_cp_session_data(request);
@@ -589,7 +681,12 @@ ngx_http_cp_req_body_filter(ngx_http_request_t *request, ngx_chain_t *request_bo
if (session_data_p == NULL) return ngx_http_next_request_body_filter(request, request_body_chain); if (session_data_p == NULL) return ngx_http_next_request_body_filter(request, request_body_chain);
write_dbg(DBG_LEVEL_DEBUG, "Request body received"); write_dbg(DBG_LEVEL_DEBUG, "Request body received [SYNC]");
if (session_data_p->initial_async_mode || (!session_data_p->initial_async_mode && is_ngx_cp_async_mode_enabled_for_request(request))) {
write_dbg(DBG_LEVEL_WARNING, "Async mode detected in sync filter - passing through");
return ngx_http_next_request_body_filter(request, request_body_chain);
}
set_current_session_id(0); set_current_session_id(0);
@@ -688,7 +785,7 @@ ngx_http_cp_req_body_filter(ngx_http_request_t *request, ngx_chain_t *request_bo
ctx.res ctx.res
); );
if (session_data_p->verdict == TRAFFIC_VERDICT_WAIT) { if (session_data_p->verdict == TRAFFIC_VERDICT_DELAYED) {
res = ngx_http_cp_hold_verdict(&ctx); res = ngx_http_cp_hold_verdict(&ctx);
if (!res) { if (!res) {
session_data_p->verdict = fail_mode_hold_verdict == NGX_OK ? TRAFFIC_VERDICT_ACCEPT : TRAFFIC_VERDICT_DROP; session_data_p->verdict = fail_mode_hold_verdict == NGX_OK ? TRAFFIC_VERDICT_ACCEPT : TRAFFIC_VERDICT_DROP;
@@ -717,12 +814,23 @@ ngx_http_cp_req_body_filter(ngx_http_request_t *request, ngx_chain_t *request_bo
return fail_mode_verdict == NGX_OK ? ngx_http_next_request_body_filter(request, request_body_chain) : NGX_HTTP_FORBIDDEN; return fail_mode_verdict == NGX_OK ? ngx_http_next_request_body_filter(request, request_body_chain) : NGX_HTTP_FORBIDDEN;
} }
if (session_data_p->verdict == TRAFFIC_VERDICT_WAIT) { if (session_data_p->verdict == TRAFFIC_VERDICT_CUSTOM_RESPONSE)
{
write_dbg(
DBG_LEVEL_DEBUG,
"Received NGX_HTTP_FORBIDDEN with TRAFFIC_VERDICT_CUSTOM_RESPONSE for session ID: %d, returning Custom Response",
session_data_p->session_id
);
return ngx_http_cp_finalize_custom_response_request(request);
}
if (session_data_p->verdict == TRAFFIC_VERDICT_DELAYED) {
write_dbg(DBG_LEVEL_DEBUG, "spawn ngx_http_cp_hold_verdict"); write_dbg(DBG_LEVEL_DEBUG, "spawn ngx_http_cp_hold_verdict");
res = ngx_http_cp_hold_verdict(&ctx); res = ngx_http_cp_hold_verdict(&ctx);
if (!res) { if (!res) {
write_dbg(DBG_LEVEL_DEBUG, "ngx_http_cp_hold_verdict failed"); write_dbg(DBG_LEVEL_DEBUG, "ngx_http_cp_hold_verdict failed");
updateMetricField(HOLD_THREAD_TIMEOUT, 1); updateMetricField(HOLD_THREAD_TIMEOUT, 1);
return fail_mode_hold_verdict == NGX_OK ? ngx_http_next_request_body_filter(request, request_body_chain) : NGX_HTTP_FORBIDDEN;
} }
} }
@@ -846,7 +954,7 @@ remove_server_header(ngx_http_request_t *r)
} }
ngx_int_t ngx_int_t
ngx_http_cp_res_header_filter(ngx_http_request_t *request) ngx_http_cp_res_header_filter_sync(ngx_http_request_t *request)
{ {
struct ngx_http_cp_event_thread_ctx_t ctx; struct ngx_http_cp_event_thread_ctx_t ctx;
ngx_http_cp_session_data *session_data_p; ngx_http_cp_session_data *session_data_p;
@@ -863,7 +971,12 @@ ngx_http_cp_res_header_filter(ngx_http_request_t *request)
set_current_session_id(session_data_p->session_id); set_current_session_id(session_data_p->session_id);
write_dbg(DBG_LEVEL_DEBUG, "Response header filter handling session ID: %d", session_data_p->session_id); write_dbg(DBG_LEVEL_DEBUG, "Response header filter [SYNC] handling session ID: %d", session_data_p->session_id);
if (session_data_p->initial_async_mode || (!session_data_p->initial_async_mode && is_ngx_cp_async_mode_enabled_for_request(request))) {
write_dbg(DBG_LEVEL_WARNING, "Async mode detected in sync filter - passing through");
return ngx_http_next_response_header_filter(request);
}
if (!isIpcReady()) { if (!isIpcReady()) {
write_dbg( write_dbg(
@@ -978,7 +1091,7 @@ ngx_http_cp_res_header_filter(ngx_http_request_t *request)
} }
ngx_int_t ngx_int_t
ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain) ngx_http_cp_res_body_filter_sync(ngx_http_request_t *request, ngx_chain_t *body_chain)
{ {
struct ngx_http_cp_event_thread_ctx_t ctx; struct ngx_http_cp_event_thread_ctx_t ctx;
ngx_http_cp_session_data *session_data_p; ngx_http_cp_session_data *session_data_p;
@@ -994,10 +1107,15 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain
if (session_data_p == NULL) return ngx_http_next_response_body_filter(request, body_chain); if (session_data_p == NULL) return ngx_http_next_response_body_filter(request, body_chain);
set_current_session_id(session_data_p->session_id); set_current_session_id(session_data_p->session_id);
write_dbg(DBG_LEVEL_DEBUG, "Response body filter handling response ID: %d", session_data_p->session_id); write_dbg(DBG_LEVEL_DEBUG, "Response body filter [SYNC] handling response ID: %d", session_data_p->session_id);
print_buffer_chain(body_chain, "incoming", 32, DBG_LEVEL_TRACE); print_buffer_chain(body_chain, "incoming", 32, DBG_LEVEL_TRACE);
if (session_data_p->initial_async_mode || (!session_data_p->initial_async_mode && is_ngx_cp_async_mode_enabled_for_request(request))) {
write_dbg(DBG_LEVEL_WARNING, "Async mode detected in sync filter - passing through");
return ngx_http_next_response_body_filter(request, body_chain);
}
if (!isIpcReady()) { if (!isIpcReady()) {
write_dbg( write_dbg(
DBG_LEVEL_TRACE, DBG_LEVEL_TRACE,
@@ -1026,18 +1144,18 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain
"Session with corrupted compression has DROP verdict, returning HTTP_FORBIDDEN. Session ID: %d", "Session with corrupted compression has DROP verdict, returning HTTP_FORBIDDEN. Session ID: %d",
session_data_p->session_id session_data_p->session_id
); );
return NGX_HTTP_FORBIDDEN; return ngx_http_filter_finalize_request(request, &ngx_http_cp_attachment_module, NGX_HTTP_FORBIDDEN);
} }
return ngx_http_next_response_body_filter(request, body_chain); return ngx_http_next_response_body_filter(request, body_chain);
} }
if ( if (
session_data_p->verdict != TRAFFIC_VERDICT_INSPECT && session_data_p->verdict != TRAFFIC_VERDICT_INSPECT &&
session_data_p->verdict != TRAFFIC_VERDICT_WAIT && session_data_p->verdict != TRAFFIC_VERDICT_DELAYED &&
( (
session_data_p->verdict != TRAFFIC_VERDICT_ACCEPT || session_data_p->verdict != TRAFFIC_VERDICT_ACCEPT ||
session_data_p->response_data.new_compression_type == NO_COMPRESSION || session_data_p->response_data.new_compression_type == NO_COMPRESSION ||
session_data_p->response_data.new_compression_type == BROTLI || (is_brotli_inspection_enabled && session_data_p->response_data.new_compression_type == BROTLI) ||
session_data_p->response_data.num_body_chunk == 0 session_data_p->response_data.num_body_chunk == 0
) )
) { ) {
@@ -1052,7 +1170,7 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain
"Session has DROP verdict, returning HTTP_FORBIDDEN instead of streaming. Session ID: %d", "Session has DROP verdict, returning HTTP_FORBIDDEN instead of streaming. Session ID: %d",
session_data_p->session_id session_data_p->session_id
); );
return NGX_HTTP_FORBIDDEN; return ngx_http_filter_finalize_request(request, &ngx_http_cp_attachment_module, NGX_HTTP_FORBIDDEN);
} }
return ngx_http_next_response_body_filter(request, body_chain); return ngx_http_next_response_body_filter(request, body_chain);
} }
@@ -1069,22 +1187,69 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain
return ngx_http_next_response_body_filter(request, body_chain); return ngx_http_next_response_body_filter(request, body_chain);
} }
if (body_chain->buf->pos != NULL && session_data_p->response_data.new_compression_type != NO_COMPRESSION && session_data_p->response_data.new_compression_type != BROTLI) { // Save original chain before any processing (we'll copy compressed data back to these buffers)
write_dbg(DBG_LEVEL_TRACE, "Decompressing response body"); ngx_chain_t *original_nginx_chain = body_chain;
if (init_cp_session_original_body(session_data_p, request->pool) == NGX_OK) { if (
body_chain->buf->pos != NULL &&
session_data_p->response_data.new_compression_type != NO_COMPRESSION &&
(
session_data_p->response_data.new_compression_type != BROTLI ||
(session_data_p->response_data.new_compression_type == BROTLI && is_brotli_inspection_enabled)
)
) {
write_dbg(
DBG_LEVEL_TRACE,
"Decompressing response body for session ID %d, compression type: %d, chunk: %d",
session_data_p->session_id,
session_data_p->response_data.new_compression_type,
session_data_p->response_data.num_body_chunk
);
// Save original body ONLY on first chunk (prevents memory spikes from accumulation)
if (session_data_p->response_data.num_body_chunk == 1) {
if (init_cp_session_original_body(session_data_p, request->pool) != NGX_OK) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to initialize original compressed body storage for session ID %d",
session_data_p->session_id
);
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;
return fail_mode_verdict == NGX_OK ?
ngx_http_next_response_body_filter(request, body_chain) :
ngx_http_filter_finalize_request(request, &ngx_http_cp_attachment_module, NGX_HTTP_FORBIDDEN);
}
}
if (session_data_p->response_data.decompression_stream == NULL) { if (session_data_p->response_data.decompression_stream == NULL) {
session_data_p->response_data.decompression_stream = initCompressionStream(); session_data_p->response_data.decompression_stream = initCompressionStream();
} }
// Get or create decompression pool for temporary decompressed data
if (session_data_p->response_data.decompression_pool == NULL) {
session_data_p->response_data.decompression_pool = ngx_create_pool(decompression_pool_size, request->pool->log);
if (session_data_p->response_data.decompression_pool == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to create decompression pool for session ID %d", session_data_p->session_id);
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;
return fail_mode_verdict == NGX_OK ?
ngx_http_next_response_body_filter(request, body_chain) :
ngx_http_filter_finalize_request(request, &ngx_http_cp_attachment_module, NGX_HTTP_FORBIDDEN);
}
write_dbg(DBG_LEVEL_TRACE, "Created decompression pool for session ID %d", session_data_p->session_id);
}
// Use decompression pool for decompressed data (will be destroyed after re-compression)
compression_result = decompress_body( compression_result = decompress_body(
session_data_p->response_data.decompression_stream, session_data_p->response_data.decompression_stream,
RESPONSE_BODY, RESPONSE_BODY,
&is_last_decompressed_part, &is_last_decompressed_part,
&body_chain, &body_chain,
&session_data_p->response_data.original_compressed_body, &session_data_p->response_data.original_compressed_body,
request->pool session_data_p->response_data.decompression_pool // Destroyed after re-compression
); );
}
if (compression_result != NGX_OK) { if (compression_result != NGX_OK) {
write_dbg(DBG_LEVEL_WARNING, "Failed to decompress response body"); write_dbg(DBG_LEVEL_WARNING, "Failed to decompress response body");
@@ -1093,16 +1258,35 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain
session_data_p->response_data.response_data_status = NGX_ERROR; session_data_p->response_data.response_data_status = NGX_ERROR;
return fail_mode_verdict == NGX_OK ? return fail_mode_verdict == NGX_OK ?
ngx_http_next_response_body_filter(request, body_chain) : ngx_http_next_response_body_filter(request, body_chain) :
NGX_ERROR; ngx_http_filter_finalize_request(request, &ngx_http_cp_attachment_module, NGX_HTTP_FORBIDDEN);
} }
} }
if (session_data_p->verdict == TRAFFIC_VERDICT_ACCEPT) { 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) { if (session_data_p->response_data.compression_stream == NULL) {
session_data_p->response_data.compression_stream = initCompressionStream(); session_data_p->response_data.compression_stream = initCompressionStream();
} }
// Recreate/reset recompression pool for this chunk to avoid accumulation
if (session_data_p->response_data.recompression_pool != NULL) {
write_dbg(DBG_LEVEL_TRACE, "Resetting recompression pool for session ID %d chunk %d",
session_data_p->session_id, session_data_p->response_data.num_body_chunk);
ngx_reset_pool(session_data_p->response_data.recompression_pool);
} else {
session_data_p->response_data.recompression_pool = ngx_create_pool(recompression_pool_size, request->pool->log);
if (session_data_p->response_data.recompression_pool == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to create recompression pool for session ID %d", session_data_p->session_id);
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;
return fail_mode_verdict == NGX_OK ?
ngx_http_next_response_body_filter(request, body_chain) :
ngx_http_filter_finalize_request(request, &ngx_http_cp_attachment_module, NGX_HTTP_FORBIDDEN);
}
write_dbg(DBG_LEVEL_TRACE, "Created recompression pool for session ID %d", session_data_p->session_id);
}
// Compress into temporary pool (body_chain will be modified to point to new compressed chain)
compression_result = compress_body( compression_result = compress_body(
session_data_p->response_data.compression_stream, session_data_p->response_data.compression_stream,
session_data_p->response_data.new_compression_type, session_data_p->response_data.new_compression_type,
@@ -1110,7 +1294,7 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain
is_last_decompressed_part, is_last_decompressed_part,
&body_chain, &body_chain,
NULL, NULL,
request->pool session_data_p->response_data.recompression_pool // Temporary - will be destroyed after copying
); );
if (compression_result != NGX_OK) { if (compression_result != NGX_OK) {
write_dbg(DBG_LEVEL_WARNING, "Failed to compress response body"); write_dbg(DBG_LEVEL_WARNING, "Failed to compress response body");
@@ -1120,16 +1304,36 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain
session_data_p->response_data.response_data_status = NGX_ERROR; session_data_p->response_data.response_data_status = NGX_ERROR;
return fail_mode_verdict == NGX_OK ? return fail_mode_verdict == NGX_OK ?
ngx_http_next_response_body_filter(request, body_chain) : ngx_http_next_response_body_filter(request, body_chain) :
NGX_ERROR; ngx_http_filter_finalize_request(request, &ngx_http_cp_attachment_module, NGX_HTTP_FORBIDDEN);
} }
return ngx_http_next_response_body_filter(request, body_chain); // Copy compressed data from temporary pool back to original Nginx buffers
ngx_chain_t *compressed_chain = body_chain;
if (copy_compressed_to_original_buffers(original_nginx_chain, compressed_chain, request->pool) != NGX_OK) {
write_dbg(DBG_LEVEL_WARNING, "Failed to copy compressed data back to original buffers");
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;
return fail_mode_verdict == NGX_OK ?
ngx_http_next_response_body_filter(request, body_chain) :
ngx_http_filter_finalize_request(request, &ngx_http_cp_attachment_module, NGX_HTTP_FORBIDDEN);
}
print_buffer_chain(original_nginx_chain, "outgoing chain elem", -1, DBG_LEVEL_TRACE);
if (session_data_p->response_data.decompression_pool != NULL) {
write_dbg(DBG_LEVEL_TRACE, "Destroying decompression pool for session ID %d", session_data_p->session_id);
ngx_destroy_pool(session_data_p->response_data.decompression_pool);
session_data_p->response_data.decompression_pool = NULL;
}
return ngx_http_next_response_body_filter(request, original_nginx_chain);
} }
if (was_transaction_timedout(session_data_p)) { if (was_transaction_timedout(session_data_p)) {
// Session was timed out. // Session was timed out.
if (session_data_p->verdict == TRAFFIC_VERDICT_DROP) { if (session_data_p->verdict == TRAFFIC_VERDICT_DROP) {
return NGX_HTTP_FORBIDDEN; return ngx_http_filter_finalize_request(request, &ngx_http_cp_attachment_module, NGX_HTTP_FORBIDDEN);
} }
session_data_p->verdict = fail_mode_verdict == NGX_OK ? TRAFFIC_VERDICT_ACCEPT : TRAFFIC_VERDICT_DROP; session_data_p->verdict = fail_mode_verdict == NGX_OK ? TRAFFIC_VERDICT_ACCEPT : TRAFFIC_VERDICT_DROP;
fini_cp_session_data(session_data_p); fini_cp_session_data(session_data_p);
@@ -1142,7 +1346,7 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain
if (fail_mode_verdict == NGX_OK) { if (fail_mode_verdict == NGX_OK) {
return ngx_http_next_response_body_filter(request, body_chain); return ngx_http_next_response_body_filter(request, body_chain);
} }
return NGX_ERROR; return ngx_http_filter_finalize_request(request, &ngx_http_cp_attachment_module, NGX_HTTP_FORBIDDEN);
} }
if (!session_data_p->was_request_fully_inspected) { if (!session_data_p->was_request_fully_inspected) {
@@ -1182,7 +1386,6 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain
) { ) {
// failed to execute thread task, or it timed out // failed to execute thread task, or it timed out
session_data_p->verdict = fail_mode_verdict == NGX_OK ? TRAFFIC_VERDICT_ACCEPT : TRAFFIC_VERDICT_DROP; session_data_p->verdict = fail_mode_verdict == NGX_OK ? TRAFFIC_VERDICT_ACCEPT : TRAFFIC_VERDICT_DROP;
fini_cp_session_data(session_data_p);
write_dbg( write_dbg(
DBG_LEVEL_DEBUG, DBG_LEVEL_DEBUG,
"res_body_filter thread failed, returning default fail mode verdict. Session id: %d, verdict: %s", "res_body_filter thread failed, returning default fail mode verdict. Session id: %d, verdict: %s",
@@ -1198,7 +1401,7 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain
if (fail_mode_verdict == NGX_OK) { if (fail_mode_verdict == NGX_OK) {
return ngx_http_next_response_body_filter(request, body_chain); return ngx_http_next_response_body_filter(request, body_chain);
} }
return NGX_HTTP_FORBIDDEN; return ngx_http_filter_finalize_request(request, &ngx_http_cp_attachment_module, NGX_HTTP_FORBIDDEN);
} }
write_dbg( write_dbg(
DBG_LEVEL_DEBUG, DBG_LEVEL_DEBUG,
@@ -1222,6 +1425,7 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain
fini_cp_session_data(session_data_p); fini_cp_session_data(session_data_p);
return ngx_http_next_response_body_filter(request, body_chain); return ngx_http_next_response_body_filter(request, body_chain);
} }
ctx.chain_part_number++;
} }
if (ctx.chain) { if (ctx.chain) {
@@ -1237,14 +1441,14 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain
if (fail_mode_verdict == NGX_OK) { if (fail_mode_verdict == NGX_OK) {
return ngx_http_next_response_body_filter(request, body_chain); return ngx_http_next_response_body_filter(request, body_chain);
} }
return NGX_HTTP_FORBIDDEN; return ngx_http_filter_finalize_request(request, &ngx_http_cp_attachment_module, NGX_HTTP_FORBIDDEN);
} }
final_res = ctx.res; final_res = ctx.res;
if (final_res == NGX_HTTP_FORBIDDEN) { if (final_res == NGX_HTTP_FORBIDDEN) {
handle_inspection_success(session_data_p); handle_inspection_success(session_data_p);
return ngx_http_cp_finalize_rejected_request(request, 1); return ngx_http_filter_finalize_request(request, &ngx_http_cp_attachment_module, NGX_HTTP_FORBIDDEN);
} }
if (final_res != NGX_OK) { if (final_res != NGX_OK) {
@@ -1257,10 +1461,16 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain
if (fail_mode_verdict == NGX_OK) { if (fail_mode_verdict == NGX_OK) {
return ngx_http_next_response_body_filter(request, body_chain); return ngx_http_next_response_body_filter(request, body_chain);
} }
return NGX_HTTP_FORBIDDEN; return ngx_http_filter_finalize_request(request, &ngx_http_cp_attachment_module, NGX_HTTP_FORBIDDEN);
} }
if (ctx.modifications && session_data_p->response_data.new_compression_type != BROTLI) { if (
ctx.modifications &&
(
session_data_p->response_data.new_compression_type != BROTLI ||
(session_data_p->response_data.new_compression_type == BROTLI && is_brotli_inspection_enabled)
)
) {
write_dbg(DBG_LEVEL_TRACE, "Handling response body modification"); write_dbg(DBG_LEVEL_TRACE, "Handling response body modification");
if (ngx_http_cp_body_modifier(body_chain, ctx.modifications, request->pool) != NGX_OK) { if (ngx_http_cp_body_modifier(body_chain, ctx.modifications, request->pool) != NGX_OK) {
write_dbg(DBG_LEVEL_WARNING, "Failed to modify response body"); write_dbg(DBG_LEVEL_WARNING, "Failed to modify response body");
@@ -1269,11 +1479,11 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain
if (fail_mode_verdict == NGX_OK) { if (fail_mode_verdict == NGX_OK) {
return ngx_http_next_response_body_filter(request, body_chain); return ngx_http_next_response_body_filter(request, body_chain);
} }
return NGX_HTTP_FORBIDDEN; return ngx_http_filter_finalize_request(request, &ngx_http_cp_attachment_module, NGX_HTTP_FORBIDDEN);
} }
} }
if (ctx.modifications && session_data_p->response_data.new_compression_type == BROTLI) { if (ctx.modifications && session_data_p->response_data.new_compression_type == BROTLI && !is_brotli_inspection_enabled) {
ngx_http_cp_modification_list *mod = ctx.modifications; ngx_http_cp_modification_list *mod = ctx.modifications;
while (mod != NULL) { while (mod != NULL) {
ngx_http_cp_modification_list *next_mod = mod->next; ngx_http_cp_modification_list *next_mod = mod->next;
@@ -1315,7 +1525,7 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain
fini_cp_session_data(session_data_p); fini_cp_session_data(session_data_p);
return fail_mode_verdict == NGX_OK ? return fail_mode_verdict == NGX_OK ?
ngx_http_next_response_body_filter(request, body_chain) : ngx_http_next_response_body_filter(request, body_chain) :
NGX_HTTP_FORBIDDEN; ngx_http_filter_finalize_request(request, &ngx_http_cp_attachment_module, NGX_HTTP_FORBIDDEN);
} }
} }
@@ -1329,7 +1539,7 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain
"Final verdict is DROP, blocking stream to client. Session ID: %d", "Final verdict is DROP, blocking stream to client. Session ID: %d",
session_data_p->session_id session_data_p->session_id
); );
return NGX_HTTP_FORBIDDEN; return ngx_http_filter_finalize_request(request, &ngx_http_cp_attachment_module, NGX_HTTP_FORBIDDEN);
} }
write_dbg( write_dbg(
@@ -1341,3 +1551,117 @@ ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain
return ngx_http_next_response_body_filter(request, body_chain); return ngx_http_next_response_body_filter(request, body_chain);
} }
///
/// @brief Dynamic wrapper for response header filter that chooses sync or async based on configuration.
/// @details Branches internally to call either the synchronous or asynchronous implementation
/// based on the async mode configuration for the specific request.
/// @param[in, out] request NGINX request.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_HTTP_FORBIDDEN
/// - #NGX_ERROR
///
ngx_int_t
ngx_http_cp_res_header_filter(ngx_http_request_t *request)
{
#ifdef NGINX_ASYNC_SUPPORTED
if (is_ngx_cp_async_mode_enabled_for_request(request)) {
return ngx_http_next_response_header_filter(request);
} else {
return ngx_http_cp_res_header_filter_sync(request);
}
#else
// For nginx versions below 1.22, always use sync mode
return ngx_http_cp_res_header_filter_sync(request);
#endif
}
///
/// @brief Dynamic wrapper for response body filter that chooses sync or async based on configuration.
/// @details Branches internally to call either the synchronous or asynchronous implementation
/// based on the async mode configuration for the specific request.
/// @param[in, out] request NGINX request.
/// @param[in, out] body_chain NGINX body chain.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_HTTP_FORBIDDEN
/// - #NGX_ERROR
///
ngx_int_t
ngx_http_cp_res_body_filter(ngx_http_request_t *request, ngx_chain_t *body_chain)
{
ngx_int_t res;
#ifdef NGINX_ASYNC_SUPPORTED
if (is_ngx_cp_async_mode_enabled_for_request(request)) {
res = ngx_http_next_response_body_filter(request, body_chain);
} else {
res = ngx_http_cp_res_body_filter_sync(request, body_chain);
}
if (is_async_toggled_off_in_last_reconfig()) {
disable_ipc_verdict_event_handler();
reset_async_mode_toggled();
}
if (is_async_toggled_on_in_last_reconfig()) {
enable_ipc_verdict_event_handler();
reset_async_mode_toggled();
}
#else
// For nginx versions below 1.22, always use sync mode
res = ngx_http_cp_res_body_filter_sync(request, body_chain);
#endif
return res;
}
///
/// @brief Dynamic wrapper for request header handler that chooses sync or async based on configuration.
/// @details Branches internally to call either the synchronous or asynchronous implementation
/// based on the async mode configuration for the specific request.
/// @param[in, out] request NGINX request.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_HTTP_FORBIDDEN
/// - #NGX_ERROR
///
ngx_int_t
ngx_http_cp_req_header_handler(ngx_http_request_t *request)
{
#ifdef NGINX_ASYNC_SUPPORTED
if (is_ngx_cp_async_mode_enabled_for_request(request)) {
return ngx_http_cp_req_header_handler_async(request);
} else {
return ngx_http_cp_req_header_handler_sync(request);
}
#else
// For nginx versions below 1.22, always use sync mode
return ngx_http_cp_req_header_handler_sync(request);
#endif
}
///
/// @brief Dynamic wrapper for request body filter that chooses sync or async based on configuration.
/// @details Branches internally to call either the synchronous or asynchronous implementation
/// based on the async mode configuration for the specific request.
/// @param[in, out] request NGINX request.
/// @param[in, out] request_body_chain NGINX body chain.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_HTTP_FORBIDDEN
/// - #NGX_ERROR
///
ngx_int_t
ngx_http_cp_req_body_filter(ngx_http_request_t *request, ngx_chain_t *request_body_chain)
{
#ifdef NGINX_ASYNC_SUPPORTED
if (is_ngx_cp_async_mode_enabled_for_request(request)) {
return ngx_http_cp_req_body_filter_async(request, request_body_chain);
} else {
return ngx_http_cp_req_body_filter_sync(request, request_body_chain);
}
#else
// For nginx versions below 1.22, always use sync mode
return ngx_http_cp_req_body_filter_sync(request, request_body_chain);
#endif
}

View File

@@ -24,7 +24,7 @@
#include <unistd.h> #include <unistd.h>
#include "ngx_cp_http_parser.h" #include "ngx_cp_http_parser.h"
#include "nginx_attachment_common.h" #include "nano_attachment_common.h"
#include "ngx_cp_hook_threads.h" #include "ngx_cp_hook_threads.h"
static const int registration_failure_weight = 2; ///< Registration failure weight. static const int registration_failure_weight = 2; ///< Registration failure weight.
@@ -36,7 +36,7 @@ static const ngx_int_t METRIC_TIMEOUT_VAL = METRIC_PERIODIC_TIMEOUT;
/// @details Such as to save verdict and session ID between the request and the response /// @details Such as to save verdict and session ID between the request and the response
typedef struct ngx_http_cp_session_data { typedef struct ngx_http_cp_session_data {
ngx_int_t was_request_fully_inspected; ///< Holds if the request fully inspected. ngx_int_t was_request_fully_inspected; ///< Holds if the request fully inspected.
ngx_http_cp_verdict_e verdict; ///< Holds the session's verdict from the Nano Service. ServiceVerdict verdict; ///< Holds the session's verdict from the Nano Service.
uint32_t session_id; ///< Current session's Id. uint32_t session_id; ///< Current session's Id.
ngx_int_t remaining_messages_to_reply; ///< Remaining messages left for the agent to respond to. ngx_int_t remaining_messages_to_reply; ///< Remaining messages left for the agent to respond to.
ngx_http_response_data response_data; ///< Holds session's response data. ngx_http_response_data response_data; ///< Holds session's response data.
@@ -46,6 +46,9 @@ typedef struct ngx_http_cp_session_data {
uint64_t processed_req_body_size; ///< Holds session's request body's size. 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'. uint64_t processed_res_body_size; ///< Holds session's response body's size'.
ngx_int_t is_res_body_inspected; ///< Holds if the response body was inspected ngx_int_t is_res_body_inspected; ///< Holds if the response body was inspected
ngx_int_t async_processing_needed; ///< Holds if async processing is needed in filters
ngx_int_t body_processed; ///< Holds if request body processing is complete
ngx_int_t initial_async_mode; ///< Initial async mode for this request (0=sync, 1=async, -1=unset)
} ngx_http_cp_session_data; } ngx_http_cp_session_data;
/// ///
@@ -100,7 +103,7 @@ ngx_int_t ngx_http_cp_req_header_handler(ngx_http_request_t *request);
/// ///
/// @brief Sends a request to the nano service to update the verdict. /// @brief Sends a request to the nano service to update the verdict.
/// @note Should be called after the nano service provided the verdict TRAFFIC_VERDICT_WAIT to get the updated verdict. /// @note Should be called after the nano service provided the verdict TRAFFIC_VERDICT_DELAYED to get the updated verdict.
/// @param[in, out] request Event thread context to be updated. /// @param[in, out] request Event thread context to be updated.
/// @returns ngx_int_t /// @returns ngx_int_t
/// - #1 if request was properly communicated with the nano service and provided an updated response. /// - #1 if request was properly communicated with the nano service and provided an updated response.
@@ -121,12 +124,12 @@ ngx_int_t was_transaction_timedout(ngx_http_cp_session_data *ctx);
/// ///
/// @brief Enforces the sessions rate. /// @brief Enforces the sessions rate.
/// @returns ngx_http_cp_verdict_e /// @returns ServiceVerdict
/// - #TRAFFIC_VERDICT_INSPECT /// - #TRAFFIC_VERDICT_INSPECT
/// - #TRAFFIC_VERDICT_ACCEPT /// - #TRAFFIC_VERDICT_ACCEPT
/// - #TRAFFIC_VERDICT_DROP /// - #TRAFFIC_VERDICT_DROP
/// ///
ngx_http_cp_verdict_e enforce_sessions_rate(); ServiceVerdict enforce_sessions_rate();
/// ///
@@ -137,4 +140,25 @@ ngx_http_cp_verdict_e enforce_sessions_rate();
/// ///
ngx_int_t ngx_http_cp_request_and_response_size_handler(ngx_http_request_t *request); ngx_int_t ngx_http_cp_request_and_response_size_handler(ngx_http_request_t *request);
// Session management functions
ngx_http_cp_session_data *init_cp_session_data(ngx_http_request_t *request);
ngx_http_cp_session_data *recover_cp_session_data(ngx_http_request_t *request);
// Utility functions
void calcProcessingTime(ngx_http_cp_session_data *session_data_p, struct timespec *hook_time_begin, int is_req);
ngx_int_t ngx_http_cp_finalize_request_headers_hook(
ngx_http_request_t *request,
ngx_http_cp_session_data *session_data_p,
ngx_http_cp_modification_list *modifications,
ngx_int_t final_res);
// Sync and async handlers
ngx_int_t ngx_http_cp_req_header_handler_sync(ngx_http_request_t *request);
ngx_int_t ngx_http_cp_req_body_filter_sync(ngx_http_request_t *request, ngx_chain_t *request_body_chain);
#ifdef NGINX_ASYNC_SUPPORTED
ngx_int_t ngx_http_cp_req_header_handler_async(ngx_http_request_t *request);
ngx_int_t ngx_http_cp_req_body_filter_async(ngx_http_request_t *request, ngx_chain_t *request_body_chain);
#endif
#endif // __NGX_CP_HOOKS_H__ #endif // __NGX_CP_HOOKS_H__

View File

@@ -56,6 +56,12 @@ typedef struct {
/// Decompression stream /// Decompression stream
CompressionStream *decompression_stream; CompressionStream *decompression_stream;
/// Pool for temporary decompression buffers (destroyed after each chunk's re-compression)
ngx_pool_t *decompression_pool;
/// Pool for re-compression buffers (reset at start of each chunk to prevent accumulation)
ngx_pool_t *recompression_pool;
} ngx_http_response_data; } ngx_http_response_data;
/// ///

View File

@@ -20,12 +20,14 @@
#include <dirent.h> #include <dirent.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <pthread.h> #include <pthread.h>
#include <fcntl.h>
#include <ngx_log.h> #include <ngx_log.h>
#include <ngx_core.h> #include <ngx_core.h>
#include <ngx_string.h> #include <ngx_string.h>
#include <ngx_files.h> #include <ngx_files.h>
#include "nano_attachment_common.h"
#include "nginx_attachment_common.h" #include "nginx_attachment_common.h"
#include "ngx_cp_io.h" #include "ngx_cp_io.h"
#include "ngx_cp_utils.h" #include "ngx_cp_utils.h"
@@ -33,6 +35,7 @@
#include "ngx_cp_compression.h" #include "ngx_cp_compression.h"
#include "attachment_types.h" #include "attachment_types.h"
#include "ngx_http_cp_attachment_module.h" #include "ngx_http_cp_attachment_module.h"
#include "async/ngx_cp_async_core.h"
typedef enum ngx_cp_attachment_registration_state { typedef enum ngx_cp_attachment_registration_state {
NOT_REGISTERED, NOT_REGISTERED,
@@ -41,6 +44,7 @@ typedef enum ngx_cp_attachment_registration_state {
} ngx_cp_attachment_registration_state_e; ///< Indicates the current attachment registation stage. } ngx_cp_attachment_registration_state_e; ///< Indicates the current attachment registation stage.
char unique_id[MAX_NGINX_UID_LEN] = ""; // Holds the unique identifier for this instance. char unique_id[MAX_NGINX_UID_LEN] = ""; // Holds the unique identifier for this instance.
uint32_t unique_id_integer = 0; // Holds the integer representation of the unique identifier.
char shared_verdict_signal_path[128]; // Holds the path associating the attachment and service. char shared_verdict_signal_path[128]; // Holds the path associating the attachment and service.
int registration_socket = -1; // Holds the file descriptor used for registering the instance. int registration_socket = -1; // Holds the file descriptor used for registering the instance.
@@ -239,7 +243,7 @@ init_signaling_socket()
close(comm_socket); close(comm_socket);
comm_socket = -1; comm_socket = -1;
write_dbg( write_dbg(
DBG_LEVEL_DEBUG, DBG_LEVEL_WARNING,
"Could not connect to nano service. Path: %s, Error: %s", "Could not connect to nano service. Path: %s, Error: %s",
server.sun_path, server.sun_path,
strerror(errno) strerror(errno)
@@ -295,6 +299,39 @@ init_signaling_socket()
return NGX_ERROR; return NGX_ERROR;
} }
// Calculate and send target core for affinity pairing
int32_t target_core = -1; // Use signed int, -1 indicates affinity disabled
if (paired_affinity_enabled) {
// Use hash of the container ID part only (before underscore) as offset to distribute containers across CPU cores
char container_id[256];
strncpy(container_id, unique_id, sizeof(container_id) - 1);
container_id[sizeof(container_id) - 1] = '\0';
char *underscore_pos = strrchr(container_id, '_');
if (underscore_pos != NULL) {
*underscore_pos = '\0'; // Truncate at underscore to get container ID only
}
uint32_t affinity_offset = hash_string(container_id);
int num_cores = sysconf(_SC_NPROCESSORS_CONF);
target_core = ((unique_id_integer - 1) + affinity_offset) % num_cores;
write_dbg(DBG_LEVEL_INFO, "Calculated target core for service affinity: worker_id=%d, offset=%u, num_cores=%d, target_core=%d (from container_id: %s, full unique_id: %s)", unique_id_integer, affinity_offset, num_cores, target_core, container_id, unique_id);
} else {
write_dbg(DBG_LEVEL_INFO, "Paired affinity disabled, sending target_core=-1 to service");
}
res = exchange_communication_data_with_service(
comm_socket,
&target_core,
sizeof(int32_t),
WRITE_TO_SOCKET,
&timeout
);
if (res <= 0) {
write_dbg(DBG_LEVEL_WARNING, "Failed to send target core");
return NGX_ERROR;
}
// Get an acknowledgement form the service that communication has been established. // Get an acknowledgement form the service that communication has been established.
timeout = get_timeout_val_sec(1); timeout = get_timeout_val_sec(1);
res = exchange_communication_data_with_service( res = exchange_communication_data_with_service(
@@ -309,7 +346,7 @@ init_signaling_socket()
return NGX_ERROR; return NGX_ERROR;
} }
write_dbg(DBG_LEVEL_DEBUG, "Successfully connected on client socket %d", comm_socket); write_dbg(DBG_LEVEL_WARNING, "Successfully connected on client socket %d", comm_socket);
return NGX_OK; return NGX_OK;
} }
@@ -343,13 +380,22 @@ get_docker_id(char **_docker_id)
size_t len = 0; size_t len = 0;
while (getline(&line, &len, file) != -1) { while (getline(&line, &len, file) != -1) {
char *docker_ptr = strstr(line, "docker/"); char *docker_ptr = strstr(line, "docker/");
if (docker_ptr == NULL) continue; char *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. // We've found a line with "docker/" so the identifier will be right after that.
docker_ptr += strlen("docker/"); docker_ptr += strlen("docker/");
snprintf(docker_id, MAX_CONTAINER_LEN + 1, "%s", docker_ptr); snprintf(docker_id, MAX_CONTAINER_LEN + 1, "%s", docker_ptr);
break; break;
} }
if (containerd_ptr != NULL) {
// We've found a line with "cri-containerd-" so the identifier will be right after that.
containerd_ptr += strlen("cri-containerd-");
snprintf(docker_id, MAX_CONTAINER_LEN + 1, "%s", containerd_ptr);
break;
}
}
free(line); free(line);
fclose(file); fclose(file);
@@ -570,13 +616,22 @@ set_unique_id()
size_t len = 0; size_t len = 0;
while (getline(&line, &len, file) != -1) { while (getline(&line, &len, file) != -1) {
char *docker_ptr = strstr(line, "docker/"); char *docker_ptr = strstr(line, "docker/");
if (docker_ptr == NULL) continue; char *containerd_ptr = strstr(line, "cri-containerd-");
if (docker_ptr != NULL) {
is_container_env = 1; is_container_env = 1;
docker_ptr += strlen("docker/"); docker_ptr += strlen("docker/");
snprintf(docker_id, max_container_id_len + 1, "%s", docker_ptr); snprintf(docker_id, max_container_id_len + 1, "%s", docker_ptr);
break; break;
} }
if (containerd_ptr != NULL) {
is_container_env = 1;
containerd_ptr += strlen("cri-containerd-");
snprintf(docker_id, max_container_id_len + 1, "%s", containerd_ptr);
break;
}
}
free(line); free(line);
fclose(file); fclose(file);
long unsigned int ngx_worker_id = ngx_worker + 1; long unsigned int ngx_worker_id = ngx_worker + 1;
@@ -588,7 +643,10 @@ set_unique_id()
snprintf(unique_id, unique_id_size, "%lu", ngx_worker_id); snprintf(unique_id, unique_id_size, "%lu", ngx_worker_id);
} }
write_dbg(DBG_LEVEL_INFO, "Successfully set attachment's unique_id: '%s'", unique_id); // Set integer representation
unique_id_integer = (uint32_t)ngx_worker_id;
write_dbg(DBG_LEVEL_INFO, "Successfully set attachment's unique_id: '%s' (int: %u)", unique_id, unique_id_integer);
return NGX_OK; return NGX_OK;
} }
@@ -634,7 +692,7 @@ ngx_cp_attachment_init_process(ngx_http_request_t *request)
set_need_registration(REGISTERED); set_need_registration(REGISTERED);
} }
if (comm_socket < 0) { if (comm_socket < 0 || is_async_toggled_in_last_reconfig()) {
write_dbg(DBG_LEVEL_DEBUG, "Registering to nano service"); write_dbg(DBG_LEVEL_DEBUG, "Registering to nano service");
if (init_signaling_socket() == NGX_ERROR) { if (init_signaling_socket() == NGX_ERROR) {
write_dbg(DBG_LEVEL_DEBUG, "Failed to register to the Nano Service"); write_dbg(DBG_LEVEL_DEBUG, "Failed to register to the Nano Service");
@@ -696,6 +754,28 @@ ngx_cp_attachment_init_process(ngx_http_request_t *request)
// we want to indicate about successful registration only once in default level // we want to indicate about successful registration only once in default level
write_dbg(dbg_is_needed ? DBG_LEVEL_DEBUG : DBG_LEVEL_INFO, "NGINX attachment (UID='%s') successfully registered to nano service after %d attempts.", unique_id, num_of_connection_attempts); write_dbg(dbg_is_needed ? DBG_LEVEL_DEBUG : DBG_LEVEL_INFO, "NGINX attachment (UID='%s') successfully registered to nano service after %d attempts.", unique_id, num_of_connection_attempts);
// Set affinity to core based on UID (worker id) only if paired affinity is enabled
if (paired_affinity_enabled) {
// Use hash of the container ID part only (before underscore) as offset to distribute containers across CPU cores
// This ensures workers within same container are distributed evenly, but different containers get different offsets
char container_id[256];
strncpy(container_id, unique_id, sizeof(container_id) - 1);
container_id[sizeof(container_id) - 1] = '\0';
char *underscore_pos = strrchr(container_id, '_');
if (underscore_pos != NULL) {
*underscore_pos = '\0'; // Truncate at underscore to get container ID only
}
uint32_t affinity_offset = hash_string(container_id);
int num_cores = sysconf(_SC_NPROCESSORS_CONF);
write_dbg(DBG_LEVEL_INFO, "Setting CPU affinity for NGINX attachment with UID: %d, offset: %u, num_cores: %d (from container_id: %s, full unique_id: %s)", unique_id_integer, affinity_offset, num_cores, container_id, unique_id);
int err = set_affinity_by_uid_with_offset_fixed_cores(unique_id_integer, affinity_offset, num_cores);
if (err != NGX_OK) {
write_dbg(DBG_LEVEL_WARNING, "Failed to set affinity for worker %d, err %d", ngx_worker, err);
}
}
dbg_is_needed = 1; dbg_is_needed = 1;
num_of_connection_attempts = 0; num_of_connection_attempts = 0;
@@ -736,6 +816,10 @@ disconnect_communication()
nano_service_ipc = NULL; nano_service_ipc = NULL;
} }
#ifdef NGINX_ASYNC_SUPPORTED
disable_ipc_verdict_event_handler();
#endif
set_need_registration(NOT_REGISTERED); set_need_registration(NOT_REGISTERED);
} }

View File

@@ -22,6 +22,8 @@
#include <stdbool.h> #include <stdbool.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <limits.h> #include <limits.h>
#include <errno.h>
#include <string.h>
#include "ngx_cp_utils.h" #include "ngx_cp_utils.h"
#include "ngx_cp_initializer.h" #include "ngx_cp_initializer.h"
@@ -91,7 +93,7 @@ ngx_http_cp_signal_to_service(uint32_t cur_session_id)
/// - #NGX_AGAIN /// - #NGX_AGAIN
/// ///
static ngx_int_t static ngx_int_t
ngx_http_cp_wait_for_service(uint32_t cur_session_id, ngx_http_chunk_type_e chunk_type, ngx_int_t tout_retries) ngx_http_cp_wait_for_service(uint32_t cur_session_id, AttachmentDataType chunk_type, ngx_int_t tout_retries)
{ {
static int dbg_count = 0; static int dbg_count = 0;
static clock_t clock_start = (clock_t) 0; static clock_t clock_start = (clock_t) 0;
@@ -100,7 +102,7 @@ ngx_http_cp_wait_for_service(uint32_t cur_session_id, ngx_http_chunk_type_e chun
uint32_t reply_from_service; uint32_t reply_from_service;
ngx_int_t retry; ngx_int_t retry;
int is_fail_open_disabled = (inspection_mode != NON_BLOCKING_THREAD); int is_fail_open_disabled = (inspection_mode != NON_BLOCKING_THREAD);
ngx_uint_t timeout = chunk_type == HOLD_DATA ? fail_open_hold_timeout : fail_open_timeout; ngx_uint_t timeout = chunk_type == REQUEST_DELAYED_VERDICT ? fail_open_hold_timeout : fail_open_timeout;
res = ngx_http_cp_signal_to_service(cur_session_id); res = ngx_http_cp_signal_to_service(cur_session_id);
if (res != NGX_OK) return res; if (res != NGX_OK) return res;
@@ -197,7 +199,7 @@ ngx_http_cp_send_data_to_service(
uint8_t num_of_data_elem, uint8_t num_of_data_elem,
uint32_t cur_session_id, uint32_t cur_session_id,
int *was_waiting, int *was_waiting,
ngx_http_chunk_type_e chunk_type, AttachmentDataType chunk_type,
ngx_int_t tout_retries ngx_int_t tout_retries
) )
{ {
@@ -245,11 +247,11 @@ ngx_http_cp_send_data_to_service(
/// ///
/// @brief Receieves data from service. /// @brief Receieves data from service.
/// @returns ngx_http_cp_reply_from_service_t /// @returns HttpReplyFromService
/// - #A valid ngx_http_cp_reply_from_service_t pointer if valid. /// - #A valid HttpReplyFromService pointer if valid.
/// - #NULL if failed. /// - #NULL if failed.
/// ///
static ngx_http_cp_reply_from_service_t * static HttpReplyFromService *
ngx_http_cp_receive_data_from_service() ngx_http_cp_receive_data_from_service()
{ {
ngx_int_t res, retry; ngx_int_t res, retry;
@@ -275,7 +277,7 @@ ngx_http_cp_receive_data_from_service()
continue; continue;
} }
return (ngx_http_cp_reply_from_service_t *)reply_data; return (HttpReplyFromService *)reply_data;
} }
return NULL; return NULL;
} }
@@ -286,7 +288,31 @@ ngx_http_cp_receive_data_from_service()
static void static void
free_data_from_service() free_data_from_service()
{ {
if (nano_service_ipc && isDataAvailable(nano_service_ipc)) {
write_dbg(DBG_LEVEL_TRACE, "Freeing data from nano service");
popData(nano_service_ipc); popData(nano_service_ipc);
}
}
///
/// @brief Create a custom JSON response by the provided data
/// @param[in] json_response_data JSON response data.
/// @returns ngx_int_t
/// - #NGX_OK
///
ngx_int_t
handle_custom_json_response(HttpJsonResponseData *json_response_data)
{
ngx_str_t json_body;
write_dbg(DBG_LEVEL_TRACE, "Preparing to set custom JSON response");
json_body.len = json_response_data->body_size;
json_body.data = (u_char *)json_response_data->body;
set_custom_response_json(&json_body, json_response_data->response_code, json_response_data->content_type);
return NGX_OK;
} }
/// ///
@@ -294,8 +320,8 @@ free_data_from_service()
/// @details If web_response_type is set to REDIRECT_WEB_RESPONSE, it will set a redirect response. /// @details If web_response_type is set to REDIRECT_WEB_RESPONSE, it will set a redirect response.
/// @param[in] web_response_data Web response data. /// @param[in] web_response_data Web response data.
/// ///
static void void
handle_custom_web_response(ngx_http_cp_web_response_data_t *web_response_data) handle_custom_web_response(HttpWebResponseData *web_response_data)
{ {
ngx_str_t title; ngx_str_t title;
ngx_str_t body; ngx_str_t body;
@@ -304,7 +330,7 @@ handle_custom_web_response(ngx_http_cp_web_response_data_t *web_response_data)
uuid.len = web_response_data->uuid_size; uuid.len = web_response_data->uuid_size;
if (web_response_data->web_repsonse_type == REDIRECT_WEB_RESPONSE) { if (web_response_data->web_response_type == REDIRECT_WEB_RESPONSE) {
// Settings a redirected web response. // Settings a redirected web response.
write_dbg(DBG_LEVEL_TRACE, "Preparing to set redirect web response"); write_dbg(DBG_LEVEL_TRACE, "Preparing to set redirect web response");
redirect_location.len = web_response_data->response_data.redirect_data.redirect_location_size; redirect_location.len = web_response_data->response_data.redirect_data.redirect_location_size;
@@ -328,7 +354,7 @@ handle_custom_web_response(ngx_http_cp_web_response_data_t *web_response_data)
body.data = (u_char *)web_response_data->response_data.custom_response_data.data + title.len; 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; uuid.data = (u_char *)web_response_data->response_data.custom_response_data.data + title.len + body.len;
set_custom_response(&title, &body, &uuid, web_response_data->response_data.custom_response_data.response_code); set_custom_response_block_page(&title, &body, &uuid, web_response_data->response_data.custom_response_data.response_code);
} }
/// ///
@@ -364,8 +390,8 @@ create_modification_buffer(char **target, uint16_t data_size, char *data, ngx_po
/// - #ngx_http_cp_modification_list pointer on success. /// - #ngx_http_cp_modification_list pointer on success.
/// - #NULL if the creation failed. /// - #NULL if the creation failed.
/// ///
static ngx_http_cp_modification_list * ngx_http_cp_modification_list *
create_modification_node(ngx_http_cp_inject_data_t *modification, ngx_http_request_t *request) create_modification_node(HttpInjectData *modification, ngx_http_request_t *request)
{ {
ngx_int_t res; ngx_int_t res;
ngx_http_cp_modification_list *modification_node = (ngx_http_cp_modification_list *)ngx_pcalloc( ngx_http_cp_modification_list *modification_node = (ngx_http_cp_modification_list *)ngx_pcalloc(
@@ -423,7 +449,7 @@ create_modification_node(ngx_http_cp_inject_data_t *modification, ngx_http_reque
ngx_int_t ngx_int_t
ngx_http_cp_is_reconf_needed() ngx_http_cp_is_reconf_needed()
{ {
ngx_http_cp_reply_from_service_t *reply_p; HttpReplyFromService *reply_p;
ngx_int_t res; ngx_int_t res;
const char *reply_data; const char *reply_data;
uint16_t reply_size; uint16_t reply_size;
@@ -439,7 +465,7 @@ ngx_http_cp_is_reconf_needed()
return NGX_ERROR; return NGX_ERROR;
} }
reply_p = (ngx_http_cp_reply_from_service_t *)reply_data; reply_p = (HttpReplyFromService *)reply_data;
if (reply_p->verdict == TRAFFIC_VERDICT_RECONF) { if (reply_p->verdict == TRAFFIC_VERDICT_RECONF) {
write_dbg(DBG_LEVEL_DEBUG, "Verdict reconf was received from the nano service. Performing reconf on the nginx worker attachment"); write_dbg(DBG_LEVEL_DEBUG, "Verdict reconf was received from the nano service. Performing reconf on the nginx worker attachment");
reset_attachment_config(); reset_attachment_config();
@@ -452,19 +478,19 @@ ngx_http_cp_is_reconf_needed()
ngx_int_t ngx_int_t
ngx_http_cp_reply_receiver( ngx_http_cp_reply_receiver(
ngx_int_t *expected_replies, ngx_int_t *expected_replies,
ngx_http_cp_verdict_e *verdict, ServiceVerdict *verdict,
ngx_int_t *inspect_all_response_headers, ngx_int_t *inspect_all_response_headers,
uint32_t cur_session_id, uint32_t cur_session_id,
ngx_http_request_t *request, ngx_http_request_t *request,
ngx_http_cp_modification_list **modification_list, ngx_http_cp_modification_list **modification_list,
ngx_http_chunk_type_e chunk_type, AttachmentDataType chunk_type,
uint64_t processed_body_size uint64_t processed_body_size
) )
{ {
ngx_http_cp_reply_from_service_t *reply_p; HttpReplyFromService *reply_p;
ngx_http_cp_modification_list *new_modification = NULL; ngx_http_cp_modification_list *new_modification = NULL;
ngx_http_cp_modification_list *current_modification = NULL; ngx_http_cp_modification_list *current_modification = NULL;
ngx_http_cp_inject_data_t *current_inject_data = NULL; HttpInjectData *current_inject_data = NULL;
ngx_int_t res; ngx_int_t res;
ngx_int_t tout_retries = min_retries_for_verdict; ngx_int_t tout_retries = min_retries_for_verdict;
uint8_t modification_count; uint8_t modification_count;
@@ -537,9 +563,9 @@ ngx_http_cp_reply_receiver(
current_modification = current_modification->next; current_modification = current_modification->next;
} }
// Saving injected data. // Saving injected data.
current_inject_data = (ngx_http_cp_inject_data_t *)( current_inject_data = (HttpInjectData *)(
(char *)current_inject_data + (char *)current_inject_data +
sizeof(ngx_http_cp_inject_data_t) + sizeof(HttpInjectData) +
current_inject_data->injection_size current_inject_data->injection_size
); );
} }
@@ -596,7 +622,7 @@ ngx_http_cp_reply_receiver(
break; break;
} }
case TRAFFIC_VERDICT_WAIT: { case TRAFFIC_VERDICT_DELAYED: {
// After a wait verdict, query the nano agent again to get an updated verdict. // After a wait verdict, query the nano agent again to get an updated verdict.
write_dbg(DBG_LEVEL_DEBUG, "Verdict wait received from the nano service"); write_dbg(DBG_LEVEL_DEBUG, "Verdict wait received from the nano service");
updateMetricField(HOLD_VERDICTS_COUNT, 1); updateMetricField(HOLD_VERDICTS_COUNT, 1);
@@ -608,6 +634,23 @@ ngx_http_cp_reply_receiver(
*inspect_all_response_headers = 0; *inspect_all_response_headers = 0;
break; break;
} }
case TRAFFIC_VERDICT_CUSTOM_RESPONSE: {
// Custom Response verdict received from the nano service.
write_dbg(DBG_LEVEL_INFO, "Verdict Custom Response received from the nano service");
handle_custom_json_response(reply_p->modify_data->json_response_data);
*expected_replies = 0;
free_data_from_service();
while (*modification_list) {
current_modification = *modification_list;
*modification_list = (*modification_list)->next;
ngx_pfree(request->pool, current_modification->modification.data);
ngx_pfree(request->pool, current_modification);
}
return NGX_OK;
}
} }
free_data_from_service(); free_data_from_service();
@@ -625,7 +668,7 @@ ngx_http_cp_reply_receiver(
/// @param[in] size Size to be set into the meta_data_sizes 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. /// @param[in] idx Index of the arrays to set the data and size into.
/// ///
static void void
set_fragment_elem(char **meta_data_elems, uint16_t *meta_data_sizes, void *data, uint16_t size, uint idx) 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_elems[idx] = data;
@@ -640,7 +683,7 @@ set_fragment_elem(char **meta_data_elems, uint16_t *meta_data_sizes, void *data,
/// @param[in] data_type Data type identifier to be set. /// @param[in] data_type Data type identifier to be set.
/// @param[in] cur_request_id Request's Id. /// @param[in] cur_request_id Request's Id.
/// ///
static void void
set_fragments_identifiers( set_fragments_identifiers(
char **meta_data_elems, char **meta_data_elems,
uint16_t *meta_data_sizes, uint16_t *meta_data_sizes,
@@ -656,7 +699,7 @@ set_fragments_identifiers(
/// @param[in, out] sockaddr Socker to convert. /// @param[in, out] sockaddr Socker to convert.
/// @param[in, out] ip_addr Output location of the conversion. /// @param[in, out] ip_addr Output location of the conversion.
/// ///
static void void
convert_sock_addr_to_string(const struct sockaddr *sa, char *ip_addr) convert_sock_addr_to_string(const struct sockaddr *sa, char *ip_addr)
{ {
void *ip = NULL; void *ip = NULL;
@@ -834,7 +877,7 @@ ngx_http_cp_meta_data_sender(ngx_http_request_t *request, uint32_t cur_request_i
ngx_int_t ngx_int_t
ngx_http_cp_end_transaction_sender( ngx_http_cp_end_transaction_sender(
ngx_http_chunk_type_e end_transaction_type, AttachmentDataType end_transaction_type,
uint32_t cur_request_id, uint32_t cur_request_id,
ngx_uint_t *num_messages_sent ngx_uint_t *num_messages_sent
) )
@@ -869,7 +912,7 @@ ngx_http_cp_wait_sender(uint32_t cur_request_id, ngx_uint_t *num_messages_sent)
char *fragments[end_transaction_num_fragments]; char *fragments[end_transaction_num_fragments];
uint16_t fragments_sizes[end_transaction_num_fragments]; uint16_t fragments_sizes[end_transaction_num_fragments];
ngx_http_chunk_type_e transaction_type = HOLD_DATA; AttachmentDataType transaction_type = REQUEST_DELAYED_VERDICT;
ngx_int_t res; ngx_int_t res;
set_fragments_identifiers(fragments, fragments_sizes, (uint16_t *)&transaction_type, &cur_request_id); set_fragments_identifiers(fragments, fragments_sizes, (uint16_t *)&transaction_type, &cur_request_id);
@@ -940,7 +983,7 @@ ngx_http_cp_content_length_sender(uint64_t content_length_n, uint32_t cur_req_id
/// @param[in] header Header to add to the fragment array. /// @param[in] header Header to add to the fragment array.
/// @param[in] index Index of the arrays to set the header into. /// @param[in] index Index of the arrays to set the header into.
/// ///
static inline void void
add_header_to_bulk(char **fragments, uint16_t *fragments_sizes, ngx_table_elt_t *header, ngx_uint_t index) add_header_to_bulk(char **fragments, uint16_t *fragments_sizes, ngx_table_elt_t *header, ngx_uint_t index)
{ {
ngx_uint_t pos = index * HEADER_DATA_COUNT; ngx_uint_t pos = index * HEADER_DATA_COUNT;
@@ -1024,7 +1067,7 @@ send_empty_header_list(
ngx_int_t ngx_int_t
ngx_http_cp_header_sender( ngx_http_cp_header_sender(
ngx_list_part_t *headers_list, ngx_list_part_t *headers_list,
ngx_http_chunk_type_e header_type, AttachmentDataType header_type,
uint32_t cur_request_id, uint32_t cur_request_id,
ngx_uint_t *num_messages_sent ngx_uint_t *num_messages_sent
) )
@@ -1111,8 +1154,9 @@ ngx_http_cp_header_sender(
ngx_int_t ngx_int_t
ngx_http_cp_body_sender( ngx_http_cp_body_sender(
ngx_chain_t *input, ngx_chain_t *input,
ngx_http_chunk_type_e body_type, AttachmentDataType body_type,
ngx_http_cp_session_data *session_data, ngx_http_cp_session_data *session_data,
ngx_int_t *part_number,
ngx_int_t *is_last_part, ngx_int_t *is_last_part,
ngx_uint_t *num_messages_sent, ngx_uint_t *num_messages_sent,
ngx_chain_t **next_elem_to_inspect ngx_chain_t **next_elem_to_inspect
@@ -1147,7 +1191,7 @@ ngx_http_cp_body_sender(
set_fragments_identifiers(fragments, fragments_sizes, (uint16_t *)&body_type, &session_data->session_id); set_fragments_identifiers(fragments, fragments_sizes, (uint16_t *)&body_type, &session_data->session_id);
num_parts_sent = 0; num_parts_sent = 0;
part_count = 0; part_count = (body_type == RESPONSE_BODY) ? *part_number : 0;
for (chain_iter = input; chain_iter && chunks_processed < max_chunks_to_process; chain_iter = chain_iter->next) { for (chain_iter = input; chain_iter && chunks_processed < max_chunks_to_process; chain_iter = chain_iter->next) {
// For each NGINX buffer, fragment the buffer and then send the fragments to the nano service. // For each NGINX buffer, fragment the buffer and then send the fragments to the nano service.
@@ -1235,14 +1279,13 @@ ngx_http_cp_metric_data_sender()
write_dbg(DBG_LEVEL_DEBUG, "Sending metric data to service"); write_dbg(DBG_LEVEL_DEBUG, "Sending metric data to service");
fragment_type = METRIC_DATA_FROM_PLUGIN; fragment_type = METRIC_DATA_FROM_PLUGIN;
ngx_http_cp_metric_data_t data_to_send; NanoHttpMetricData data_to_send;
data_to_send.data_type = fragment_type; data_to_send.data_type = fragment_type;
memcpy(data_to_send.data, metric_data, METRIC_TYPES_COUNT * sizeof(data_to_send.data[0])); memcpy(data_to_send.data, metric_data, METRIC_TYPES_COUNT * sizeof(data_to_send.data[0]));
fragments = (char *)&data_to_send; fragments = (char *)&data_to_send;
fragments_sizes = sizeof(ngx_http_cp_metric_data_t); fragments_sizes = sizeof(NanoHttpMetricData);
res = ngx_http_cp_send_data_to_service(&fragments, &fragments_sizes, 1, 0, NULL, fail_open_timeout, min_retries_for_verdict); res = ngx_http_cp_send_data_to_service(&fragments, &fragments_sizes, 1, 0, NULL, fail_open_timeout, min_retries_for_verdict);
reset_metric_data(); reset_metric_data();
return res; return res;
} }

View File

@@ -22,7 +22,7 @@
#include <unistd.h> #include <unistd.h>
#include "shmem_ipc.h" #include "shmem_ipc.h"
#include "nginx_attachment_common.h" #include "nano_attachment_common.h"
#include "ngx_cp_custom_response.h" #include "ngx_cp_custom_response.h"
#include "ngx_cp_hooks.h" #include "ngx_cp_hooks.h"
@@ -57,12 +57,12 @@ extern int comm_socket; ///< Communication socket.
ngx_int_t ngx_int_t
ngx_http_cp_reply_receiver( ngx_http_cp_reply_receiver(
ngx_int_t *expected_replies, ngx_int_t *expected_replies,
ngx_http_cp_verdict_e *verdict, ServiceVerdict *verdict,
ngx_int_t *inspect_all_response_headers, ngx_int_t *inspect_all_response_headers,
uint32_t cur_session_id, uint32_t cur_session_id,
ngx_http_request_t *request, ngx_http_request_t *request,
ngx_http_cp_modification_list **modification_list, ngx_http_cp_modification_list **modification_list,
ngx_http_chunk_type_e chunk_type, AttachmentDataType chunk_type,
uint64_t processed_body_size uint64_t processed_body_size
); );
@@ -97,7 +97,7 @@ ngx_http_cp_meta_data_sender(
/// ///
ngx_int_t ngx_int_t
ngx_http_cp_end_transaction_sender( ngx_http_cp_end_transaction_sender(
ngx_http_chunk_type_e end_transaction_type, AttachmentDataType end_transaction_type,
uint32_t cur_request_id, uint32_t cur_request_id,
ngx_uint_t *num_messages_sent ngx_uint_t *num_messages_sent
); );
@@ -148,7 +148,7 @@ ngx_http_cp_content_length_sender(
ngx_int_t ngx_int_t
ngx_http_cp_header_sender( ngx_http_cp_header_sender(
ngx_list_part_t *headers, ngx_list_part_t *headers,
ngx_http_chunk_type_e header_type, AttachmentDataType header_type,
uint32_t cur_request_id, uint32_t cur_request_id,
ngx_uint_t *num_messages_sent ngx_uint_t *num_messages_sent
); );
@@ -170,16 +170,17 @@ ngx_http_cp_header_sender(
ngx_int_t ngx_int_t
ngx_http_cp_body_sender( ngx_http_cp_body_sender(
ngx_chain_t *input, ngx_chain_t *input,
ngx_http_chunk_type_e body_type, AttachmentDataType body_type,
ngx_http_cp_session_data *session_data, ngx_http_cp_session_data *session_data,
ngx_int_t *part_number,
ngx_int_t *is_last_part, ngx_int_t *is_last_part,
ngx_uint_t *num_messages_sent, ngx_uint_t *num_messages_sent,
ngx_chain_t **next_elem_to_inspect ngx_chain_t **next_elem_to_inspect
); );
/// ///
/// @brief Sends HOLD_DATA request to the nano service. /// @brief Sends REQUEST_DELAYED_VERDICT request to the nano service.
/// @details HOLD_DATA request is a request that asks the nano service to provide with an updated verdict. /// @details REQUEST_DELAYED_VERDICT request is a request that asks the nano service to provide with an updated verdict.
/// @param[in] cur_request_id Request session's Id. /// @param[in] cur_request_id Request session's Id.
/// @param[in, out] num_messages_sent Number of messages sent will be saved onto this parameter. /// @param[in, out] num_messages_sent Number of messages sent will be saved onto this parameter.
/// - #NGX_OK /// - #NGX_OK
@@ -216,4 +217,43 @@ void ngx_http_cp_report_time_metrics(
double res_proccesing_time double res_proccesing_time
); );
///
/// @brief Create a modifications node.
/// @param[in] modification Modification data.
/// @param[in] request NGINX request.
/// @returns modification_node
/// - #ngx_http_cp_modification_list pointer on success.
/// - #NULL if the creation failed.
///
ngx_http_cp_modification_list *
create_modification_node(HttpInjectData *modification, ngx_http_request_t *request);
///
/// @brief Convert socket address to string
/// @param[in] sa Socket address
/// @param[out] ip_addr String buffer to write IP address to
///
void
convert_sock_addr_to_string(const struct sockaddr *sa, char *ip_addr);
///
/// @brief Set fragments identifiers for data transmission
/// @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
///
void
set_fragments_identifiers(
char **meta_data_elems,
uint16_t *meta_data_sizes,
uint16_t *data_type,
uint32_t *cur_request_id);
void set_fragment_elem(char **meta_data_elems, uint16_t *meta_data_sizes, void *data, uint16_t size, uint idx);
void add_header_to_bulk(char **fragments, uint16_t *fragments_sizes, ngx_table_elt_t *header, ngx_uint_t index);
ngx_int_t handle_custom_json_response(HttpJsonResponseData *json_response_data);
void handle_custom_web_response(HttpWebResponseData *web_response_data);
#endif // __NGX_CP_IO_H__ #endif // __NGX_CP_IO_H__

View File

@@ -37,7 +37,7 @@ reset_metric_data()
/// @param[in] value Value to increment the metric type. /// @param[in] value Value to increment the metric type.
/// ///
static void static void
updateCounterMetricField(ngx_http_plugin_metric_type_e metric_type, uint64_t value) updateCounterMetricField(AttachmentMetricType metric_type, uint64_t value)
{ {
metric_data[metric_type] += value; metric_data[metric_type] += value;
} }
@@ -48,7 +48,7 @@ updateCounterMetricField(ngx_http_plugin_metric_type_e metric_type, uint64_t val
/// @param[in] value Value to add to the average metric. /// @param[in] value Value to add to the average metric.
/// ///
static void static void
updateAverageMetricField(ngx_http_plugin_metric_type_e metric_type, uint64_t value) updateAverageMetricField(AttachmentMetricType metric_type, uint64_t value)
{ {
metric_data[metric_type] = metric_data[metric_type] =
(((metric_data[metric_type] * metric_average_data_divisor[metric_type]) + value) / (metric_average_data_divisor[metric_type] + 1)); (((metric_data[metric_type] * metric_average_data_divisor[metric_type]) + value) / (metric_average_data_divisor[metric_type] + 1));
@@ -61,7 +61,7 @@ updateAverageMetricField(ngx_http_plugin_metric_type_e metric_type, uint64_t val
/// @param[in] value Value to set. /// @param[in] value Value to set.
/// ///
static void static void
updateMaxMetricField(ngx_http_plugin_metric_type_e metric_type, uint64_t value) updateMaxMetricField(AttachmentMetricType metric_type, uint64_t value)
{ {
if (metric_data[metric_type] < value) metric_data[metric_type] = value; if (metric_data[metric_type] < value) metric_data[metric_type] = value;
} }
@@ -72,7 +72,7 @@ updateMaxMetricField(ngx_http_plugin_metric_type_e metric_type, uint64_t value)
/// @param[in] value Value to set. /// @param[in] value Value to set.
/// ///
static void static void
updateMinMetricField(ngx_http_plugin_metric_type_e metric_type, uint64_t value) updateMinMetricField(AttachmentMetricType metric_type, uint64_t value)
{ {
if (metric_data[metric_type] == 0) { if (metric_data[metric_type] == 0) {
metric_data[metric_type] = value; metric_data[metric_type] = value;
@@ -82,7 +82,7 @@ updateMinMetricField(ngx_http_plugin_metric_type_e metric_type, uint64_t value)
} }
void void
updateMetricField(ngx_http_plugin_metric_type_e metric_type, uint64_t value) updateMetricField(AttachmentMetricType metric_type, uint64_t value)
{ {
switch (metric_type) { switch (metric_type) {
case CPU_USAGE: case CPU_USAGE:

View File

@@ -16,7 +16,7 @@
#ifndef __NGX_CP_METRIC_H__ #ifndef __NGX_CP_METRIC_H__
#define __NGX_CP_METRIC_H__ #define __NGX_CP_METRIC_H__
#include <nginx_attachment_common.h> #include <nano_attachment_common.h>
#include <ngx_config.h> #include <ngx_config.h>
#include <ngx_core.h> #include <ngx_core.h>
@@ -25,7 +25,7 @@
/// @param[in] metric_type Metric type to update. /// @param[in] metric_type Metric type to update.
/// @param[in] value Value to set. /// @param[in] value Value to set.
/// ///
void updateMetricField(ngx_http_plugin_metric_type_e metric_type, uint64_t value); void updateMetricField(AttachmentMetricType metric_type, uint64_t value);
/// ///
/// @brief Goes over all the metrics and resets them to 0. /// @brief Goes over all the metrics and resets them to 0.

View File

@@ -484,7 +484,7 @@ finalize_static_resource_response(
} }
ngx_int_t ngx_int_t
handle_static_resource_request(uint32_t session_id, ngx_http_cp_verdict_e *verdict, ngx_http_request_t *request) handle_static_resource_request(uint32_t session_id, ServiceVerdict *verdict, ngx_http_request_t *request)
{ {
ngx_str_t null_terminated_uri; ngx_str_t null_terminated_uri;
ngx_str_t static_resource_name; ngx_str_t static_resource_name;

View File

@@ -20,7 +20,7 @@
#include <ngx_core.h> #include <ngx_core.h>
#include <ngx_http.h> #include <ngx_http.h>
#include "nginx_attachment_common.h" #include "nano_attachment_common.h"
#define NOT_A_STATIC_RESOURCE NGX_DECLINED #define NOT_A_STATIC_RESOURCE NGX_DECLINED
@@ -55,7 +55,7 @@ ngx_int_t is_static_resources_table_initialized(void);
/// ///
ngx_int_t handle_static_resource_request( ngx_int_t handle_static_resource_request(
uint32_t session_id, uint32_t session_id,
ngx_http_cp_verdict_e *verdict, ServiceVerdict *verdict,
ngx_http_request_t *request ngx_http_request_t *request
); );

View File

@@ -28,8 +28,13 @@
#include "nginx_attachment_util.h" #include "nginx_attachment_util.h"
#include "ngx_cp_initializer.h" #include "ngx_cp_initializer.h"
#include "nginx_attachment_common.h" #include "nano_attachment_common.h"
#include "ngx_cp_metric.h" #include "ngx_cp_metric.h"
#ifdef NGINX_ASYNC_SUPPORTED
#include "async/ngx_cp_async_core.h"
#endif
extern void disconnect_communication(void);
#define USERCHECK_TITLE_START "<!-- CHECK_POINT_USERCHECK_TITLE_PLACEHOLDER-->" #define USERCHECK_TITLE_START "<!-- CHECK_POINT_USERCHECK_TITLE_PLACEHOLDER-->"
#define USERCHECK_BODY_START "<!-- CHECK_POINT_USERCHECK_BODY_PLACEHOLDER-->" #define USERCHECK_BODY_START "<!-- CHECK_POINT_USERCHECK_BODY_PLACEHOLDER-->"
@@ -73,6 +78,9 @@ static uint32_t cur_session_id = 0; ///< Current session ID.
static uint pid = 0; static uint pid = 0;
static uint is_async_mode_toggled_on_in_last_reconfig = 0;
static uint is_async_mode_toggled_off_in_last_reconfig = 0;
ngx_http_cp_sessions_per_minute_limit sessions_per_minute_limit_info = { ngx_http_cp_sessions_per_minute_limit sessions_per_minute_limit_info = {
.sessions_per_second = {0}, .sessions_per_second = {0},
.last_minute_sessions_sum = 0, .last_minute_sessions_sum = 0,
@@ -87,7 +95,7 @@ ngx_int_t dbg_is_needed = 0; ///< Debug flag.
ngx_int_t num_of_connection_attempts = 0; ///< Maximum number of attempted connections. ngx_int_t num_of_connection_attempts = 0; ///< Maximum number of attempted connections.
ngx_uint_t fail_open_timeout = 50; ///< Fail open timeout in milliseconds. ngx_uint_t fail_open_timeout = 50; ///< Fail open timeout in milliseconds.
ngx_uint_t fail_open_hold_timeout = 150; ///< Fail open wait timeout in milliseconds. ngx_uint_t fail_open_hold_timeout = 150; ///< Fail open wait timeout in milliseconds.
ngx_http_cp_verdict_e sessions_per_minute_limit_verdict = TRAFFIC_VERDICT_ACCEPT; ServiceVerdict sessions_per_minute_limit_verdict = TRAFFIC_VERDICT_ACCEPT;
ngx_uint_t max_sessions_per_minute = 0; ///< Masimum session per minute. ngx_uint_t max_sessions_per_minute = 0; ///< Masimum session per minute.
ngx_uint_t req_max_proccessing_ms_time = 3000; ///< Total Request processing timeout in milliseconds. ngx_uint_t req_max_proccessing_ms_time = 3000; ///< Total Request processing timeout in milliseconds.
ngx_uint_t res_max_proccessing_ms_time = 3000; ///< Total Response processing timeout in milliseconds. ngx_uint_t res_max_proccessing_ms_time = 3000; ///< Total Response processing timeout in milliseconds.
@@ -97,8 +105,8 @@ ngx_uint_t req_body_thread_timeout_msec = 150; ///< Request body processing time
ngx_uint_t res_header_thread_timeout_msec = 100; ///< Response header processing timeout in milliseconds. ngx_uint_t res_header_thread_timeout_msec = 100; ///< Response header processing timeout in milliseconds.
ngx_uint_t res_body_thread_timeout_msec = 150; ///< Response body processing timeout in milliseconds. ngx_uint_t res_body_thread_timeout_msec = 150; ///< Response body processing timeout in milliseconds.
ngx_uint_t waiting_for_verdict_thread_timeout_msec = 150; ///< Wait thread processing timeout in milliseconds. ngx_uint_t waiting_for_verdict_thread_timeout_msec = 150; ///< Wait thread processing timeout in milliseconds.
ngx_http_inspection_mode_e inspection_mode = NON_BLOCKING_THREAD; ///< Default inspection mode. NanoHttpInspectionMode inspection_mode = NON_BLOCKING_THREAD; ///< Default inspection mode.
ngx_uint_t num_of_nginx_ipc_elements = 200; ///< Number of NGINX IPC elements. ngx_uint_t num_of_nginx_ipc_elements = 2048; ///< Number of NGINX IPC elements.
ngx_msec_t keep_alive_interval_msec = DEFAULT_KEEP_ALIVE_INTERVAL_MSEC; 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 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 max_retries_for_verdict = 15; ///< Maximum number of retries for verdict.
@@ -106,6 +114,16 @@ 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 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 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. ngx_uint_t remove_res_server_header = 0; ///< Remove server header flag.
ngx_uint_t paired_affinity_enabled = 0; ///< Paired affinity enabled flag.
ngx_uint_t decompression_pool_size = 262144; ///< Decompression pool size in bytes (256KB for high compression rates).
ngx_uint_t recompression_pool_size = 16384; ///< Recompression pool size in bytes.
ngx_uint_t is_async_mode_enabled = 0; ///< Async mode enabled flag.
ngx_uint_t is_brotli_inspection_enabled = 0; ///< Brotli inspection enabled flag.
// JSON response support
static ngx_str_t json_response_body = {0, NULL};
static ngx_uint_t json_response_code = NGX_HTTP_FORBIDDEN;
static AttachmentContentType json_response_content_type = CONTENT_TYPE_APPLICATION_JSON;
static struct timeval static struct timeval
getCurrTimeFast() getCurrTimeFast()
@@ -521,7 +539,7 @@ get_timeout_val_msec(const int delta_time_in_msec)
} }
void void
set_custom_response(const ngx_str_t *title, const ngx_str_t *body, const ngx_str_t *uuid, ngx_uint_t response_code) set_custom_response_block_page(const ngx_str_t *title, const ngx_str_t *body, const ngx_str_t *uuid, ngx_uint_t response_code)
{ {
write_dbg( write_dbg(
DBG_LEVEL_TRACE, DBG_LEVEL_TRACE,
@@ -539,6 +557,9 @@ set_custom_response(const ngx_str_t *title, const ngx_str_t *body, const ngx_str
web_response_body_size = body->len; web_response_body_size = body->len;
web_response_uuid_size = uuid->len; web_response_uuid_size = uuid->len;
memcpy(web_response_uuid, uuid->data, web_response_uuid_size);
web_response_uuid[web_response_uuid_size] = 0;
if (web_response_title_size == 0 || web_response_body_size == 0) return; if (web_response_title_size == 0 || web_response_body_size == 0) return;
// Copies the provided variables into their respective response variables. // Copies the provided variables into their respective response variables.
memcpy(web_response_title, title->data, web_response_title_size); memcpy(web_response_title, title->data, web_response_title_size);
@@ -546,8 +567,6 @@ set_custom_response(const ngx_str_t *title, const ngx_str_t *body, const ngx_str
if (web_response_uuid_size >= sizeof(web_response_uuid)) { if (web_response_uuid_size >= sizeof(web_response_uuid)) {
web_response_uuid_size = sizeof(web_response_uuid) - 1; web_response_uuid_size = sizeof(web_response_uuid) - 1;
} }
memcpy(web_response_uuid, uuid->data, web_response_uuid_size);
web_response_uuid[web_response_uuid_size] = 0;
} }
void void
@@ -601,7 +620,7 @@ set_response_page_chain_elem(ngx_buf_t **part, ngx_str_t *content, ngx_chain_t *
} }
ngx_int_t ngx_int_t
get_response_page(ngx_http_request_t *request, ngx_chain_t (*out_chain)[7]) get_block_page_response(ngx_http_request_t *request, ngx_chain_t (*out_chain)[7])
{ {
ngx_int_t idx; ngx_int_t idx;
ngx_chain_t *tmp_next; ngx_chain_t *tmp_next;
@@ -651,7 +670,7 @@ get_response_page(ngx_http_request_t *request, ngx_chain_t (*out_chain)[7])
} }
ngx_uint_t ngx_uint_t
get_response_page_length(void) get_response_page_length_web_page(void)
{ {
ngx_uint_t idx; ngx_uint_t idx;
ngx_uint_t total_length = 0; ngx_uint_t total_length = 0;
@@ -675,6 +694,79 @@ get_response_code(void)
return web_triggers_response_code; return web_triggers_response_code;
} }
void
set_custom_response_json(const ngx_str_t *body, ngx_uint_t response_code, AttachmentContentType content_type)
{
write_dbg(
DBG_LEVEL_INFO,
"Setting JSON response: response_code = %d, body size = %d, uuid size = %d",
response_code,
body->len
);
json_response_code = response_code;
json_response_content_type = content_type;
if (json_response_body.data && memory_pool) {
ngx_pfree(memory_pool, json_response_body.data);
json_response_body.data = NULL;
json_response_body.len = 0;
}
if (memory_pool && body->len > 0) {
json_response_body.len = body->len;
json_response_body.data = ngx_pcalloc(memory_pool, body->len + 1);
if (json_response_body.data) {
ngx_memcpy(json_response_body.data, body->data, body->len);
json_response_body.data[body->len] = 0;
}
}
}
ngx_int_t
get_response_page_json(ngx_http_request_t *request, ngx_chain_t (*out_chain)[1])
{
ngx_buf_t *buf = ngx_calloc_buf(request->pool);
if (buf == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to allocate new buffer element for JSON response");
return NGX_ERROR_ERR;
}
if (json_response_body.data == NULL || json_response_body.len == 0) {
write_dbg(DBG_LEVEL_INFO, "JSON response body is empty or not set");
return NGX_ERROR_ERR;
}
buf->pos = json_response_body.data;
buf->last = buf->pos + json_response_body.len;
buf->memory = 1;
buf->last_buf = 1;
buf->last_in_chain = 1;
(*out_chain)[0].buf = buf;
(*out_chain)[0].next = NULL;
return NGX_OK;
}
ngx_uint_t
get_response_page_length_json(void)
{
return json_response_body.len;
}
ngx_uint_t
get_response_code_json(void)
{
return json_response_code;
}
AttachmentContentType
get_response_content_type(void)
{
return json_response_content_type;
}
const char * const char *
get_web_response_uuid(void) get_web_response_uuid(void)
{ {
@@ -718,7 +810,7 @@ get_number_of_digits(int num)
return num_of_digits; return num_of_digits;
} }
ngx_http_cp_verdict_e ServiceVerdict
get_sessions_per_minute_limit_verdict() get_sessions_per_minute_limit_verdict()
{ {
return sessions_per_minute_limit_verdict; return sessions_per_minute_limit_verdict;
@@ -914,6 +1006,31 @@ reset_dbg_ctx()
is_ctx_match = 1; is_ctx_match = 1;
} }
void
reset_async_mode_toggled()
{
is_async_mode_toggled_on_in_last_reconfig = 0;
is_async_mode_toggled_off_in_last_reconfig = 0;
}
ngx_int_t
is_async_toggled_on_in_last_reconfig()
{
return is_async_mode_toggled_on_in_last_reconfig;
}
ngx_int_t
is_async_toggled_off_in_last_reconfig()
{
return is_async_mode_toggled_off_in_last_reconfig;
}
ngx_int_t
is_async_toggled_in_last_reconfig()
{
return is_async_toggled_off_in_last_reconfig() || is_async_toggled_on_in_last_reconfig();
}
ngx_int_t ngx_int_t
init_general_config(const char *conf_path) init_general_config(const char *conf_path)
{ {
@@ -973,9 +1090,30 @@ init_general_config(const char *conf_path)
max_retries_for_verdict = getMaxRetriesForVerdict(); max_retries_for_verdict = getMaxRetriesForVerdict();
body_size_trigger = getReqBodySizeTrigger(); body_size_trigger = getReqBodySizeTrigger();
remove_res_server_header = getRemoveResServerHeader(); remove_res_server_header = getRemoveResServerHeader();
decompression_pool_size = getDecompressionPoolSize();
recompression_pool_size = getRecompressionPoolSize();
is_brotli_inspection_enabled = getIsBrotliInspectionEnabled();
num_of_nginx_ipc_elements = getNumOfNginxIpcElements(); num_of_nginx_ipc_elements = getNumOfNginxIpcElements();
keep_alive_interval_msec = (ngx_msec_t) getKeepAliveIntervalMsec(); keep_alive_interval_msec = (ngx_msec_t) getKeepAliveIntervalMsec();
paired_affinity_enabled = isPairedAffinityEnabled();
#ifdef NGINX_ASYNC_SUPPORTED
ngx_uint_t current_async_mode_enabled = is_async_mode_enabled;
is_async_mode_enabled = isAsyncModeEnabled();
if (is_async_mode_enabled && (is_async_mode_enabled != current_async_mode_enabled)) {
write_dbg(DBG_LEVEL_INFO, "Enabling async mode");
is_async_mode_toggled_on_in_last_reconfig = 1;
is_async_mode_toggled_off_in_last_reconfig = 0;
}
if (!is_async_mode_enabled && (is_async_mode_enabled != current_async_mode_enabled)) {
write_dbg(DBG_LEVEL_INFO, "Disabling async mode");
is_async_mode_toggled_off_in_last_reconfig = 1;
is_async_mode_toggled_on_in_last_reconfig = 0;
}
#endif
set_static_resources_path(getStaticResourcesPath()); set_static_resources_path(getStaticResourcesPath());
is_configuration_updated = NGX_OK; is_configuration_updated = NGX_OK;
@@ -1001,12 +1139,15 @@ init_general_config(const char *conf_path)
"wait thread timeout: %u msec, " "wait thread timeout: %u msec, "
"static resources path: %s, " "static resources path: %s, "
"num of nginx ipc elements: %u, " "num of nginx ipc elements: %u, "
"keep alive interval msec: %u msec" "keep alive interval msec: %u msec, "
"min retries for verdict: %u" "min retries for verdict: %u, "
"max retries for verdict: %u" "max retries for verdict: %u, "
"num retries for hold verdict: %u" "num retries for hold verdict: %u, "
"polling time for hold verdict: %u" "polling time for hold verdict: %u, "
"body size trigger for request: %u", "body size trigger for request: %u, "
"decompression pool size: %u bytes, "
"recompression pool size: %u bytes, "
"async mode: %d",
inspection_mode, inspection_mode,
new_dbg_level, new_dbg_level,
(fail_mode_verdict == NGX_OK ? "fail-open" : "fail-close"), (fail_mode_verdict == NGX_OK ? "fail-open" : "fail-close"),
@@ -1030,7 +1171,10 @@ init_general_config(const char *conf_path)
max_retries_for_verdict, max_retries_for_verdict,
hold_verdict_retries, hold_verdict_retries,
hold_verdict_polling_time, hold_verdict_polling_time,
body_size_trigger body_size_trigger,
decompression_pool_size,
recompression_pool_size,
is_async_mode_enabled
); );
@@ -1224,10 +1368,23 @@ print_buffer_chain(ngx_chain_t *chain, char *msg, int num_bytes, int _dbg_level)
for (ngx_chain_t *chain_elem = chain; chain_elem != NULL; chain_elem = chain_elem->next) { for (ngx_chain_t *chain_elem = chain; chain_elem != NULL; chain_elem = chain_elem->next) {
write_dbg( write_dbg(
DBG_LEVEL_WARNING, DBG_LEVEL_WARNING,
"%s chain elem: size: %d, is last buf: %d", "%s chain elem: size=%d "
"[tmp:%d mem:%d mmap:%d in_file:%d "
"flush:%d sync:%d recycled:%d "
"last_buf:%d last_in_chain:%d last_shadow:%d temp_file:%d]",
msg, msg,
chain_elem->buf->last - chain_elem->buf->pos, (int)(chain_elem->buf->last - chain_elem->buf->pos),
chain_elem->buf->last_buf chain_elem->buf->temporary,
chain_elem->buf->memory,
chain_elem->buf->mmap,
chain_elem->buf->in_file,
chain_elem->buf->flush,
chain_elem->buf->sync,
chain_elem->buf->recycled,
chain_elem->buf->last_buf,
chain_elem->buf->last_in_chain,
chain_elem->buf->last_shadow,
chain_elem->buf->temp_file
); );
print_buffer(chain_elem->buf, num_bytes, _dbg_level); print_buffer(chain_elem->buf, num_bytes, _dbg_level);
} }

View File

@@ -23,7 +23,7 @@
#include <sys/time.h> #include <sys/time.h>
#include <assert.h> #include <assert.h>
#include "nginx_attachment_common.h" #include "nano_attachment_common.h"
#ifndef __FILENAME__ #ifndef __FILENAME__
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
@@ -60,7 +60,7 @@ extern ngx_uint_t req_body_thread_timeout_msec;
extern ngx_uint_t res_header_thread_timeout_msec; extern ngx_uint_t res_header_thread_timeout_msec;
extern ngx_uint_t res_body_thread_timeout_msec; extern ngx_uint_t res_body_thread_timeout_msec;
extern ngx_uint_t waiting_for_verdict_thread_timeout_msec; extern ngx_uint_t waiting_for_verdict_thread_timeout_msec;
extern ngx_http_inspection_mode_e inspection_mode; extern NanoHttpInspectionMode inspection_mode;
extern ngx_uint_t num_of_nginx_ipc_elements; extern ngx_uint_t num_of_nginx_ipc_elements;
extern ngx_uint_t min_retries_for_verdict; extern ngx_uint_t min_retries_for_verdict;
extern ngx_uint_t max_retries_for_verdict; extern ngx_uint_t max_retries_for_verdict;
@@ -68,6 +68,11 @@ extern ngx_uint_t hold_verdict_retries;
extern ngx_uint_t hold_verdict_polling_time; extern ngx_uint_t hold_verdict_polling_time;
extern ngx_uint_t body_size_trigger; extern ngx_uint_t body_size_trigger;
extern ngx_uint_t remove_res_server_header; extern ngx_uint_t remove_res_server_header;
extern ngx_uint_t paired_affinity_enabled;
extern ngx_uint_t decompression_pool_size;
extern ngx_uint_t recompression_pool_size;
extern ngx_uint_t is_async_mode_enabled;
extern ngx_uint_t is_brotli_inspection_enabled;
/// ///
/// @struct ngx_http_cp_list_iterator /// @struct ngx_http_cp_list_iterator
@@ -232,13 +237,20 @@ const char *get_web_response_uuid(void);
ngx_uint_t get_web_response_uuid_size(void); ngx_uint_t get_web_response_uuid_size(void);
/// ///
/// @brief Sets a custom response page by modifying web_response_title/body/uuid variables. /// @brief Sets a custom web page response by modifying web_response_title/body/uuid variables.
/// @param[in] title Sets the web response title. /// @param[in] title Sets the web response title.
/// @param[in] message Sets the response body. /// @param[in] body Sets the response body.
/// @param[in] uuid Sets the uuid of the custom response. /// @param[in] uuid Sets the uuid of the custom response.
/// @param[in, out] response_code Sets the response code of the custom response. /// @param[in] response_code Sets the response code of the custom response.
/// ///
void set_custom_response(const ngx_str_t *title, const ngx_str_t *message, const ngx_str_t *uuid, ngx_uint_t response_code); void set_custom_response_block_page(const ngx_str_t *title, const ngx_str_t *body, const ngx_str_t *uuid, ngx_uint_t response_code);
///
/// @brief Sets a custom JSON response.
/// @param[in] body Sets the JSON response body.
/// @param[in] response_code Sets the response code of the custom response.
///
void set_custom_response_json(const ngx_str_t *body, ngx_uint_t response_code, AttachmentContentType content_type);
/// ///
/// @brief Sets a redirect response by modifying redirect triggers, redirect_location and web_response_uuid. /// @brief Sets a redirect response by modifying redirect triggers, redirect_location and web_response_uuid.
@@ -294,28 +306,52 @@ struct timeval get_timeout_val_usec(const int delta_time_in_usec);
/// ///
struct timeval get_timeout_val_msec(const int delta_time_in_msec); struct timeval get_timeout_val_msec(const int delta_time_in_msec);
///
/// @brief Get the currently set response page.
/// @param[in, out] request NGINX request, used to get the NGINX pool to allocate buffer needed for out_chain.
/// @param[in, out] out_chain NGINX chain that the response page data will be written to.
/// @returns ngx_int_t
/// - #NGX_OK.
/// - #NGX_ERROR_ERR.
///
ngx_int_t get_response_page(ngx_http_request_t *request, ngx_chain_t (*out_chain)[7]);
///
/// @brief Get currently set response page length.
/// @returns ngx_uint_t length of the response page.
///
ngx_uint_t get_response_page_length(void);
/// ///
/// @brief Get currently set response code. /// @brief Get currently set response code.
/// @returns ngx_uint_t web_triggers_response_code variable. /// @returns ngx_uint_t web_triggers_response_code variable.
/// ///
ngx_uint_t get_response_code(void); ngx_uint_t get_response_code(void);
///
/// @brief Get JSON response page.
/// @param[in] request NGINX request.
/// @param[out] out_chain NGINX chain to fill with JSON response data.
/// @returns ngx_int_t NGX_OK if successful, NGX_ERROR_ERR if failed.
///
ngx_int_t get_response_page_json(ngx_http_request_t *request, ngx_chain_t (*out_chain)[1]);
///
/// @brief Get currently set JSON response page length.
/// @returns ngx_uint_t length of the JSON response body.
///
ngx_uint_t get_response_page_length_json(void);
///
/// @brief Get currently set JSON response code.
/// @returns ngx_uint_t JSON response code variable.
///
ngx_uint_t get_response_code_json(void);
///
/// @brief Get currently set JSON content type.
/// @returns AttachmentContentType JSON content type variable.
///
AttachmentContentType get_response_content_type(void);
///
/// @brief Get JSON response page for web page format.
/// @param[in] request NGINX request.
/// @param[out] out_chain NGINX chain to fill with web page response data.
/// @returns ngx_int_t NGX_OK if successful, NGX_ERROR_ERR if failed.
///
ngx_int_t get_block_page_response(ngx_http_request_t *request, ngx_chain_t (*out_chain)[7]);
///
/// @brief Get currently set web page response length.
/// @returns ngx_uint_t length of the web page response.
///
ngx_uint_t get_response_page_length_web_page(void);
/// ///
/// @brief Get currently set static resource path. /// @brief Get currently set static resource path.
/// @returns char * get static_resources_path variable. /// @returns char * get static_resources_path variable.
@@ -343,9 +379,9 @@ unsigned int get_number_of_digits(int num);
/// ///
/// @brief Get sessions per minute limit verdict. /// @brief Get sessions per minute limit verdict.
/// @returns ngx_http_cp_verdict_e sessions_per_minute_limit_verdict variable. /// @returns ServiceVerdict sessions_per_minute_limit_verdict variable.
/// ///
ngx_http_cp_verdict_e get_sessions_per_minute_limit_verdict(void); ServiceVerdict get_sessions_per_minute_limit_verdict(void);
/// ///
/// @brief Get maximum sessions per minute. /// @brief Get maximum sessions per minute.
@@ -460,5 +496,9 @@ void print_buffer(ngx_buf_t *buf, int num_bytes, int _dbg_level);
/// ///
void print_buffer_chain(ngx_chain_t *chain, char *msg, int num_bytes, int _dbg_level); void print_buffer_chain(ngx_chain_t *chain, char *msg, int num_bytes, int _dbg_level);
ngx_int_t is_async_toggled_on_in_last_reconfig();
ngx_int_t is_async_toggled_off_in_last_reconfig();
ngx_int_t is_async_toggled_in_last_reconfig();
void reset_async_mode_toggled();
#endif // __NGX_CP_UTILS_H__ #endif // __NGX_CP_UTILS_H__

View File

@@ -20,10 +20,15 @@
#include <pthread.h> #include <pthread.h>
#include "ngx_cp_hooks.h" #include "ngx_cp_hooks.h"
#ifdef NGINX_ASYNC_SUPPORTED
#include "async/ngx_cp_async_core.h"
#include "async/ngx_cp_async_headers.h"
#include "async/ngx_cp_async_body.h"
#endif
#include "ngx_cp_utils.h" #include "ngx_cp_utils.h"
#include "ngx_cp_initializer.h" #include "ngx_cp_initializer.h"
#include "ngx_http_cp_attachment_module.h" #include "ngx_http_cp_attachment_module.h"
#include "nginx_attachment_common.h" #include "nano_attachment_common.h"
extern ngx_uint_t current_config_version; ///< NGINX configuration version. extern ngx_uint_t current_config_version; ///< NGINX configuration version.
@@ -85,6 +90,15 @@ static ngx_int_t ngx_cp_attachment_init_worker(ngx_cycle_t *cycle);
/// ///
static void ngx_cp_attachment_fini_worker(ngx_cycle_t *cycle); static void ngx_cp_attachment_fini_worker(ngx_cycle_t *cycle);
///
/// @brief Initialize global environment variable cache for async mode.
/// @details Called once during module initialization to cache the CP_ASYNC_MODE environment variable.
///
static void ngx_cp_init_env_async_mode(void);
// Global variable to cache environment variable check
static ngx_int_t g_env_async_mode = 0; ///< Cached environment async mode setting
ngx_http_output_header_filter_pt ngx_http_next_response_header_filter; ///< NGINX response header filter. ngx_http_output_header_filter_pt ngx_http_next_response_header_filter; ///< NGINX response header filter.
ngx_http_request_body_filter_pt ngx_http_next_request_body_filter; ///< NGINX request body filter. ngx_http_request_body_filter_pt ngx_http_next_request_body_filter; ///< NGINX request body filter.
ngx_http_output_body_filter_pt ngx_http_next_response_body_filter; ///< NGINX output body filter. ngx_http_output_body_filter_pt ngx_http_next_response_body_filter; ///< NGINX output body filter.
@@ -123,6 +137,16 @@ static ngx_command_t ngx_cp_attachment_commands[] = {
offsetof(ngx_cp_attachment_conf_t, waf_tag), offsetof(ngx_cp_attachment_conf_t, waf_tag),
NULL NULL
}, },
#ifdef NGINX_ASYNC_SUPPORTED
{
ngx_string("ngx_cp_async_mode"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_cp_attachment_conf_t, async_mode),
NULL
},
#endif
ngx_null_command ngx_null_command
}; };
@@ -213,6 +237,9 @@ ngx_cp_attachment_create_conf(ngx_conf_t *conf)
module_conf->enable = NGX_CONF_UNSET; module_conf->enable = NGX_CONF_UNSET;
module_conf->num_of_workers = 0; module_conf->num_of_workers = 0;
module_conf->current_loc_config_version = current_config_version; module_conf->current_loc_config_version = current_config_version;
#ifdef NGINX_ASYNC_SUPPORTED
module_conf->async_mode = NGX_CONF_UNSET;
#endif
ngx_str_null(&module_conf->waf_tag); ngx_str_null(&module_conf->waf_tag);
write_dbg(DBG_LEVEL_TRACE, "Successfully created attachment module configuration"); write_dbg(DBG_LEVEL_TRACE, "Successfully created attachment module configuration");
return module_conf; return module_conf;
@@ -265,6 +292,37 @@ ngx_cp_set_module_loc_conf(ngx_http_request_t *request, ngx_flag_t new_state)
write_dbg(DBG_LEVEL_INFO, "Configuration set to be %s", conf->enable ? "enabled" : "disabled"); write_dbg(DBG_LEVEL_INFO, "Configuration set to be %s", conf->enable ? "enabled" : "disabled");
} }
ngx_int_t
is_ngx_cp_async_mode_enabled_for_request(ngx_http_request_t *request)
{
#ifndef NGINX_ASYNC_SUPPORTED
// For nginx versions below 1.20, async mode is not supported
(void)request;
is_async_mode_enabled = 0;
return 0;
#else
if (is_async_mode_enabled) {
return 1;
}
if (g_env_async_mode) {
is_async_mode_enabled = 1;
return 1;
}
ngx_cp_attachment_conf_t *conf = ngx_http_get_module_loc_conf(request, ngx_http_cp_attachment_module);
if (conf != NULL && conf->async_mode != NGX_CONF_UNSET) {
if (conf->async_mode) {
is_async_mode_enabled = 1;
return 1;
}
}
is_async_mode_enabled = 0;
return 0;
#endif
}
static char * static char *
ngx_cp_attachment_merge_conf(ngx_conf_t *configure, void *curr, void *next) ngx_cp_attachment_merge_conf(ngx_conf_t *configure, void *curr, void *next)
{ {
@@ -274,6 +332,9 @@ ngx_cp_attachment_merge_conf(ngx_conf_t *configure, void *curr, void *next)
ngx_conf_merge_value(conf->enable, prev->enable, NGX_CONF_UNSET); ngx_conf_merge_value(conf->enable, prev->enable, NGX_CONF_UNSET);
ngx_conf_merge_value(conf->num_of_workers, prev->num_of_workers, ngx_ncpu); ngx_conf_merge_value(conf->num_of_workers, prev->num_of_workers, ngx_ncpu);
#ifdef NGINX_ASYNC_SUPPORTED
ngx_conf_merge_value(conf->async_mode, prev->async_mode, 0);
#endif
ngx_conf_merge_str_value(conf->waf_tag, prev->waf_tag, ""); ngx_conf_merge_str_value(conf->waf_tag, prev->waf_tag, "");
write_dbg(DBG_LEVEL_TRACE, "Successfully set attachment module configuration in nginx configuration chain"); write_dbg(DBG_LEVEL_TRACE, "Successfully set attachment module configuration in nginx configuration chain");
@@ -460,7 +521,20 @@ ngx_cp_attachment_init_worker(ngx_cycle_t *cycle)
timer_interval_msec timer_interval_msec
); );
ngx_cp_init_env_async_mode();
init_attachment_registration_thread(); init_attachment_registration_thread();
#ifdef NGINX_ASYNC_SUPPORTED
if (g_env_async_mode || is_async_mode_enabled) {
if (ngx_cp_async_init() != NGX_OK) {
write_dbg(DBG_LEVEL_WARNING, "Failed to initialize async connection management");
}
} else {
write_dbg(DBG_LEVEL_INFO, "Async mode disabled, skipping async connection management initialization");
}
#else
write_dbg(DBG_LEVEL_INFO, "Nginx version does not support async mode");
#endif
} }
return NGX_OK; return NGX_OK;
} }
@@ -476,6 +550,11 @@ ngx_cp_attachment_fini_worker(ngx_cycle_t *cycle)
reset_attachment_registration(); reset_attachment_registration();
#ifdef NGINX_ASYNC_SUPPORTED
// Cleanup async connection management
ngx_cp_async_cleanup();
#endif
(void)cycle; (void)cycle;
if (is_timer_active) ngx_del_timer(&ngx_keep_alive_event); if (is_timer_active) ngx_del_timer(&ngx_keep_alive_event);
write_dbg(DBG_LEVEL_INFO, "Timer successfully deleted"); write_dbg(DBG_LEVEL_INFO, "Timer successfully deleted");
@@ -488,6 +567,7 @@ ngx_cp_attachment_init(ngx_conf_t *conf)
ngx_http_handler_pt *handler; ngx_http_handler_pt *handler;
ngx_http_handler_pt *size_metrics_handler; ngx_http_handler_pt *size_metrics_handler;
ngx_http_core_main_conf_t *http_core_main_conf; ngx_http_core_main_conf_t *http_core_main_conf;
write_dbg(DBG_LEVEL_TRACE, "Setting the memory pool used in the current context"); write_dbg(DBG_LEVEL_TRACE, "Setting the memory pool used in the current context");
if (conf->pool == NULL) { if (conf->pool == NULL) {
write_dbg( write_dbg(
@@ -510,14 +590,17 @@ ngx_cp_attachment_init(ngx_conf_t *conf)
write_dbg(DBG_LEVEL_WARNING, "Failed to set HTTP request headers' handler"); write_dbg(DBG_LEVEL_WARNING, "Failed to set HTTP request headers' handler");
return NGX_ERROR; return NGX_ERROR;
} }
*handler = ngx_http_cp_req_header_handler;
ngx_http_next_response_header_filter = ngx_http_top_header_filter; // Set handler based on async mode configuration
ngx_http_top_header_filter = ngx_http_cp_res_header_filter; write_dbg(DBG_LEVEL_DEBUG, "Setting unified handlers with dynamic async mode support");
*handler = ngx_http_cp_req_header_handler;
ngx_http_next_request_body_filter = ngx_http_top_request_body_filter; ngx_http_next_request_body_filter = ngx_http_top_request_body_filter;
ngx_http_top_request_body_filter = ngx_http_cp_req_body_filter; ngx_http_top_request_body_filter = ngx_http_cp_req_body_filter;
ngx_http_next_response_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_cp_res_header_filter;
ngx_http_next_response_body_filter = ngx_http_top_body_filter; ngx_http_next_response_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_cp_res_body_filter; ngx_http_top_body_filter = ngx_http_cp_res_body_filter;
@@ -533,3 +616,25 @@ ngx_cp_attachment_init(ngx_conf_t *conf)
return NGX_OK; return NGX_OK;
} }
///
/// @brief Initialize global environment variable cache for async mode.
/// @details Called once during module initialization to cache the CP_ASYNC_MODE environment variable.
///
static void
ngx_cp_init_env_async_mode(void)
{
#ifdef NGINX_ASYNC_SUPPORTED
char *env_async_mode = getenv("CP_ASYNC_MODE");
if (env_async_mode != NULL) {
if (strcmp(env_async_mode, "true") == 0 || strcmp(env_async_mode, "1") == 0) {
g_env_async_mode = 1;
} else {
g_env_async_mode = 0;
}
}
#else
// For nginx versions below 1.20, async mode is not supported
g_env_async_mode = 0;
#endif
}

View File

@@ -32,6 +32,7 @@ typedef struct {
ngx_int_t num_of_workers; ///< Number of workers. ngx_int_t num_of_workers; ///< Number of workers.
ngx_uint_t current_loc_config_version; ///< NGINX configuration version. ngx_uint_t current_loc_config_version; ///< NGINX configuration version.
ngx_str_t waf_tag; ///< WAF tag value for the location block. ngx_str_t waf_tag; ///< WAF tag value for the location block.
ngx_flag_t async_mode; ///< Flags if async mode is enabled (default: true).
} ngx_cp_attachment_conf_t; } ngx_cp_attachment_conf_t;
/// ///
@@ -63,4 +64,11 @@ ngx_uint_t get_num_of_workers(ngx_http_request_t *request);
/// ///
void ngx_cp_set_module_loc_conf(ngx_http_request_t *request, ngx_flag_t new_state); void ngx_cp_set_module_loc_conf(ngx_http_request_t *request, ngx_flag_t new_state);
///
/// @brief Check if async mode is enabled for a specific request.
/// @param[in] request NGINX request.
/// @returns ngx_int_t 1 if async mode is enabled, 0 if disabled.
///
ngx_int_t is_ngx_cp_async_mode_enabled_for_request(ngx_http_request_t *request);
#endif // __NGX_HTTP_CP_ATTACHMENT_MODULE_H__ #endif // __NGX_HTTP_CP_ATTACHMENT_MODULE_H__

View File

@@ -106,7 +106,7 @@ HttpAttachmentConfiguration::save(cereal::JSONOutputArchive &archive) const
"waiting_for_verdict_thread_timeout_msec", "waiting_for_verdict_thread_timeout_msec",
getNumericalValue("waiting_for_verdict_thread_timeout_msec") getNumericalValue("waiting_for_verdict_thread_timeout_msec")
), ),
cereal::make_nvp("nginx_inspection_mode", getNumericalValue("inspection_mode")), cereal::make_nvp("nginx_inspection_mode", getNumericalValue("nginx_inspection_mode")),
cereal::make_nvp("num_of_nginx_ipc_elements", getNumericalValue("num_of_nginx_ipc_elements")), cereal::make_nvp("num_of_nginx_ipc_elements", getNumericalValue("num_of_nginx_ipc_elements")),
cereal::make_nvp("keep_alive_interval_msec", getNumericalValue("keep_alive_interval_msec")), 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("min_retries_for_verdict", getNumericalValue("min_retries_for_verdict")),
@@ -114,7 +114,12 @@ HttpAttachmentConfiguration::save(cereal::JSONOutputArchive &archive) const
cereal::make_nvp("hold_verdict_retries", getNumericalValue("hold_verdict_retries")), 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("hold_verdict_polling_time", getNumericalValue("hold_verdict_polling_time")),
cereal::make_nvp("body_size_trigger", getNumericalValue("body_size_trigger")), cereal::make_nvp("body_size_trigger", getNumericalValue("body_size_trigger")),
cereal::make_nvp("remove_server_header", getNumericalValue("remove_server_header")) cereal::make_nvp("remove_server_header", getNumericalValue("remove_server_header")),
cereal::make_nvp("decompression_pool_size", getNumericalValue("decompression_pool_size")),
cereal::make_nvp("recompression_pool_size", getNumericalValue("recompression_pool_size")),
cereal::make_nvp("is_paired_affinity_enabled", getNumericalValue("is_paired_affinity_enabled")),
cereal::make_nvp("is_async_mode_enabled", getNumericalValue("is_async_mode_enabled")),
cereal::make_nvp("is_brotli_inspection_enabled", getNumericalValue("is_brotli_inspection_enabled"))
); );
} }
@@ -173,6 +178,21 @@ HttpAttachmentConfiguration::load(cereal::JSONInputArchive &archive)
loadNumericalValue(archive, "hold_verdict_polling_time", 1); loadNumericalValue(archive, "hold_verdict_polling_time", 1);
loadNumericalValue(archive, "body_size_trigger", 200000); loadNumericalValue(archive, "body_size_trigger", 200000);
loadNumericalValue(archive, "remove_server_header", 0); loadNumericalValue(archive, "remove_server_header", 0);
loadNumericalValue(archive, "decompression_pool_size", 262144);
loadNumericalValue(archive, "recompression_pool_size", 16384);
loadNumericalValue(archive, "is_paired_affinity_enabled", 0);
loadNumericalValue(archive, "is_brotli_inspection_enabled", 0);
int g_env_async_mode = 1;
char *env_async_mode = getenv("CP_ASYNC_MODE");
if (env_async_mode != NULL) {
if (strcmp(env_async_mode, "true") == 0 || strcmp(env_async_mode, "1") == 0) {
g_env_async_mode = 1;
} else {
g_env_async_mode = 0;
}
}
loadNumericalValue(archive, "is_async_mode_enabled", g_env_async_mode);
} }
bool bool

View File

@@ -22,6 +22,8 @@
#include <strings.h> #include <strings.h>
#include <string.h> #include <string.h>
#include <zlib.h> #include <zlib.h>
#include <brotli/encode.h>
#include <brotli/decode.h>
using namespace std; using namespace std;
@@ -29,6 +31,10 @@ using DebugFunction = void(*)(const char *);
static const int max_debug_level = static_cast<int>(CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_ASSERTION); static const int max_debug_level = static_cast<int>(CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_ASSERTION);
static const int max_retries = 3;
static const size_t default_brotli_buffer_size = 16384;
static const size_t brotli_decompression_probe_size = 64;
static void static void
defaultPrint(const char *debug_message) defaultPrint(const char *debug_message)
{ {
@@ -104,12 +110,23 @@ static const int zlib_no_flush = Z_NO_FLUSH;
struct CompressionStream struct CompressionStream
{ {
CompressionStream() { bzero(&stream, sizeof(z_stream)); } CompressionStream()
:
br_encoder_state(nullptr),
br_decoder_state(nullptr)
{
bzero(&stream, sizeof(z_stream));
}
~CompressionStream() { fini(); } ~CompressionStream() { fini(); }
tuple<basic_string<unsigned char>, bool> tuple<basic_string<unsigned char>, bool>
decompress(const unsigned char *data, uint32_t size) decompress(const unsigned char *data, uint32_t size)
{ {
if (state == TYPE::UNINITIALIZED && size > 0 && isBrotli(data, size)) return decompressBrotli(data, size);
if (state == TYPE::DECOMPRESS_BROTLI) return decompressBrotli(data, size);
initInflate(); initInflate();
if (state != TYPE::DECOMPRESS) throw runtime_error("Could not start decompression"); if (state != TYPE::DECOMPRESS) throw runtime_error("Could not start decompression");
@@ -138,7 +155,7 @@ struct CompressionStream
res.append(work_space.data(), stream.total_out - old_total_out); res.append(work_space.data(), stream.total_out - old_total_out);
} else { } else {
++retries; ++retries;
if (retries > 3) { if (retries > max_retries) {
fini(); fini();
throw runtime_error("No results from inflate more than three times"); throw runtime_error("No results from inflate more than three times");
} }
@@ -156,6 +173,7 @@ struct CompressionStream
basic_string<unsigned char> basic_string<unsigned char>
compress(CompressionType type, const unsigned char *data, uint32_t size, int is_last_chunk) compress(CompressionType type, const unsigned char *data, uint32_t size, int is_last_chunk)
{ {
if (type == CompressionType::BROTLI) return compressBrotli(data, size, is_last_chunk);
initDeflate(type); initDeflate(type);
if (state != TYPE::COMPRESS) throw runtime_error("Could not start compression"); if (state != TYPE::COMPRESS) throw runtime_error("Could not start compression");
@@ -183,7 +201,7 @@ struct CompressionStream
res.append(work_space.data(), stream.total_out - old_total_out); res.append(work_space.data(), stream.total_out - old_total_out);
} else { } else {
++retries; ++retries;
if (retries > 3) { if (retries > max_retries) {
fini(); fini();
throw runtime_error("No results from deflate more than three times"); throw runtime_error("No results from deflate more than three times");
} }
@@ -201,7 +219,7 @@ private:
void void
initInflate() initInflate()
{ {
if (state != TYPE::UNINITIALIZAED) return; if (state != TYPE::UNINITIALIZED) return;
auto init_status = inflateInit2(&stream, default_num_window_bits + 32); auto init_status = inflateInit2(&stream, default_num_window_bits + 32);
if (init_status != zlib_ok_return_value) { if (init_status != zlib_ok_return_value) {
@@ -216,7 +234,7 @@ private:
void void
initDeflate(CompressionType type) initDeflate(CompressionType type)
{ {
if (state != TYPE::UNINITIALIZAED) return; if (state != TYPE::UNINITIALIZED) return;
int num_history_window_bits; int num_history_window_bits;
switch (type) { switch (type) {
@@ -228,6 +246,10 @@ private:
num_history_window_bits = default_num_window_bits; num_history_window_bits = default_num_window_bits;
break; break;
} }
case CompressionType::BROTLI: {
zlibDbgAssertion << "Brotli compression should use compressBrotli()";
return;
}
default: { default: {
zlibDbgAssertion zlibDbgAssertion
<< "Invalid compression type value: " << "Invalid compression type value: "
@@ -253,6 +275,190 @@ private:
state = TYPE::COMPRESS; state = TYPE::COMPRESS;
} }
basic_string<unsigned char>
compressBrotli(const unsigned char *data, uint32_t size, int is_last_chunk)
{
if (state == TYPE::UNINITIALIZED) {
br_encoder_state = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);
if (!br_encoder_state) throw runtime_error("Failed to create Brotli encoder state");
BrotliEncoderSetParameter(br_encoder_state, BROTLI_PARAM_QUALITY, BROTLI_DEFAULT_QUALITY);
BrotliEncoderSetParameter(br_encoder_state, BROTLI_PARAM_LGWIN, BROTLI_DEFAULT_WINDOW);
state = TYPE::COMPRESS_BROTLI;
} else if (state != TYPE::COMPRESS_BROTLI) {
throw runtime_error("Compression stream in inconsistent state for Brotli compression");
}
basic_string<unsigned char> output;
vector<uint8_t> buffer(16384);
int retries = 0;
const uint8_t* next_in = data;
size_t available_in = size;
while (available_in > 0 || is_last_chunk) {
size_t available_out = buffer.size();
uint8_t* next_out = buffer.data();
BrotliEncoderOperation op = is_last_chunk ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS;
auto brotli_success = BrotliEncoderCompressStream(
br_encoder_state,
op,
&available_in,
&next_in,
&available_out,
&next_out,
nullptr
);
if (brotli_success == BROTLI_FALSE) {
fini();
throw runtime_error("Brotli compression error");
}
size_t bytes_written = buffer.size() - available_out;
if (bytes_written > 0) {
output.append(buffer.data(), bytes_written);
retries = 0;
} else {
retries++;
if (retries > max_retries) {
fini();
throw runtime_error("Brotli compression error: Exceeded retry limit.");
}
}
if (BrotliEncoderIsFinished(br_encoder_state)) break;
if (available_in == 0 && !is_last_chunk) break;
}
if (is_last_chunk) fini();
return output;
}
tuple<basic_string<unsigned char>, bool>
decompressBrotli(const unsigned char *data, uint32_t size)
{
if (state != TYPE::DECOMPRESS_BROTLI) {
br_decoder_state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
if (!br_decoder_state) throw runtime_error("Failed to create Brotli decoder state");
BrotliDecoderSetParameter(br_decoder_state, BROTLI_DECODER_PARAM_LARGE_WINDOW, 1u);
state = TYPE::DECOMPRESS_BROTLI;
}
basic_string<unsigned char> output;
const uint8_t* next_in = data;
size_t available_in = size;
size_t buffer_size = max<size_t>(size * 4, default_brotli_buffer_size);
vector<uint8_t> buffer(buffer_size);
// Use a constant ratio for max buffer size relative to input size
const size_t max_buffer_size = 256 * 1024 * 1024; // 256 MB max buffer size
while (true) {
size_t available_out = buffer.size();
uint8_t* next_out = buffer.data();
BrotliDecoderResult result = BrotliDecoderDecompressStream(
br_decoder_state,
&available_in,
&next_in,
&available_out,
&next_out,
nullptr
);
if (result == BROTLI_DECODER_RESULT_ERROR) {
fini();
auto error_msg = string(BrotliDecoderErrorString(BrotliDecoderGetErrorCode(br_decoder_state)));
throw runtime_error("Brotli decompression error: " + error_msg);
}
// Handle any produced output
size_t bytes_produced = buffer.size() - available_out;
if (bytes_produced > 0) {
output.append(buffer.data(), bytes_produced);
}
if (result == BROTLI_DECODER_RESULT_SUCCESS) {
bool is_finished = BrotliDecoderIsFinished(br_decoder_state);
if (is_finished) fini();
return make_tuple(output, is_finished);
}
if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
// Check if we've exceeded the maximum buffer size limit
if (buffer.size() >= max_buffer_size) {
fini();
throw runtime_error("Brotli decompression buffer size limit exceeded - possibly corrupted data");
}
// Resize buffer to accommodate more output
size_t new_size = min(buffer.size() * 2, max_buffer_size);
buffer.resize(new_size);
continue; // Continue with the same input, new buffer
}
// If we reach here, we need more input but have no more to provide
if (available_in == 0) {
// No more input data available, return what we have so far
return make_tuple(output, false);
}
}
return make_tuple(output, false);
}
bool
isBrotli(const unsigned char *data, uint32_t size)
{
if (size < 4) return false;
BrotliDecoderState* test_decoder = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
if (!test_decoder) return false;
const uint8_t* next_in = data;
size_t available_in = min<size_t>(size, brotli_decompression_probe_size);
uint8_t output[brotli_decompression_probe_size];
size_t available_out = sizeof(output);
uint8_t* next_out = output;
BrotliDecoderResult result = BrotliDecoderDecompressStream(
test_decoder,
&available_in,
&next_in,
&available_out,
&next_out,
nullptr
);
bool is_brotli = false;
if (
result != BROTLI_DECODER_RESULT_ERROR &&
(
available_out < sizeof(output) ||
available_in < min<size_t>(size, brotli_decompression_probe_size)
)
) {
is_brotli = true;
}
BrotliDecoderDestroyInstance(test_decoder);
if (is_brotli) {
br_decoder_state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
BrotliDecoderSetParameter(br_decoder_state, BROTLI_DECODER_PARAM_LARGE_WINDOW, 1u);
state = TYPE::DECOMPRESS_BROTLI;
return true;
}
return false;
}
void void
fini() fini()
{ {
@@ -261,11 +467,21 @@ private:
if (state == TYPE::DECOMPRESS) end_stream_res = inflateEnd(&stream); if (state == TYPE::DECOMPRESS) end_stream_res = inflateEnd(&stream);
if (state == TYPE::COMPRESS) end_stream_res = deflateEnd(&stream); if (state == TYPE::COMPRESS) end_stream_res = deflateEnd(&stream);
if (end_stream_res != zlib_ok_return_value) { if (br_encoder_state) {
BrotliEncoderDestroyInstance(br_encoder_state);
br_encoder_state = nullptr;
}
if (br_decoder_state) {
BrotliDecoderDestroyInstance(br_decoder_state);
br_decoder_state = nullptr;
}
if (end_stream_res != zlib_ok_return_value && end_stream_res != Z_DATA_ERROR) {
zlibDbgError << "Failed to clean state: " << getZlibError(end_stream_res); zlibDbgError << "Failed to clean state: " << getZlibError(end_stream_res);
} }
state = TYPE::UNINITIALIZAED; state = TYPE::UNINITIALIZED;
} }
string string
@@ -288,7 +504,16 @@ private:
} }
z_stream stream; z_stream stream;
enum class TYPE { UNINITIALIZAED, COMPRESS, DECOMPRESS } state = TYPE::UNINITIALIZAED; enum class TYPE {
UNINITIALIZED,
COMPRESS,
DECOMPRESS,
COMPRESS_BROTLI,
DECOMPRESS_BROTLI
} state = TYPE::UNINITIALIZED;
BrotliEncoderState* br_encoder_state = nullptr;
BrotliDecoderState* br_decoder_state = nullptr;
}; };
void void

View File

@@ -15,286 +15,10 @@
#ifndef __NGINX_ATTACHMENT_COMMON_H__ #ifndef __NGINX_ATTACHMENT_COMMON_H__
#define __NGINX_ATTACHMENT_COMMON_H__ #define __NGINX_ATTACHMENT_COMMON_H__
#include <stddef.h> // This file has been deprecated. Do not add anything here.
#include <stdint.h> // Any future additions should be added to nano_attachment_common.h
#include <sys/types.h> // For any inquiries please contact Daniel Yashin.
#include <assert.h>
#define MAX_NGINX_UID_LEN 32
#define NUM_OF_NGINX_IPC_ELEMENTS 200
#define DEFAULT_KEEP_ALIVE_INTERVAL_MSEC 300000
#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
extern char shared_verdict_signal_path[];
extern int workers_amount_to_send; extern int workers_amount_to_send;
typedef int64_t ngx_http_cp_inject_pos_t;
#ifdef __cplusplus
typedef enum class ngx_http_modification_type
#else
typedef enum ngx_http_modification_type
#endif
{
APPEND,
INJECT,
REPLACE
} ngx_http_modification_type_e;
#ifdef __cplusplus
typedef enum class ngx_http_chunk_type
#else
typedef enum ngx_http_chunk_type
#endif
{
REQUEST_START,
REQUEST_HEADER,
REQUEST_BODY,
REQUEST_END,
RESPONSE_CODE,
RESPONSE_HEADER,
RESPONSE_BODY,
RESPONSE_END,
CONTENT_LENGTH,
METRIC_DATA_FROM_PLUGIN,
HOLD_DATA,
COUNT
} ngx_http_chunk_type_e;
#ifdef __cplusplus
typedef enum class ngx_http_plugin_metric_type
#else
typedef enum ngx_http_plugin_metric_type
#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_HEADER_THREAD_TIMEOUT,
REQ_BODY_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,
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
} ngx_http_plugin_metric_type_e;
#ifdef __cplusplus
typedef enum class ngx_http_cp_verdict
#else
typedef enum ngx_http_cp_verdict
#endif
{
TRAFFIC_VERDICT_INSPECT,
TRAFFIC_VERDICT_ACCEPT,
TRAFFIC_VERDICT_DROP,
TRAFFIC_VERDICT_INJECT,
TRAFFIC_VERDICT_IRRELEVANT,
TRAFFIC_VERDICT_RECONF,
TRAFFIC_VERDICT_WAIT,
LIMIT_RESPONSE_HEADERS
} ngx_http_cp_verdict_e;
#ifdef __cplusplus
typedef enum class ngx_http_cp_debug_level
#else
typedef enum ngx_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
} ngx_http_cp_debug_level_e;
#ifdef __cplusplus
typedef enum class ngx_http_meta_data
#else
typedef enum ngx_http_meta_data
#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,
WAF_TAG_SIZE,
WAF_TAG_DATA,
META_DATA_COUNT
} ngx_http_meta_data_e;
#ifdef __cplusplus
typedef enum class ngx_http_header_data
#else
typedef enum ngx_http_header_data
#endif
{
HEADER_KEY_SIZE,
HEADER_KEY_DATA,
HEADER_VAL_SIZE,
HEADER_VAL_DATA,
HEADER_DATA_COUNT
} ngx_http_header_data_e;
typedef enum ngx_http_inspection_mode
{
NON_BLOCKING_THREAD,
BLOCKING_THREAD,
NO_THREAD,
INSPECTION_MODE_COUNT
} ngx_http_inspection_mode_e;
#ifdef __cplusplus
typedef enum class ngx_web_response_type
#else
typedef enum ngx_web_response_type
#endif
{
CUSTOM_WEB_RESPONSE,
CUSTOM_WEB_BLOCK_PAGE_RESPONSE,
RESPONSE_CODE_ONLY,
REDIRECT_WEB_RESPONSE,
NO_WEB_RESPONSE
} ngx_web_response_type_e;
typedef struct __attribute__((__packed__)) ngx_http_cp_inject_data {
ngx_http_cp_inject_pos_t injection_pos;
ngx_http_modification_type_e mod_type;
uint16_t injection_size;
uint8_t is_header;
uint8_t orig_buff_index;
char data[0];
} ngx_http_cp_inject_data_t;
typedef struct __attribute__((__packed__)) ngx_http_cp_web_response_data {
uint8_t web_repsonse_type;
uint8_t uuid_size;
union {
struct __attribute__((__packed__)) ngx_http_cp_custom_web_response_data {
uint16_t response_code;
uint8_t title_size;
uint8_t body_size;
char data[0];
} custom_response_data;
struct __attribute__((__packed__)) ngx_http_cp_redirect_data {
uint8_t unused_dummy;
uint8_t add_event_id;
uint16_t redirect_location_size;
char redirect_location[0];
} redirect_data;
} response_data;
} ngx_http_cp_web_response_data_t;
static_assert(
sizeof(((ngx_http_cp_web_response_data_t*)0)->response_data.custom_response_data) ==
sizeof(((ngx_http_cp_web_response_data_t*)0)->response_data.redirect_data),
"custom_response_data must be equal to redirect_data in size"
);
typedef union __attribute__((__packed__)) ngx_http_cp_modify_data {
ngx_http_cp_inject_data_t inject_data[0];
ngx_http_cp_web_response_data_t web_response_data[0];
} ngx_http_cp_modify_data_t;
typedef struct __attribute__((__packed__)) ngx_http_cp_reply_from_service {
uint16_t verdict;
uint32_t session_id;
uint8_t modification_count;
ngx_http_cp_modify_data_t modify_data[0];
} ngx_http_cp_reply_from_service_t;
typedef struct __attribute__((__packed__)) ngx_http_cp_request_data {
uint16_t data_type;
uint32_t session_id;
unsigned char data[0];
} ngx_http_cp_request_data_t;
typedef struct __attribute__((__packed__)) ngx_http_cp_metric_data {
uint16_t data_type;
#ifdef __cplusplus
uint64_t data[static_cast<int>(ngx_http_plugin_metric_type::METRIC_TYPES_COUNT)];
#else
uint64_t data[METRIC_TYPES_COUNT];
#endif
} ngx_http_cp_metric_data_t;
#endif // __NGINX_ATTACHMENT_COMMON_H__ #endif // __NGINX_ATTACHMENT_COMMON_H__

View File

@@ -17,7 +17,7 @@
#include <stdio.h> #include <stdio.h>
#include "nginx_attachment_common.h" #include "nano_attachment_common.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@@ -29,7 +29,7 @@ typedef const char * c_str;
int initAttachmentConfig(c_str conf_file); int initAttachmentConfig(c_str conf_file);
ngx_http_inspection_mode_e getInspectionMode(); NanoHttpInspectionMode getInspectionMode();
unsigned int getNumOfNginxIpcElements(); unsigned int getNumOfNginxIpcElements();
unsigned int getKeepAliveIntervalMsec(); unsigned int getKeepAliveIntervalMsec();
unsigned int getDbgLevel(); unsigned int getDbgLevel();
@@ -61,11 +61,16 @@ unsigned int getMinRetriesForVerdict();
unsigned int getMaxRetriesForVerdict(); unsigned int getMaxRetriesForVerdict();
unsigned int getReqBodySizeTrigger(); unsigned int getReqBodySizeTrigger();
unsigned int getRemoveResServerHeader(); unsigned int getRemoveResServerHeader();
unsigned int getDecompressionPoolSize();
unsigned int getRecompressionPoolSize();
unsigned int getIsBrotliInspectionEnabled();
unsigned int getWaitingForVerdictThreadTimeout(); unsigned int getWaitingForVerdictThreadTimeout();
int isIPAddress(c_str ip_str); int isIPAddress(c_str ip_str);
int isSkipSource(c_str ip_str); int isSkipSource(c_str ip_str);
unsigned int isPairedAffinityEnabled();
unsigned int isAsyncModeEnabled();
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@@ -199,6 +199,11 @@ void
resetIpc(SharedMemoryIPC *ipc, uint16_t num_of_data_segments) resetIpc(SharedMemoryIPC *ipc, uint16_t num_of_data_segments)
{ {
writeDebug(TraceLevel, "Reseting IPC queues\n"); writeDebug(TraceLevel, "Reseting IPC queues\n");
if (!ipc || !ipc->rx_queue || !ipc->tx_queue) {
writeDebug(WarningLevel, "resetIpc called with NULL ipc pointer\n");
return;
}
resetRingQueue(ipc->rx_queue, num_of_data_segments); resetRingQueue(ipc->rx_queue, num_of_data_segments);
resetRingQueue(ipc->tx_queue, num_of_data_segments); resetRingQueue(ipc->tx_queue, num_of_data_segments);
} }
@@ -208,6 +213,11 @@ destroyIpc(SharedMemoryIPC *shmem, int is_owner)
{ {
writeDebug(TraceLevel, "Destroying IPC queues\n"); writeDebug(TraceLevel, "Destroying IPC queues\n");
if (!shmem) {
writeDebug(WarningLevel, "Destroying IPC queues called with NULL shmem pointer\n");
return;
}
if (shmem->rx_queue != NULL) { if (shmem->rx_queue != NULL) {
destroySharedRingQueue(shmem->rx_queue, is_owner, isTowardsOwner(is_owner, 0)); destroySharedRingQueue(shmem->rx_queue, is_owner, isTowardsOwner(is_owner, 0));
shmem->rx_queue = NULL; shmem->rx_queue = NULL;
@@ -225,6 +235,10 @@ dumpIpcMemory(SharedMemoryIPC *ipc)
{ {
writeDebug(WarningLevel, "Ipc memory dump:\n"); writeDebug(WarningLevel, "Ipc memory dump:\n");
writeDebug(WarningLevel, "RX queue:\n"); writeDebug(WarningLevel, "RX queue:\n");
if (!ipc || !ipc->rx_queue) {
writeDebug(WarningLevel, "RX queue is NULL\n");
return;
}
dumpRingQueueShmem(ipc->rx_queue); dumpRingQueueShmem(ipc->rx_queue);
writeDebug(WarningLevel, "TX queue:\n"); writeDebug(WarningLevel, "TX queue:\n");
dumpRingQueueShmem(ipc->tx_queue); dumpRingQueueShmem(ipc->tx_queue);
@@ -234,6 +248,10 @@ int
sendData(SharedMemoryIPC *ipc, const uint16_t data_to_send_size, const char *data_to_send) sendData(SharedMemoryIPC *ipc, const uint16_t data_to_send_size, const char *data_to_send)
{ {
writeDebug(TraceLevel, "Sending data of size %u\n", data_to_send_size); writeDebug(TraceLevel, "Sending data of size %u\n", data_to_send_size);
if (!ipc || !ipc->tx_queue) {
writeDebug(WarningLevel, "sendData called with NULL ipc pointer\n");
return -1;
}
return pushToQueue(ipc->tx_queue, data_to_send, data_to_send_size); return pushToQueue(ipc->tx_queue, data_to_send, data_to_send_size);
} }
@@ -247,12 +265,22 @@ sendChunkedData(
{ {
writeDebug(TraceLevel, "Sending %u chunks of data\n", num_of_data_elem); writeDebug(TraceLevel, "Sending %u chunks of data\n", num_of_data_elem);
if (!ipc) {
writeDebug(WarningLevel, "sendChunkedData called with NULL ipc pointer\n");
return -1;
}
return pushBuffersToQueue(ipc->tx_queue, data_elem_to_send, data_to_send_sizes, num_of_data_elem); return pushBuffersToQueue(ipc->tx_queue, data_elem_to_send, data_to_send_sizes, num_of_data_elem);
} }
int int
receiveData(SharedMemoryIPC *ipc, uint16_t *received_data_size, const char **received_data) receiveData(SharedMemoryIPC *ipc, uint16_t *received_data_size, const char **received_data)
{ {
if (!ipc) {
writeDebug(WarningLevel, "receiveData called with NULL ipc pointer\n");
return -1;
}
int res = peekToQueue(ipc->rx_queue, received_data, received_data_size); int res = peekToQueue(ipc->rx_queue, received_data, received_data_size);
writeDebug(TraceLevel, "Received data from queue. Res: %d, data size: %u\n", res, *received_data_size); writeDebug(TraceLevel, "Received data from queue. Res: %d, data size: %u\n", res, *received_data_size);
return res; return res;
@@ -261,6 +289,10 @@ receiveData(SharedMemoryIPC *ipc, uint16_t *received_data_size, const char **rec
int int
popData(SharedMemoryIPC *ipc) popData(SharedMemoryIPC *ipc)
{ {
if (!ipc) {
writeDebug(WarningLevel, "popData called with NULL ipc pointer\n");
return -1;
}
int res = popFromQueue(ipc->rx_queue); int res = popFromQueue(ipc->rx_queue);
writeDebug(TraceLevel, "Popped data from queue. Res: %d\n", res); writeDebug(TraceLevel, "Popped data from queue. Res: %d\n", res);
return res; return res;
@@ -269,6 +301,10 @@ popData(SharedMemoryIPC *ipc)
int int
isDataAvailable(SharedMemoryIPC *ipc) isDataAvailable(SharedMemoryIPC *ipc)
{ {
if (!ipc) {
writeDebug(WarningLevel, "isDataAvailable called with NULL ipc pointer\n");
return 0;
}
int res = !isQueueEmpty(ipc->rx_queue); int res = !isQueueEmpty(ipc->rx_queue);
writeDebug(TraceLevel, "Checking if there is data pending to be read. Res: %d\n", res); writeDebug(TraceLevel, "Checking if there is data pending to be read. Res: %d\n", res);
return res; return res;
@@ -277,6 +313,11 @@ isDataAvailable(SharedMemoryIPC *ipc)
int int
isCorruptedShmem(SharedMemoryIPC *ipc, int is_owner) isCorruptedShmem(SharedMemoryIPC *ipc, int is_owner)
{ {
if (!ipc) {
writeDebug(WarningLevel, "isCorruptedShmem called with NULL ipc pointer\n");
return 1;
}
if (isCorruptedQueue(ipc->rx_queue, isTowardsOwner(is_owner, 0)) || if (isCorruptedQueue(ipc->rx_queue, isTowardsOwner(is_owner, 0)) ||
isCorruptedQueue(ipc->tx_queue, isTowardsOwner(is_owner, 1)) isCorruptedQueue(ipc->tx_queue, isTowardsOwner(is_owner, 1))
) { ) {