Uploading attachment code

This commit is contained in:
noam
2022-06-29 21:50:10 +03:00
parent feefcbcd6b
commit 7e8e141c53
133 changed files with 40692 additions and 62 deletions

View File

@@ -0,0 +1,548 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_compression.c
#include "ngx_cp_compression.h"
#include "ngx_cp_utils.h"
#include "ngx_cp_metric.h"
static ngx_int_t is_debug_printing_initialized = 0;
ngx_int_t
is_compression_debug_printing_initialized()
{
return is_debug_printing_initialized;
}
///
/// @brief Writes a debug message at a debug level of Trace.
/// @param[in] debug_message message to be written.
///
static void
compression_trace_level_debug_printer(const char *debug_message)
{
write_dbg(DBG_LEVEL_TRACE, debug_message);
}
///
/// @brief Writes a debug message at a debug level of Debug.
/// @param[in] debug_message message to be written.
///
static void
compression_debug_level_debug_printer(const char *debug_message)
{
write_dbg(DBG_LEVEL_DEBUG, debug_message);
}
///
/// @brief Writes a debug message at a debug level of Info.
/// @param[in] debug_message message to be written.
///
static void
compression_info_level_debug_printer(const char *debug_message)
{
write_dbg(DBG_LEVEL_INFO, debug_message);
}
///
/// @brief Writes a debug message at a debug level of Warning.
/// @param[in] debug_message message to be written.
///
static void
compression_warning_level_debug_printer(const char *debug_message)
{
write_dbg(DBG_LEVEL_WARNING, debug_message);
}
///
/// @brief Writes a debug message at a debug level of Error.
/// @param[in] debug_message message to be written.
///
static void
compression_error_level_debug_printer(const char *debug_message)
{
write_dbg(DBG_LEVEL_ERROR, debug_message);
}
///
/// @brief Writes a debug message at a debug level of Assert.
/// @param[in] debug_message message to be written.
///
static void
compression_assert_level_debug_printer(const char *debug_message)
{
write_dbg(DBG_LEVEL_ASSERT, debug_message);
}
void
initialize_compression_debug_printing()
{
setCompressionDebugFunction(COMPRESSION_DBG_LEVEL_TRACE, compression_trace_level_debug_printer);
setCompressionDebugFunction(COMPRESSION_DBG_LEVEL_DEBUG, compression_debug_level_debug_printer);
setCompressionDebugFunction(COMPRESSION_DBG_LEVEL_INFO, compression_info_level_debug_printer);
setCompressionDebugFunction(COMPRESSION_DBG_LEVEL_WARNING, compression_warning_level_debug_printer);
setCompressionDebugFunction(COMPRESSION_DBG_LEVEL_ERROR, compression_error_level_debug_printer);
setCompressionDebugFunction(COMPRESSION_DBG_LEVEL_ASSERTION, compression_assert_level_debug_printer);
is_debug_printing_initialized = 1;
}
///
/// @brief Checks if the compression buffer is valid.
/// @param[in] should_compress Checks if buffer can be used for compression.
/// - #0 - Buffer is used for decompression.
/// - #1 - Buffer is used for compression.
/// @param[in] buffer message to be written.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
static ngx_int_t
is_valid_compression_buffer(const ngx_int_t should_compress, const ngx_buf_t *buffer)
{
uint64_t buffer_size = buffer->last - buffer->pos;
if (buffer_size == 0 && !should_compress) {
write_dbg(DBG_LEVEL_WARNING, "Invalid decompression buffer: has size 0");
return NGX_ERROR;
}
return NGX_OK;
}
///
/// @brief Gets the NGINX string data from NGINX buffer.
/// @param[in, out] buffer_data NGINX string, used as a destination.
/// @param[in] buffer NGINX buffer.
///
static void
get_buffer_data(ngx_str_t *buffer_data, const ngx_buf_t *buffer)
{
if (buffer_data == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Passed a null pointer as destination buffer");
return;
}
buffer_data->len = buffer->last - buffer->pos;
buffer_data->data = buffer->pos;
}
///
/// @brief Sets the buffer from NGINX string to NGINX buffer.
/// @param[in, out] buffer NGINX buffer, used as a destination.
/// @param[in] buffer_data NGINX string.
///
static void
set_buffer_data(ngx_buf_t *buffer, const ngx_str_t *data)
{
buffer->start = data->data;
buffer->pos = buffer->start;
buffer->last = buffer->start + data->len;
buffer->end = buffer->last;
}
///
/// @brief Decompresses or compresses the provided data.
/// @param[in] should_compress Checks if buffer is used for compression or decompression.
/// - #0 - Function will decompression.
/// - #1 - Function will compress.
/// @param[in, out] compression_stream CompressionStream to compress.
/// @param[in] is_last_decompressed_part Flags if the buffer's last part was decompressed.
/// @param[in, out] output NGINX string to be used as output.
/// @param[in] input NGINX string input to be used as input.
/// @param[in] pool NGINX pool.
/// @param[in, out] params Holds NGINX compression parameters.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
static ngx_int_t
compression_data_filter(
const ngx_int_t should_compress,
CompressionStream *compression_stream,
int *is_last_decompressed_part,
ngx_str_t *output,
ngx_str_t *input,
ngx_pool_t *pool,
ngx_cp_http_compression_params *params
)
{
CompressionResult compression_result;
write_dbg(DBG_LEVEL_TRACE, "Performing %s on buffer data", should_compress ? "compression" : "decompression");
if (should_compress && params == NULL) {
write_dbg(DBG_LEVEL_ASSERT, "Passed a pointer to null as compression parameters");
return NGX_ERROR;
}
if (should_compress) {
// Compressing data.
compression_result = compressData(
compression_stream,
params->compression_type,
input->len,
input->data,
params->is_last_part
);
} else {
// Decompressing data.
DecompressionResult decompression_result = decompressData(compression_stream, input->len, input->data);
compression_result.ok = decompression_result.ok;
compression_result.num_output_bytes = decompression_result.num_output_bytes;
compression_result.output = decompression_result.output;
*is_last_decompressed_part = decompression_result.is_last_chunk;
}
if (!compression_result.ok) return NGX_ERROR;
if (compression_result.output == NULL) {
output->len = 0;
output->data = (u_char *)"";
} else {
output->len = compression_result.num_output_bytes;
output->data = ngx_palloc(pool, output->len);
if (output->data == NULL) {
// Failed to allocate a new buffer.
write_dbg(DBG_LEVEL_WARNING, "Failed to allocate a new buffer");
return NGX_ERROR;
}
ngx_memcpy(output->data, compression_result.output, output->len);
free(compression_result.output);
}
write_dbg(DBG_LEVEL_TRACE, "Successfully %s buffer data", should_compress ? "compressed" : "decompressed");
return NGX_OK;
}
///
/// @brief Decompresses or compresses the provided buffer.
/// @param[in] should_compress Checks if buffer is used for compression or decompression.
/// - #0 - Function will decompression.
/// - #1 - Function will compress.
/// @param[in, out] compression_stream CompressionStream to compress.
/// @param[in] is_last_decompressed_part Flags if the buffer's last part was decompressed.
/// @param[in, out] dest NGINX buffer used as destination.
/// @param[in] src NGINX buffer used as source.
/// @param[in] pool NGINX pool.
/// @param[in] params Holds NGINX compression parameters.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
static ngx_int_t
compression_buffer_filter(
const ngx_int_t should_compress,
CompressionStream *compression_stream,
int *is_last_decompressed_part,
ngx_buf_t *dest,
ngx_buf_t *src,
ngx_pool_t *pool,
ngx_cp_http_compression_params *params
)
{
ngx_str_t src_data;
ngx_str_t dest_data;
ngx_int_t compression_result;
write_dbg(DBG_LEVEL_TRACE, "Performing %s on buffer", should_compress ? "compression" : "decompression");
if (is_valid_compression_buffer(should_compress, src) != NGX_OK) {
// Invalid buffer provided.
write_dbg(DBG_LEVEL_WARNING, "Failed to %s: invalid buffer", should_compress ? "compress" : "decompress");
return NGX_ERROR;
}
if (should_compress) {
// Preparing data for compression.
params->is_last_part = src->last_buf;
if (params->is_last_part && src->pos == NULL) {
src->start = (u_char *)"";
src->pos = src->start;
src->last = src->start;
src->end = src->start;
}
}
get_buffer_data(&src_data, src);
// Compresses the data
compression_result = compression_data_filter(
should_compress,
compression_stream,
is_last_decompressed_part,
&dest_data,
&src_data,
pool,
params
);
if (compression_result != NGX_OK) {
// Failed to compress or decompress.
write_dbg(DBG_LEVEL_WARNING, "Failed to %s data", should_compress ? "compress" : "decompress");
return NGX_ERROR;
}
ngx_memcpy(dest, src, sizeof(ngx_buf_t));
set_buffer_data(dest, &dest_data);
write_dbg(DBG_LEVEL_TRACE, "Successfully %s buffer", should_compress ? "compressed" : "decompressed");
return NGX_OK;
}
///
/// @brief Compresses the provided chain.
/// @param[in] should_compress Checks if buffer is used for compression or decompression.
/// - #0 - Function will decompression.
/// - #1 - Function will compress.
/// @param[in, out] compression_stream CompressionStream to compress.
/// @param[in] is_last_decompressed_part Flags if the buffer's last part was decompressed.
/// @param[in, out] body NGINX chain used as destination.
/// @param[in] original_body_contents NGINX chain used as source.
/// @param[in] pool NGINX pool.
/// @param[in] params Holds NGINX cp compression parameters.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
static ngx_int_t
compression_chain_filter(
const ngx_int_t should_compress,
CompressionStream *compression_stream,
int *is_last_decompressed_part,
ngx_chain_t **body,
ngx_chain_t **original_body_contents,
ngx_pool_t *pool,
ngx_cp_http_compression_params *params
)
{
ngx_int_t compression_result;
ngx_buf_t *output_buffer = ngx_calloc_buf(pool);
ngx_chain_t *curr_input_link = NULL;
ngx_chain_t *curr_original_contents_link = original_body_contents == NULL ? NULL : *original_body_contents;
if (body == NULL) {
// Null body parameter has been passed.
write_dbg(
DBG_LEVEL_WARNING,
"Failed to %s chain: passed null pointer as output chain",
should_compress ? "compress" : "decompress"
);
return NGX_ERROR;
}
write_dbg(DBG_LEVEL_TRACE, "Performing %s on chain", should_compress ? "compression" : "decompression");
for (curr_input_link = *body; curr_input_link != NULL; curr_input_link = curr_input_link->next) {
// Decompress or compresses buffer
compression_result = compression_buffer_filter(
should_compress,
compression_stream,
is_last_decompressed_part,
output_buffer,
curr_input_link->buf,
pool,
params
);
if (compression_result != NGX_OK) {
// Failed to decompress or compress.
free_chain(pool, *body);
return NGX_ERROR;
}
if (curr_original_contents_link != NULL) {
curr_original_contents_link->buf = ngx_calloc_buf(pool);
ngx_memcpy(curr_original_contents_link->buf, curr_input_link->buf, sizeof(ngx_buf_t));
if (curr_input_link->next != NULL) {
// 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));
curr_input_link->buf->memory = 1;
}
write_dbg(DBG_LEVEL_TRACE, "Successfully %s chain", should_compress ? "compressed" : "decompressed");
return NGX_OK;
}
///
/// @brief Sets the ngx_cp_http_compression_params and calls compression_chain_filter with compression flag.
/// @param[in, out] compression_stream CompressionStream to compress.
/// @param[in] compression_type Compression type to be used by compressions.
/// @param[in] is_last_part Flags if the buffer's last part was compressed.
/// @param[in, out] body NGINX chain used as destination.
/// @param[in] original_body_contents NGINX chain used as source.
/// @param[in] pool NGINX pool.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
static ngx_int_t
compress_chain(
CompressionStream *compression_stream,
const CompressionType compression_type,
const int is_last_part,
ngx_chain_t **body,
ngx_chain_t **original_body_contents,
ngx_pool_t *pool
)
{
ngx_cp_http_compression_params params;
params.compression_type = compression_type;
params.is_last_part = is_last_part;
return compression_chain_filter(1, compression_stream, NULL, body, original_body_contents, pool, &params);
}
///
/// @brief Sets the ngx_cp_http_compression_params and calls compression_chain_filter with decompression flag.
/// @param[in, out] compression_stream CompressionStream to compress.
/// @param[in] compression_type Compression type to be used by compressions.
/// @param[in] is_last_decompressed_part Flags if the buffer's last part was decompressed.
/// @param[in, out] body NGINX chain used as destination.
/// @param[in] original_body_contents NGINX chain used as source.
/// @param[in] pool NGINX pool.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
static ngx_int_t
decompress_chain(
CompressionStream *decompress_stream,
int *is_last_decompressed_part,
ngx_chain_t **body,
ngx_chain_t **original_body,
ngx_pool_t *pool
)
{
return
compression_chain_filter(
0,
decompress_stream,
is_last_decompressed_part,
body,
original_body,
pool,
NULL
);
}
ngx_int_t
decompress_body(
CompressionStream *decompression_stream,
const ngx_http_chunk_type_e chunk_type,
int *is_last_decompressed_part,
ngx_chain_t **body,
ngx_chain_t **original_body_contents,
ngx_pool_t *pool
)
{
char *body_type = chunk_type == REQUEST_BODY ? "request" : "response";
write_dbg(DBG_LEVEL_TRACE, "Decompressing %s body", body_type);
ngx_int_t decompress_data_res = decompress_chain(
decompression_stream,
is_last_decompressed_part,
body,
original_body_contents,
pool
);
if (decompress_data_res != NGX_OK) {
// Failed to decompress the provided data.
write_dbg(DBG_LEVEL_WARNING, "Failed to decompress %s body", body_type);
updateMetricField(
chunk_type == REQUEST_BODY ? REQ_FAILED_DECOMPRESSION_COUNT : RES_FAILED_DECOMPRESSION_COUNT,
1
);
return NGX_ERROR;
}
write_dbg(DBG_LEVEL_TRACE, "Successfully decompressed %s body", body_type);
updateMetricField(
chunk_type == REQUEST_BODY ? REQ_SUCCESSFUL_DECOMPRESSION_COUNT : RES_SUCCESSFUL_DECOMPRESSION_COUNT,
1
);
return NGX_OK;
}
ngx_int_t
compress_body(
CompressionStream *compression_stream,
const CompressionType compression_type,
const ngx_http_chunk_type_e chunk_type,
const int is_last_part,
ngx_chain_t **body,
ngx_chain_t **original_body_contents,
ngx_pool_t *pool
)
{
ngx_int_t compress_res;
char *body_type;
if (compression_type == NO_COMPRESSION) {
// This function should not be called with a NO_COMPRESSION type.
// This if statement serves a case that somewhere throughout the code the data
// is set to be compressed but the compression type is wrongly set.
write_dbg(DBG_LEVEL_WARNING, "Invalid compression type: NO_COMPRESSION");
return NGX_ERROR;
}
body_type = chunk_type == REQUEST_BODY ? "request" : "response";
write_dbg(
DBG_LEVEL_TRACE,
"Compressing plain-text %s body in the format \"%s\"",
body_type,
compression_type == GZIP ? "gzip" : "zlib"
);
// Checks if the compression was successful.
compress_res = compress_chain(
compression_stream,
compression_type,
is_last_part,
body,
original_body_contents,
pool
);
if (compress_res != NGX_OK) {
// Failed to compress the body.
write_dbg(DBG_LEVEL_WARNING, "Failed to compress %s body", body_type);
updateMetricField(
chunk_type == REQUEST_BODY ? REQ_FAILED_COMPRESSION_COUNT : RES_FAILED_COMPRESSION_COUNT,
1
);
return NGX_ERROR;
}
write_dbg(DBG_LEVEL_TRACE, "Successfully compressed %s body", body_type);
updateMetricField(
chunk_type == REQUEST_BODY ? REQ_SUCCESSFUL_COMPRESSION_COUNT : RES_SUCCESSFUL_COMPRESSION_COUNT,
1
);
return NGX_OK;
}

View File

@@ -0,0 +1,100 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_compression.h
#ifndef __NGX_CP_COMPRESSION_H__
#define __NGX_CP_COMPRESSION_H__
#include <ngx_core.h>
#include "nginx_attachment_common.h"
#include "compression_utils.h"
/// @struct ngx_cp_http_compression_params
/// @brief Holds all the information regarding NGINX compression.
typedef struct {
ngx_int_t is_last_part;
CompressionType compression_type;
} ngx_cp_http_compression_params;
///
/// @brief Returns compression debug printing initialization status.
/// @returns ngx_int_t;
/// - #0 Debug printing is not initialized.
/// - #1 Debug printing is initialized.
///
ngx_int_t is_compression_debug_printing_initialized();
///
/// @brief Initialize compression debug printing.
///
void initialize_compression_debug_printing();
///
/// @brief Decompress the provided body stream.
/// @param[in, out] decompression_stream CompressionStream to decompress.
/// @param[in] chunk_type Body chunk type:
/// - #REQUEST_BODY
/// - #RESPONSE_BODY
/// @param[in, out] is_last_decompressed_part Flags if the buffer's last part was decompressed.
/// @param[in] body NGINX chain, serves as an output.
/// @param[in] original_body_contents NGINX chain, serves as an input to be decompressed.
/// @param[in, out] pool NGINX pool.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
ngx_int_t
decompress_body(
CompressionStream *decompression_stream,
const ngx_http_chunk_type_e chunk_type,
int *is_last_decompressed_part,
ngx_chain_t **body,
ngx_chain_t **original_body_contents,
ngx_pool_t *pool
);
///
/// @brief Compresses the provided body stream.
/// @details Provided by the body, body type (Response/Request) and stream.
/// Compresses the provided body by using the provided compression stream.
/// @param[in, out] decompression_stream CompressionStream to compress.
/// @param[in] compression_type Compression type.
/// - #GZIP
/// - #ZLIB
/// - #NO_COMPRESSION - Serves as a sanity check in case this function is called
/// on a compression type of data that isn't defined and will return NGX_ERROR.
/// @param[in] chunk_type Body chunk type:
/// - #REQUEST_BODY
/// - #RESPONSE_BODY
/// @param[in, out] is_last_part Saves the value if last part was compressed.
/// @param[in] body NGINX chain.
/// @param[in] original_body_contents NGINX chain, serves as an input to be compressed.
/// @param[in, out] pool NGINX pool.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
ngx_int_t
compress_body(
CompressionStream *compression_stream,
const CompressionType compression_type,
const ngx_http_chunk_type_e chunk_type,
const int is_last_part,
ngx_chain_t **body,
ngx_chain_t **original_body_contents,
ngx_pool_t *pool
);
#endif // __NGX_CP_COMPRESSION_H__

View File

@@ -0,0 +1,852 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_custom_response.c
#include "ngx_cp_custom_response.h"
#include "ngx_cp_utils.h"
///
/// @brief Pushes a key and value into a header list.
/// @param[in, out] headers_list Headers list to push the new head into.
/// @param[in] key_data_size Key data size to be pushed.
/// @param[in, out] key_data Key data to be pushed.
/// @param[in] value_data_size Value data size to be pushed.
/// @param[in, out] value_data Value data to be pushed.
/// @return ngx_table_elt_t
/// - #A pointer to the pushed NGINX header element.
/// - #NULL if failed.
///
static ngx_table_elt_t *
push_header_to_list(
ngx_list_t *headers_list,
int16_t key_data_size,
u_char *key_data,
int16_t value_data_size,
u_char *value_data
);
///
/// @brief Allocates a file buffer to the provided file.
/// @param[in, out] memory_pool NGINX pool.
/// @param[in, out] open_file_info NGINX file info - file information.
/// @param[in, out] is_last_buffer Symbolize if the newly allocated buffer is the last buffer.
/// @param[in, out] file_path NGINX string.
/// @param[in, out] log NGINX log.
/// @returns ngx_buf_t
/// - #A valid pointer to NGINX buffer.
/// - #NULL
///
static ngx_buf_t *
allocate_file_buffer(
ngx_pool_t *memory_pool,
ngx_open_file_info_t *open_file_info,
ngx_int_t is_last_buffer,
ngx_str_t *file_path,
ngx_log_t *log
)
{
ngx_buf_t *file_buffer = ngx_calloc_buf(memory_pool);
if (file_buffer == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to allocate file buffer: could not allocate memory for the buffer");
return NULL;
}
file_buffer->file = ngx_pcalloc(memory_pool, sizeof(ngx_file_t));
if (file_buffer->file == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to allocate file buffer's file descriptor");
ngx_pfree(memory_pool, file_buffer);
return NULL;
}
file_buffer->file_pos = 0;
file_buffer->file_last = open_file_info->size;
file_buffer->in_file = file_buffer->file_last ? 1: 0;
file_buffer->last_buf = is_last_buffer;
file_buffer->last_in_chain = 1;
file_buffer->file->fd = open_file_info->fd;
file_buffer->file->name = *file_path;
file_buffer->file->log = log;
file_buffer->file->directio = open_file_info->is_directio;
return file_buffer;
}
///
/// @brief Deletes the provided header list.
/// @details Iterates over the header list and frees all the nodes' memory back to the NGX pool.
/// @param[in, out] headers_list
///
static void
delete_headers_list(ngx_list_t *headers_list)
{
ngx_list_part_t *headers_iter = headers_list->part.next;
ngx_list_part_t *header_to_del;
while (headers_iter) {
header_to_del = headers_iter;
headers_iter = headers_iter->next;
ngx_pfree(headers_list->pool, header_to_del->elts);
header_to_del->elts = NULL;
header_to_del->nelts = 0;
ngx_pfree(headers_list->pool, header_to_del);
}
headers_list->part.nelts = 0;
headers_list->last = &headers_list->part;
headers_list->part.next = NULL;
}
ngx_int_t
ngx_http_cp_response_headers_sender(
ngx_http_request_t *request,
const ngx_uint_t response_code,
const off_t content_length,
const time_t last_modified_time,
const unsigned int allow_ranges,
const unsigned int keepalive
)
{
write_dbg(
DBG_LEVEL_DEBUG,
"Generating response headers: response code: %ui, content length: %O, last modified time: %T",
response_code,
content_length,
last_modified_time
);
// Writes the custom response data onto the response headers.
request->headers_out.status = response_code;
request->headers_out.content_length_n = content_length;
request->headers_out.last_modified_time = last_modified_time;
request->allow_ranges = allow_ranges;
request->keepalive = keepalive;
if (ngx_http_set_content_type(request) != NGX_OK) {
// Failed to get the header's type.
write_dbg(DBG_LEVEL_WARNING, "Failed to set content type header");
return NGX_ERROR;
}
write_dbg(DBG_LEVEL_DEBUG,"Successfully generated response headers, sending response headers");
return ngx_http_send_header(request);
}
ngx_int_t
ngx_http_cp_file_response_sender(
ngx_http_request_t *request,
ngx_str_t *file_path,
ngx_open_file_info_t *open_file_info,
ngx_int_t is_main_request,
ngx_log_t *log,
ngx_pool_t *memory_pool
)
{
ngx_int_t is_last_buffer;
ngx_buf_t *file_buffer;
ngx_int_t send_output_chain_result;
ngx_chain_t *output_chain;
write_dbg(DBG_LEVEL_DEBUG, "Trying to send file: %.*s", file_path->len, file_path->data);
is_last_buffer = is_main_request ? 1: 0;
// Allocates file's buffer and NGINX chain.
file_buffer = allocate_file_buffer(memory_pool, open_file_info, is_last_buffer, file_path, log);
output_chain = ngx_alloc_chain_link(memory_pool);
output_chain->buf = file_buffer;
output_chain->next = NULL;
send_output_chain_result = ngx_http_output_filter(request, output_chain);
ngx_pfree(memory_pool, file_buffer->file);
ngx_pfree(memory_pool, file_buffer);
return send_output_chain_result;
}
///
/// @brief Adds event ID to the provided NGINX request.
/// @param[in, out] request NGINX request.
///
void
ngx_add_event_id_to_header(ngx_http_request_t *request)
{
u_char *uuid = (u_char *)get_web_response_uuid();
ngx_uint_t uuid_size = get_web_response_uuid_size();
static u_char uuid_key[] = { 'x', '_', 'e', 'v', 'e', 'n', 't', '_', 'i', 'd' };
write_dbg(
DBG_LEVEL_WARNING,
"Adding instance ID to header. Incident ID: %s, Incident ID size: %d",
uuid,
uuid_size
);
push_header_to_list(
&(request->headers_out.headers),
sizeof(uuid_key),
uuid_key,
uuid_size,
uuid
);
}
ngx_int_t
ngx_http_cp_finalize_rejected_request(ngx_http_request_t *request)
{
static u_char text_html[] = {'t', 'e', 'x', 't', '/', 'h', 't', 'm', 'l'};
static size_t size_of_text_html = sizeof(text_html);
ngx_int_t res_code, res;
ngx_table_elt_t *location_header;
ngx_chain_t out_chain[7]; // http://lxr.nginx.org/source/src/http/ngx_http_special_response.c#0772
int send_response_custom_body = 1;
write_dbg(DBG_LEVEL_TRACE, "Finalizing rejecting request");
request->keepalive = 0;
res_code = get_response_code();
request->headers_out.status = res_code;
request->headers_out.status_line.len = 0;
if (res_code == 0) {
// Response code was not provided, setting it to NGX_HTTP_CLOSE.
write_dbg(
DBG_LEVEL_WARNING,
"Response code was not provided. Returning default response: %d (NGX_HTTP_CLOSE)",
NGX_HTTP_CLOSE
);
res_code = NGX_HTTP_CLOSE;
request->headers_out.status = res_code;
goto CUSTOM_RES_OUT;
}
if (get_response_code() == NGX_HTTP_TEMPORARY_REDIRECT) {
// Handling redirect web response.
write_dbg(
DBG_LEVEL_DEBUG,
"Sending Redirect web response"
);
static u_char location_key[] = {'L', 'o', 'c', 'a', 't', 'i', 'o', 'n'};
location_header = push_header_to_list(
&(request->headers_out.headers),
sizeof(location_key),
location_key,
get_redirect_location_size(),
get_redirect_location()
);
if (location_header == NULL) {
// Failed to allocate header.
write_dbg(DBG_LEVEL_ERROR, "Failed to allocate header");
res_code = NGX_HTTP_CLOSE;
goto CUSTOM_RES_OUT;
}
if (get_add_event_id()) {
// Add event ID into the header.
ngx_add_event_id_to_header(request);
}
request->keepalive = 1;
goto CUSTOM_RES_OUT;
}
ngx_add_event_id_to_header(request);
if (get_response_page_length() == 0) {
// Page details were not provided.
write_dbg(
DBG_LEVEL_WARNING,
"Web response page details were not provided. Returning %d response code",
get_response_code()
);
send_response_custom_body = 0;
}
// Writes the finalized rejected data into the headers.
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.data = text_html;
request->headers_out.content_length_n = get_response_page_length();
delete_headers_list(&request->headers_out.headers);
write_dbg(DBG_LEVEL_TRACE, "Sending response headers for rejected request");
res = ngx_http_send_header(request);
if (res == NGX_ERROR || res > NGX_OK) {
// Failed to send response headers.
write_dbg(
DBG_LEVEL_DEBUG,
"Failed to send response headers (result: %d). Returning response code: %d",
res,
res_code
);
goto CUSTOM_RES_OUT;
}
write_dbg(
DBG_LEVEL_TRACE,
"Successfully sent response headers for rejected request."
" Generating web response page for rejected request."
);
if (send_response_custom_body) {
// Sending response custom body.
if (get_response_page(request, &out_chain) != NGX_OK) {
// Failed to generate custom response page.
write_dbg(
DBG_LEVEL_DEBUG,
"Failed to generate web response page. Returning response code: %d",
get_response_code()
);
goto CUSTOM_RES_OUT;
}
write_dbg(DBG_LEVEL_TRACE, "Successfully generated web response page for rejected request");
write_dbg(DBG_LEVEL_TRACE, "Sending web response body");
ngx_int_t output_filter_result = ngx_http_output_filter(request, out_chain);
if (output_filter_result != NGX_OK) {
// Failed to send response body.
write_dbg(DBG_LEVEL_WARNING, "Failed to send web response body");
} else {
write_dbg(DBG_LEVEL_TRACE, "Successfully sent web response body");
}
} else {
out_chain[0].buf = ngx_calloc_buf(request->pool);
if (out_chain[0].buf == NULL) {
// Failed to send web response.
write_dbg(DBG_LEVEL_WARNING, "Failed to send web response");
return NGX_ERROR;
}
out_chain[0].buf->last_buf = 1;
out_chain[0].next = NULL;
return ngx_http_output_filter(request, &out_chain[0]);
}
CUSTOM_RES_OUT:
ngx_http_finalize_request(request, res_code);
return res_code;
}
///
/// @brief Frees modification list.
/// @param[in, out] modification_list NGINX modifications.
/// @param[in, out] pool NGINX pool.
///
static void
free_modifications_list(ngx_http_cp_modification_list *modification_list, ngx_pool_t *pool)
{
ngx_http_cp_modification_list *next_modification;
write_dbg(DBG_LEVEL_DEBUG, "Freeing modification list");
while (modification_list) {
next_modification = modification_list->next;
ngx_pfree(pool, modification_list->modification_buffer);
ngx_pfree(pool, modification_list);
modification_list = next_modification;
}
}
///
/// @brief Injects the provided buffer at the provided position into the original_buffer.
/// @param[in, out] original_buffer NGINX string that new data will be injected to.
/// @param[in] injection_pos Injection position on the original buffer.
/// @param[in] injected_buffer_size Injected buffer size.
/// @param[in, out] injection_buffer Injected buffer.
/// @param[in, out] pool NGINX pool.
/// @return ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
static ngx_int_t
inject_buffer(
ngx_str_t *original_buffer,
const size_t injection_pos,
const size_t injected_buffer_size,
u_char *injection_buffer,
ngx_pool_t *pool
)
{
size_t new_buffer_len;
if (injection_pos > original_buffer->len) {
// Injection position is after original buffer's end position.
write_dbg(
DBG_LEVEL_WARNING,
"Injection position is after original buffer's end. Injection position: %u, buffer's size: %u",
injection_pos,
original_buffer->len
);
return NGX_ERROR;
}
// Allocates memory for a new buffer.
new_buffer_len = original_buffer->len + injected_buffer_size;
u_char *new_buffer_value = ngx_palloc(pool, new_buffer_len);
if (new_buffer_value == NULL) {
// Failed to allocate memory for a new buffer.
write_dbg(DBG_LEVEL_WARNING, "Failed to allocate memory for a new buffer, size: %u", new_buffer_len);
return NGX_ERROR;
}
// Copies the injected data onto the original buffer.
ngx_memcpy(new_buffer_value, original_buffer->data, injection_pos);
ngx_memcpy(new_buffer_value + injection_pos, injection_buffer, injected_buffer_size);
if (injection_pos < original_buffer->len) {
ngx_memcpy(
new_buffer_value + injection_pos + injected_buffer_size,
original_buffer->data + injection_pos,
original_buffer->len - injection_pos
);
}
original_buffer->len = new_buffer_len;
ngx_pfree(pool, original_buffer->data);
original_buffer->data = new_buffer_value;
return NGX_OK;
}
///
/// @brief Inject modification into a header's value.
/// @param[in, out] header NGINX string that the modifications will be injected into.
/// @param[in] modification Modifications to inject.
/// @param[in, out] header_pool NGINX pool.
/// @return ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
static ngx_int_t
inject_header_value(
ngx_table_elt_t *header,
ngx_http_cp_modification_list *modification,
ngx_pool_t *header_pool
)
{
if (modification->modification.injection_pos < 0) {
// Injection position is after original buffer's end position.
write_dbg(DBG_LEVEL_ASSERT, "Negative injection position: %d", modification->modification.injection_pos);
}
write_dbg(
DBG_LEVEL_DEBUG,
"Injecting buffer into header's value. "
"Header's index: %ui, original header's data: '%.*s' (size: %u): '%.*s' (size: %u), "
"injection position: %ui, injected buffer: %s (size: %u)",
modification->modification.orig_buff_index,
header->key.len,
header->key.data,
header->key.len,
header->value.len,
header->value.data,
header->value.len,
modification->modification.injection_pos,
modification->modification_buffer,
modification->modification.injection_size
);
// Inject the modification's buffer into the header.
ngx_int_t inject_buffer_result = inject_buffer(
&header->value,
modification->modification.injection_pos,
modification->modification.injection_size,
(u_char *)modification->modification_buffer,
header_pool
);
if (inject_buffer_result != NGX_OK) return NGX_ERROR;
write_dbg(
DBG_LEVEL_DEBUG,
"Successfully injected header value. Header's value after injection: %.*s (size: %u)",
header->value.len,
header->value.data,
header->value.len
);
return NGX_OK;
}
static ngx_table_elt_t *
push_header_to_list(
ngx_list_t *headers_list,
int16_t key_data_size,
u_char *key_data,
int16_t value_data_size,
u_char *value_data
)
{
ngx_table_elt_t *header = ngx_list_push(headers_list);
if (header == NULL) {
// Failed to allocate header.
write_dbg(DBG_LEVEL_WARNING, "Failed to allocate header");
return NULL;
}
header->hash = 1;
header->key.data = key_data;
header->key.len = key_data_size;
header->value.data = value_data;
header->value.len = value_data_size;
return header;
}
///
/// @brief Validate header modification.
/// @details Modifications come in two nodes: The first one handles the key, the other handles the value.
/// This function validates that the second node (which is the next) that holds data exists and if their
/// data is valid.
/// @param[in, out] modification
/// @return ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
static ngx_int_t
validate_append_header_modification(ngx_http_cp_modification_list *modification)
{
write_dbg(DBG_LEVEL_DEBUG, "Validating append header modification data");
if (modification->next == NULL) {
// Modification data value for the provided modification is missing.
write_dbg(DBG_LEVEL_WARNING, "Error: Append header modification is missing modification data for value");
return NGX_ERROR;
}
if (!modification->modification.is_header || !modification->next->modification.is_header) {
// Modification key or value are not of type header and therefor aren't a proper header modification.
write_dbg(
DBG_LEVEL_WARNING,
"Error: Append header modification is missing modification data for %s",
!modification->modification.is_header ? "key" : "value"
);
return NGX_ERROR;
}
write_dbg(DBG_LEVEL_DEBUG, "Append header modification data is valid");
return NGX_OK;
}
///
/// @brief Append modification into the headers list.
/// @param[in, out] headers_list NGINX list.
/// @param[in, out] modification modification to append into the header list.
/// @return ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
static ngx_int_t
append_header(ngx_list_t *headers_list, ngx_http_cp_modification_list *modification)
{
ngx_http_cp_modification_list *key_modification = modification;
ngx_http_cp_modification_list *value_modification = modification->next;
write_dbg(
DBG_LEVEL_DEBUG,
"Appending header: '%s' (size: %ui) : '%s' (size: %ui)",
key_modification->modification_buffer,
key_modification->modification.injection_size,
value_modification->modification_buffer,
value_modification->modification.injection_size
);
// Appending the header.
ngx_table_elt_t *new_header = push_header_to_list(
headers_list,
key_modification->modification.injection_size,
(u_char *)key_modification->modification_buffer,
value_modification->modification.injection_size,
(u_char *)value_modification->modification_buffer
);
if (new_header == NULL) return NGX_ERROR;
write_dbg(
DBG_LEVEL_TRACE,
"Successfully appended header. Key: '%.*s' (size: %u), value: '%.*s' (size: %u)",
new_header->key.len,
new_header->key.data,
new_header->key.len,
new_header->value.len,
new_header->value.data,
new_header->value.len
);
return NGX_OK;
}
///
/// @brief Modifies a header using the modification list.
/// @param[in, out] request NGINX request.
/// @param[in, out] headers headers NGINX list.
/// @param[in, out] headers_iterator NGINX CP header iterator.
/// @param[in, out] modification Modifications list.
/// @param[in] type Modification type.
/// @param[in] is_content_length A flag if the header is content length.
/// @return ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
static ngx_int_t
perform_header_modification(
ngx_http_request_t *request,
ngx_list_t *headers,
ngx_http_cp_list_iterator *headers_iterator,
ngx_http_cp_modification_list *modification,
ngx_http_modification_type_e type,
ngx_flag_t is_content_length
)
{
ngx_table_elt_t *injected_header;
switch (type) {
case APPEND: {
// Appends a modification into the header.
if (append_header(headers, modification) != NGX_OK) {
// Failed to append the modification.
write_dbg(DBG_LEVEL_ERROR, "Failed to append header");
return NGX_ERROR;
}
break;
}
case INJECT: {
// Injects a modification into the header.
injected_header = get_list_element(headers_iterator, modification->modification.orig_buff_index);
if (injected_header == NULL) {
// No header found with the index.
write_dbg(
DBG_LEVEL_ASSERT,
"No header found with index %ui",
modification->modification.orig_buff_index
)
return NGX_ERROR;
}
if (inject_header_value(injected_header, modification, headers->pool) != NGX_OK) {
// Failed to inject a header value.
write_dbg(DBG_LEVEL_ERROR, "Failed to inject header value");
return NGX_ERROR;
}
break;
}
case REPLACE: {
if (is_content_length == 1) {
// Replacing Content-Length.
write_dbg(DBG_LEVEL_DEBUG, "Content-Length will be replaced")
ngx_http_clear_content_length(request);
}
break;
}
default:
// Failed to get a known modificatino type.
write_dbg(DBG_LEVEL_ASSERT, "Unknown modification type: %d", type);
}
return NGX_OK;
}
///
/// @brief Get the next modification from modification list.
/// @param[in, out] modification Modification list to get the next header from.
/// @param[in, out] type Modification type.
/// @return ngx_http_cp_modification_list
/// - #ngx_http_cp_modification_list pointer to the next element.
/// - #NULL if failed to get the next element.
///
static ngx_http_cp_modification_list *
get_next_header_modification(ngx_http_cp_modification_list *modification, ngx_http_modification_type_e type)
{
switch (type) {
case APPEND:
return modification->next->next;
case INJECT:
case REPLACE:
return modification->next;
default:
write_dbg(DBG_LEVEL_ASSERT, "Unknown modification type: %d", type);
}
return NULL;
}
///
/// @brief Free modifications.
/// @param[in, out] modification Modification to free.
/// @param[in, out] type Modification type.
/// @param[in, out] pool NGINX pool.
///
static void
free_header_modification(
ngx_http_cp_modification_list *modification,
ngx_http_modification_type_e type,
ngx_pool_t *pool
)
{
write_dbg(DBG_LEVEL_DEBUG, "Freeing header modification");
switch (type) {
case APPEND: {
ngx_pfree(pool, modification->next->modification_buffer);
ngx_pfree(pool, modification->next);
ngx_pfree(pool, modification->modification_buffer);
ngx_pfree(pool, modification);
break;
}
case INJECT:
case REPLACE: {
ngx_pfree(pool, modification->modification_buffer);
ngx_pfree(pool, modification);
break;
}
default:
write_dbg(DBG_LEVEL_ASSERT, "Unknown modification type: %d", type);
}
}
ngx_int_t
ngx_http_cp_header_modifier(
ngx_list_t *headers,
ngx_http_cp_modification_list *modifications,
ngx_http_request_t *request,
ngx_flag_t is_content_length
)
{
ngx_http_modification_type_e type;
ngx_http_cp_modification_list *next_modification;
ngx_http_cp_list_iterator headers_iterator;
init_list_iterator(headers, &headers_iterator);
while (modifications != NULL) {
// Check if modification is a header.
if (!modifications->modification.is_header) return NGX_OK;
type = modifications->modification.mod_type;
write_dbg(
DBG_LEVEL_DEBUG,
type == APPEND ?
"Appending header" :
type == REPLACE ?
"Changing header's value" :
"Injecting into header's value");
if (type == APPEND && validate_append_header_modification(modifications) != NGX_OK) {
// Modification is not of a valid append type.
free_modifications_list(modifications, request->pool);
return NGX_ERROR;
}
if (perform_header_modification(
request,
headers,
&headers_iterator,
modifications,
type,
is_content_length
) != NGX_OK) {
// Failed to perform modification on header
write_dbg(DBG_LEVEL_WARNING, "Failed to perform modification on header");
free_modifications_list(modifications, request->pool);
return NGX_ERROR;
}
next_modification = get_next_header_modification(modifications, type);
free_header_modification(modifications, type, request->pool);
modifications = next_modification;
}
return NGX_OK;
}
ngx_int_t
ngx_http_cp_body_modifier(
ngx_chain_t *body_chain,
ngx_http_cp_modification_list *curr_modification,
ngx_pool_t *pool
)
{
ngx_http_cp_modification_list *next_modification;
ngx_uint_t cur_body_chunk = 0;
ngx_chain_t *chain_iter;
ngx_chain_t *injected_chain_elem;
ngx_uint_t num_appended_elements;
size_t cur_chunk_size = 0;
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->modification.orig_buff_index != cur_body_chunk) continue;
cur_chunk_size = body_chain->buf->last - body_chain->buf->pos;
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);
continue;
}
write_dbg(
DBG_LEVEL_DEBUG,
"Handling current 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) {
free_modifications_list(curr_modification, pool);
return NGX_ERROR;
}
write_dbg(DBG_LEVEL_DEBUG, "Handling modification of chain element number %d", cur_body_chunk);
// Handling modification of a chain element.
if (curr_modification->modification.injection_pos == 0) {
// Pre appends chain element.
prepend_chain_elem(chain_iter, injected_chain_elem);
chain_iter = chain_iter->next;
num_appended_elements = 0;
} else if (curr_modification->modification.injection_pos == chain_iter->buf->last - chain_iter->buf->pos + 1) {
// Prepend a chain element.
append_chain_elem(chain_iter, injected_chain_elem);
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);
chain_iter = chain_iter->next->next;
num_appended_elements = 2;
}
// Moves to the next modification element and frees the modifier.
next_modification = curr_modification->next;
ngx_pfree(pool, curr_modification);
curr_modification = next_modification;
cur_body_chunk += num_appended_elements;
}
return NGX_OK;
}

View File

@@ -0,0 +1,120 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_custom_response.h
#ifndef __NGX_CP_CUSTOM_RESPONSE_H__
#define __NGX_CP_CUSTOM_RESPONSE_H__
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include "nginx_attachment_common.h"
/// @struct ngx_http_cp_modification_list
/// @brief A node that holds all the information regarding modifications.
typedef struct ngx_http_cp_modification_list {
struct ngx_http_cp_modification_list *next; ///< Next node.
ngx_http_cp_inject_data_t modification; ///< Modification data.
char *modification_buffer; ///< Modification buffer used to store extra needed data.
} ngx_http_cp_modification_list;
///
/// @brief Generates and sends a response headers.
/// @param[in, out] request NGINX request.
/// @param[in] response_code Response code.
/// @param[in] content_length Content length.
/// @param[in] last_modified_time Last modification time.
/// @param[in] allow_ranges Allowed ranges.
/// @param[in] keepalive Keep alive metadata.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
ngx_int_t
ngx_http_cp_response_headers_sender(
ngx_http_request_t *request,
const ngx_uint_t response_code,
const off_t content_length,
const time_t last_modified_time,
const unsigned int allow_ranges,
const unsigned int keepalive
);
///
/// @brief Generates and sends a response file.
/// @param[in, out] request NGINX request.
/// @param[in, out] file_path NGINX string.
/// @param[in, out] open_file_info NGINX file info - file information.
/// @param[in] is_main_request Flags if the file is the main request.
/// @param[in] log NGINX log.
/// @param[in] memory_pool NGINX pool.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
ngx_int_t
ngx_http_cp_file_response_sender(
ngx_http_request_t *request,
ngx_str_t *file_path,
ngx_open_file_info_t *open_file_info,
ngx_int_t is_main_request,
ngx_log_t *log,
ngx_pool_t *memory_pool
);
///
/// @brief Finalizing a rejected request.
/// @param[in, out] request NGINX request.
/// @return ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
ngx_int_t ngx_http_cp_finalize_rejected_request(ngx_http_request_t *request);
///
/// @brief Modifies headers with the provided modifiers.
/// @param[in, out] headers NGINX headers list.
/// @param[in] modifications CP modification list.
/// @param[in, out] request NGINX request.
/// @param[in] is_content_length Flag that signals if the header is of content length.
/// @return ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
ngx_int_t
ngx_http_cp_header_modifier(
ngx_list_t *headers,
ngx_http_cp_modification_list *modifications,
ngx_http_request_t *request,
ngx_flag_t is_content_length
);
///
/// @brief Modifies body chain with the provided modifiers.
/// @param[in, out] body_chain NGINX body chain.
/// @param[in] modifications CP modification list.
/// @param[in, out] modification_pool NGINX pool for modifications.
/// @return ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
ngx_int_t
ngx_http_cp_body_modifier(
ngx_chain_t *body_chain,
ngx_http_cp_modification_list *modifications,
ngx_pool_t *modification_pool
);
#endif // __NGX_CP_CUSTOM_RESPONSE_H__

View File

@@ -0,0 +1,238 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_failing_state.c
#include "ngx_cp_failing_state.h"
#include "ngx_cp_utils.h"
#include "ngx_cp_initializer.h"
#include "ngx_cp_metric.h"
#define FIVE_ERRORS_PER_FIFTEEN_SECONDS 0
///
/// @struct failure_state
/// @brief Holds failure state data.
///
typedef struct failure_state {
ngx_uint_t max_allowed_failed_requests; ///< Maximum allowed failed requests.
ngx_uint_t failing_interval_boundry; ///< Intervals between each failure.
ngx_uint_t transparent_period_sec; ///< Transperancy mode period.
} failure_state;
///
/// @struct ngx_http_cp_periodic_failure
/// @brief Holds NGINX periodic failure data.
///
typedef struct ngx_http_cp_periodic_failure {
ngx_uint_t max_allowed_failed_requests; ///< Maximum allowed failed requests.
ngx_uint_t current_failed_requests; ///< Current failed requests.
ngx_flag_t is_transparent_mode_active; ///< Transparent mode flag.
ngx_uint_t transparent_interval_boundry; ///< Transparent internval boundary.
struct timeval transparent_interval; ///< Transparent time interval.
struct timeval failing_interval; ///< Falling data time interval.
} ngx_http_cp_periodic_failure;
/// Failure state session monitors.
static const failure_state failed_sessions_monitor[] = {{5, 20, 60}, {5, 20, 300}, {5, 20, 600}};
static const ngx_uint_t failed_sessions_monitor_length = 3;
static ngx_http_cp_periodic_failure current_periodic_failure = {
.max_allowed_failed_requests = 0,
.current_failed_requests = 0,
.is_transparent_mode_active = 0,
.transparent_interval_boundry = 0,
.transparent_interval = {0, 0},
.failing_interval = {0, 0}
};
static ngx_uint_t current_fail_state = FIVE_ERRORS_PER_FIFTEEN_SECONDS;
static ngx_flag_t should_update_timeout = 0;
///
/// @brief Resetting current failure state.
///
static void
reset_failure_state()
{
write_dbg(
DBG_LEVEL_DEBUG,
"Resetting failing interval to default. Interval: %u seconds",
failed_sessions_monitor[current_fail_state].failing_interval_boundry
);
current_periodic_failure.max_allowed_failed_requests =
failed_sessions_monitor[current_fail_state].max_allowed_failed_requests;
current_periodic_failure.failing_interval =
get_timeout_val_sec(failed_sessions_monitor[current_fail_state].failing_interval_boundry);
}
ngx_int_t
is_in_transparent_mode()
{
static int transparent_mode_is_active = 0;
if (current_periodic_failure.is_transparent_mode_active && !is_timeout_reached(&current_periodic_failure.transparent_interval)) {
if (!transparent_mode_is_active) {
write_dbg(
DBG_LEVEL_INFO,
"NGINX is in transparent mode. Transparent timeout: %u seconds",
current_periodic_failure.transparent_interval_boundry
);
updateMetricField(TOTAL_TRANSPARENTS_TIME, (uint64_t)current_periodic_failure.transparent_interval_boundry);
}
transparent_mode_is_active = 1;
return 1;
}
transparent_mode_is_active = 0;
current_periodic_failure.is_transparent_mode_active = 0;
current_periodic_failure.current_failed_requests = 0;
reset_failure_state();
return 0;
}
void
reset_transparent_mode()
{
current_fail_state = 0;
should_update_timeout = 0;
memset(&current_periodic_failure, 0, sizeof(current_periodic_failure));
current_periodic_failure.max_allowed_failed_requests = failed_sessions_monitor[current_fail_state].max_allowed_failed_requests;
current_periodic_failure.current_failed_requests = 0;
current_periodic_failure.is_transparent_mode_active = 0;
current_periodic_failure.transparent_interval_boundry = failed_sessions_monitor[current_fail_state].transparent_period_sec;
current_periodic_failure.failing_interval = get_timeout_val_sec(failed_sessions_monitor[current_fail_state].failing_interval_boundry);
}
///
/// @brief Activate transparent mode.
///
static void
activate_transparent_mode()
{
write_dbg(
DBG_LEVEL_WARNING,
"Activating transparency mode. Transparency period: %u seconds",
failed_sessions_monitor[current_fail_state].transparent_period_sec
);
current_periodic_failure.is_transparent_mode_active = 1;
current_periodic_failure.transparent_interval = get_timeout_val_sec(failed_sessions_monitor[current_fail_state].transparent_period_sec);
current_periodic_failure.transparent_interval_boundry = failed_sessions_monitor[current_fail_state].transparent_period_sec;
}
///
/// @brief Reports the provided session's metric.
/// @param[in, out] state Session's data to report the time metric of.
///
void
ngx_http_cp_report_time_metrics(ngx_http_cp_session_data *state)
{
struct timespec session_end_time;
clock_gettime(CLOCK_REALTIME, &session_end_time);
double begin_usec = (state->session_start_time.tv_sec * 1000000) + (state->session_start_time.tv_nsec / 1000);
double end_usec = (session_end_time.tv_sec * 1000000) + (session_end_time.tv_nsec / 1000);
double overall_process_time = end_usec - begin_usec;
updateMetricField(AVERAGE_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT, overall_process_time);
updateMetricField(MAX_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT, overall_process_time);
updateMetricField(MIN_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT, overall_process_time);
updateMetricField(AVERAGE_REQ_PPROCESSING_TIME_UNTIL_VERDICT, state->req_proccesing_time);
updateMetricField(MAX_REQ_PPROCESSING_TIME_UNTIL_VERDICT, state->req_proccesing_time);
updateMetricField(MIN_REQ_PPROCESSING_TIME_UNTIL_VERDICT, state->req_proccesing_time);
updateMetricField(AVERAGE_RES_PPROCESSING_TIME_UNTIL_VERDICT, state->res_proccesing_time);
updateMetricField(MAX_RES_PPROCESSING_TIME_UNTIL_VERDICT, state->res_proccesing_time);
updateMetricField(MIN_RES_PPROCESSING_TIME_UNTIL_VERDICT, state->res_proccesing_time);
}
void
handle_inspection_failure(int weight, ngx_int_t fail_mode_verdict, ngx_http_cp_session_data *state)
{
if (state->verdict != TRAFFIC_VERDICT_INSPECT) {
// Skipping already inspected data.
write_dbg(DBG_LEVEL_TRACE, "Skipping already inspected data");
return;
}
ngx_http_cp_report_time_metrics(state);
if (fail_mode_verdict == NGX_OK) {
// Increase open fail counter.
updateMetricField(INSPECTION_OPEN_FAILURES_COUNT, 1);
state->verdict = TRAFFIC_VERDICT_ACCEPT;
} else {
// Increase close fail counter.
updateMetricField(INSPECTION_CLOSE_FAILURES_COUNT, 1);
state->verdict = TRAFFIC_VERDICT_DROP;
}
current_periodic_failure.current_failed_requests += weight;
if (is_timeout_reached(&current_periodic_failure.failing_interval)) {
// Enough time had passed without errors. Limits can be reset
reset_failure_state();
return;
}
if (current_periodic_failure.current_failed_requests <= current_periodic_failure.max_allowed_failed_requests) {
write_dbg(
DBG_LEVEL_TRACE,
"Failure count did not reach maximum allowed limit. Current failure count: %u, Max failure limit: %u",
current_periodic_failure.current_failed_requests,
current_periodic_failure.max_allowed_failed_requests
);
return;
}
activate_transparent_mode();
if (current_fail_state < failed_sessions_monitor_length) {
// Setting new transparent interval.
current_fail_state++;
write_dbg(
DBG_LEVEL_DEBUG,
"Setting new transparent interval. New interval: %u seconds",
failed_sessions_monitor[current_fail_state].transparent_period_sec
);
} else {
// Reached impossible fail state, setting highest level of state.
write_dbg(
DBG_LEVEL_WARNING,
"Reached impossible fail state - Setting highest level instead. Current fail state: %u, New fail state: %u",
current_fail_state,
failed_sessions_monitor_length - 1
);
current_fail_state = failed_sessions_monitor_length - 1;
}
}
void
handle_inspection_success(ngx_http_cp_session_data *state)
{
updateMetricField(INSPECTION_SUCCESSES_COUNT, 1);
ngx_http_cp_report_time_metrics(state);
if (!is_timeout_reached(&current_periodic_failure.failing_interval)) return;
if (current_periodic_failure.current_failed_requests != 0 || should_update_timeout == 1) return;
current_fail_state = 0;
}

View File

@@ -0,0 +1,59 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_failing_state.h
#ifndef __NGX_CP_FAILING_STATE_H__
#define __NGX_CP_FAILING_STATE_H__
#include <ngx_config.h>
#include <ngx_core.h>
#include "nginx_attachment_common.h"
#include "ngx_cp_hooks.h"
///
/// @brief Returns if transparent mode is activated.
/// @returns ngx_int_t
/// - #0 if transparent mode is off.
/// - #1 if transparent mode is on.
///
ngx_int_t is_in_transparent_mode(void);
///
/// @brief Handles inspection failure.
/// @details Updates metric fields with the provided data.
/// Metric fields included such as:
/// - #INSPECTION_OPEN_FAILURES_COUNT
/// - #INSPECTION_CLOSE_FAILURES_COUNT
/// @param[in, out] weight Failure's weight.
/// @param[in, out] fail_mode_verdict Fail mode verdict.
/// @param[in, out] state NGINX session data.
///
void handle_inspection_failure(int weight, ngx_int_t fail_mode_verdict, ngx_http_cp_session_data *state);
///
/// @brief Handles inspection success.
/// @details The function updates "INSPECTION_SUCCESSES_COUNT" metric.
/// Furthermore the function updates time verdicts metrics.
/// @param[in, out] state NGINX session data.
///
void handle_inspection_success(ngx_http_cp_session_data *state);
///
/// @brief Resets transparent mode.
/// @details Reset transparent mode to 0, and all the related parameters.
///
void reset_transparent_mode(void);
#endif // __NGX_CP_FAILING_STATE_H__

View File

@@ -0,0 +1,453 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_hook_threads.c
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_files.h>
#include <ngx_string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include "nginx_attachment_util.h"
#include "shmem_ipc.h"
#include "compression_utils.h"
#include "nginx_attachment_common.h"
#include "ngx_cp_io.h"
#include "ngx_cp_utils.h"
#include "ngx_cp_initializer.h"
#include "ngx_http_cp_attachment_module.h"
#include "ngx_cp_static_content.h"
#include "ngx_cp_compression.h"
#include "ngx_cp_http_parser.h"
#include "ngx_cp_hook_threads.h"
#include "ngx_cp_failing_state.h"
#include "ngx_cp_metric.h"
#include "ngx_cp_thread.h"
#include "ngx_cp_hooks.h"
///
/// @brief THREAD_CTX_RETURN, sets this session's context to _X value and set the context
/// to return without going to the next filter.
///
#define THREAD_CTX_RETURN(_X) do { ctx->res = (_X); ctx->should_return = 1; return NULL; } while (0);
///
/// @brief THREAD_CTX_RETURN_NEXT_FILTER, sets this session's context to _X value and set the context
/// to be scanned by the next filter.
///
#define THREAD_CTX_RETURN_NEXT_FILTER() do { ctx->should_return_next_filter = 1; return NULL; } while (0);
static ngx_int_t already_registered = 0; ///< Registration status with the nano service.
static const ngx_int_t inspection_irrelevant = INSPECTION_IRRELEVANT;
extern struct timeval metric_timeout; ///< Holds per-session metric timeout.
void
set_already_registered(ngx_int_t value)
{
already_registered = value;
}
ngx_int_t
get_already_registered()
{
return already_registered;
}
void
init_thread_ctx(
struct ngx_http_cp_event_thread_ctx_t *ctx,
ngx_http_request_t *request,
ngx_http_cp_session_data *session_data_p,
ngx_chain_t *chain)
{
ctx->request = request;
ctx->session_data_p = session_data_p;
ctx->res = NGX_OK;
ctx->should_return = 0;
ctx->should_return_next_filter = 0;
ctx->chain = chain;
ctx->modifications = NULL;
}
void *
ngx_http_cp_registration_thread(void *_ctx)
{
struct ngx_http_cp_event_thread_ctx_t *ctx = (struct ngx_http_cp_event_thread_ctx_t*)_ctx;
ngx_int_t res = ngx_cp_attachment_init_process();
if (res == NGX_ABORT && already_registered) {
already_registered = 0;
disconnect_communication();
reset_transparent_mode();
}
if (res != NGX_OK) {
// failed to register to the attachment service.
if (already_registered) handle_inspection_failure(registration_failure_weight, fail_mode_verdict, ctx->session_data_p);
write_dbg(DBG_LEVEL_DEBUG, "Communication with nano service is not ready yet");
THREAD_CTX_RETURN(NGX_OK);
}
return NULL;
}
///
/// @brief Sends end request header to the attachment's service.
/// @details Communicates with the attachment service by sending end request header
/// to the attachment's service and returns verdict.
/// @param[in, out] session_data_p If the function returns NGX_OK, session data will be modified.
/// @param[in, out] request NGINX Request.
/// @param[in] modifications A list of this session's data modifications.
/// @return ngx_int_t of the following values:
/// - #NGX_OK
/// - #NGX_ERROR
///
static ngx_int_t
end_req_header_handler(
ngx_http_cp_session_data *session_data_p,
ngx_http_request_t *request,
ngx_http_cp_modification_list **modifications)
{
ngx_uint_t num_messages_sent = 0;
if (!does_contain_body(&(request->headers_in))) {
if (ngx_http_cp_end_transaction_sender(REQUEST_END, session_data_p->session_id, &num_messages_sent) != NGX_OK) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to send request end data to the nano service. Session ID: %d",
session_data_p->session_id
);
handle_inspection_failure(inspection_failure_weight, fail_mode_verdict, session_data_p);
return fail_mode_verdict;
}
session_data_p->remaining_messages_to_reply += num_messages_sent;
}
// Fetch nano services' results.
return ngx_http_cp_reply_receiver(
&session_data_p->remaining_messages_to_reply,
&session_data_p->verdict,
session_data_p->session_id,
request,
modifications
);
}
ngx_int_t
does_contain_body(ngx_http_headers_in_t *headers)
{
return headers->chunked || (headers->content_length_n != (off_t)(-1) && headers->content_length_n > 0);
}
void *
ngx_http_cp_req_header_handler_thread(void *_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_cp_session_data *session_data_p = ctx->session_data_p;
ngx_int_t send_meta_data_result;
ngx_uint_t num_messages_sent = 0;
ngx_int_t send_header_result;
send_meta_data_result = ngx_http_cp_meta_data_sender(request, session_data_p->session_id, &num_messages_sent);
if (send_meta_data_result == inspection_irrelevant) {
// Ignoring irrelevant requests.
session_data_p->verdict = TRAFFIC_VERDICT_IRRELEVANT;
write_dbg(DBG_LEVEL_DEBUG, "Ignoring non-interesting request. Session ID: %d", session_data_p->session_id);
THREAD_CTX_RETURN(NGX_OK);
}
if (send_meta_data_result != NGX_OK) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to send request meta data to the nano service. Session ID: %d",
session_data_p->session_id
);
handle_inspection_failure(inspection_failure_weight, fail_mode_verdict, session_data_p);
THREAD_CTX_RETURN(fail_mode_verdict);
}
session_data_p->remaining_messages_to_reply += num_messages_sent;
if (is_timeout_reached(&metric_timeout)) {
// Thread task was timed out.
set_metric_cpu_usage();
set_metric_memory_usage();
if (ngx_http_cp_metric_data_sender() != NGX_OK) {
write_dbg(DBG_LEVEL_DEBUG, "Failed to send metric data from the plugin to the service");
}
metric_timeout = get_timeout_val_sec(METRIC_TIMEOUT_VAL);
}
num_messages_sent = 0;
send_header_result = ngx_http_cp_header_sender(
&(request->headers_in.headers.part),
REQUEST_HEADER,
session_data_p->session_id,
&num_messages_sent
);
if (send_header_result != NGX_OK) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to send request headers to the nano service. Session ID: %d",
session_data_p->session_id
);
handle_inspection_failure(inspection_failure_weight, fail_mode_verdict, session_data_p);
THREAD_CTX_RETURN(fail_mode_verdict);
}
session_data_p->remaining_messages_to_reply += num_messages_sent;
// Notify the nano service that we've reached the end of the request headers.
ctx->res = end_req_header_handler(session_data_p, request, &ctx->modifications);
// The caller function will continue and apply the modified ctx->modifications
return NULL;
}
void *
ngx_http_cp_req_body_filter_thread(void *_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_cp_session_data *session_data_p = ctx->session_data_p;
ngx_int_t is_last_part;
ngx_int_t send_body_result;
ngx_uint_t num_messages_sent = 0;
send_body_result = ngx_http_cp_body_sender(
ctx->chain,
REQUEST_BODY,
session_data_p,
&is_last_part,
&num_messages_sent,
&ctx->chain
);
if (send_body_result != NGX_OK) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to send request body data to the nano service. Session ID: %d",
session_data_p->session_id
);
handle_inspection_failure(inspection_failure_weight, fail_mode_verdict, session_data_p);
if (fail_mode_verdict == NGX_OK) {
THREAD_CTX_RETURN_NEXT_FILTER();
}
THREAD_CTX_RETURN(NGX_ERROR);
}
session_data_p->remaining_messages_to_reply += num_messages_sent;
num_messages_sent = 0;
if (is_last_part) {
// Signals the nano service that the transaction reached the end.
if (ngx_http_cp_end_transaction_sender(REQUEST_END, session_data_p->session_id, &num_messages_sent) != NGX_OK) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to send request end data to the nano service. Session ID: %d",
session_data_p->session_id
);
handle_inspection_failure(inspection_failure_weight, fail_mode_verdict, session_data_p);
if (fail_mode_verdict == NGX_OK) {
THREAD_CTX_RETURN_NEXT_FILTER();
}
THREAD_CTX_RETURN(NGX_ERROR);
}
session_data_p->remaining_messages_to_reply++;
}
// Fetch nano services' results.
ctx->res = ngx_http_cp_reply_receiver(
&session_data_p->remaining_messages_to_reply,
&session_data_p->verdict,
session_data_p->session_id,
request,
&ctx->modifications
);
if (is_last_part) session_data_p->was_request_fully_inspected = 1;
return NULL;
}
void *
ngx_http_cp_res_header_filter_thread(void *_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_cp_session_data *session_data_p = ctx->session_data_p;
ngx_int_t set_response_content_encoding_res;
ngx_int_t send_res_code_result;
ngx_int_t send_content_length_result;
ngx_int_t send_header_result;
ngx_uint_t num_messages_sent = 0;
// Sends response code to the nano service.
send_res_code_result = ngx_http_cp_res_code_sender(
request->headers_out.status,
session_data_p->session_id,
&num_messages_sent
);
if (send_res_code_result != NGX_OK) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to send response meta data to the nano service. Session ID: %d",
session_data_p->session_id
);
handle_inspection_failure(inspection_failure_weight, fail_mode_verdict, session_data_p);
if (fail_mode_verdict == NGX_OK) {
THREAD_CTX_RETURN_NEXT_FILTER();
}
THREAD_CTX_RETURN(NGX_ERROR);
}
session_data_p->remaining_messages_to_reply += num_messages_sent;
num_messages_sent = 0;
// Sends response content length to the nano service.
send_content_length_result = ngx_http_cp_content_length_sender(
request->headers_out.content_length_n,
session_data_p->session_id,
&num_messages_sent
);
if (send_content_length_result != NGX_OK) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to send headers content length to the nano service. Session ID: %d",
session_data_p->session_id
);
handle_inspection_failure(inspection_failure_weight, fail_mode_verdict, session_data_p);
if (fail_mode_verdict == NGX_OK) {
THREAD_CTX_RETURN_NEXT_FILTER();
}
THREAD_CTX_RETURN(NGX_ERROR);
}
session_data_p->remaining_messages_to_reply += num_messages_sent;
// Sets response body's content encoding.
set_response_content_encoding_res = set_response_content_encoding(
&session_data_p->response_data.original_compression_type,
request->headers_out.content_encoding
);
if (set_response_content_encoding_res != NGX_OK) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to set response body's content encoding. Session ID: %d",
session_data_p->session_id
);
handle_inspection_failure(inspection_failure_weight, fail_mode_verdict, session_data_p);
if (fail_mode_verdict == NGX_OK) {
THREAD_CTX_RETURN_NEXT_FILTER();
}
THREAD_CTX_RETURN(NGX_ERROR);
}
session_data_p->response_data.new_compression_type = session_data_p->response_data.original_compression_type;
// Sends response headers to the nano service.
num_messages_sent = 0;
send_header_result = ngx_http_cp_header_sender(
&request->headers_out.headers.part,
RESPONSE_HEADER,
session_data_p->session_id,
&num_messages_sent
);
if (send_header_result != NGX_OK) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to send response headers to the nano service. Session ID: %d",
session_data_p->session_id
);
handle_inspection_failure(inspection_failure_weight, fail_mode_verdict, session_data_p);
if (fail_mode_verdict == NGX_OK) {
THREAD_CTX_RETURN_NEXT_FILTER();
}
THREAD_CTX_RETURN(NGX_ERROR);
}
session_data_p->remaining_messages_to_reply += num_messages_sent;
// Fetch nano services' results.
ctx->res = ngx_http_cp_reply_receiver(
&session_data_p->remaining_messages_to_reply,
&session_data_p->verdict,
session_data_p->session_id,
request,
&ctx->modifications
);
return NULL;
}
void *
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;
ngx_http_request_t *request = ctx->request;
ngx_http_cp_session_data *session_data_p = ctx->session_data_p;
ngx_int_t send_body_result;
ngx_uint_t num_messages_sent = 0;
ngx_int_t is_last_response_part = 0;
// Send response body data to the nano service.
send_body_result = ngx_http_cp_body_sender(
ctx->chain,
RESPONSE_BODY,
session_data_p,
&is_last_response_part,
&num_messages_sent,
&ctx->chain
);
if (send_body_result != NGX_OK) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to send response body data to the nano service. Session ID: %d",
session_data_p->session_id
);
handle_inspection_failure(inspection_failure_weight, fail_mode_verdict, session_data_p);
if (fail_mode_verdict == NGX_OK) {
THREAD_CTX_RETURN_NEXT_FILTER();
}
THREAD_CTX_RETURN(NGX_ERROR);
}
session_data_p->remaining_messages_to_reply += num_messages_sent;
num_messages_sent = 0;
if (is_last_response_part) {
// Signals the nano service that the transaction reached the end.
if (ngx_http_cp_end_transaction_sender(RESPONSE_END, session_data_p->session_id, &num_messages_sent) != NGX_OK) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to send response end data to the nano service. Session ID: %d",
session_data_p->session_id
);
handle_inspection_failure(inspection_failure_weight, fail_mode_verdict, session_data_p);
if (fail_mode_verdict == NGX_OK) {
THREAD_CTX_RETURN_NEXT_FILTER();
}
THREAD_CTX_RETURN(NGX_ERROR);
}
session_data_p->remaining_messages_to_reply++;
}
// Fetch nano services' results.
ctx->res = ngx_http_cp_reply_receiver(
&session_data_p->remaining_messages_to_reply,
&session_data_p->verdict,
session_data_p->session_id,
request,
&ctx->modifications
);
return NULL;
}

View File

@@ -0,0 +1,157 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_hook_threads.h
#ifndef __NGX_CP_HOOK_THREADS_H__
#define __NGX_CP_HOOK_THREADS_H__
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <unistd.h>
typedef struct ngx_http_cp_session_data ngx_http_cp_session_data; ///< Holds per-session data.
typedef struct ngx_http_cp_modification_list ngx_http_cp_modification_list; ///< Holds per-session data modifications.
/// @struct ngx_http_cp_event_thread_ctx_t
/// @brief Holds all the information needed to communicate with the attachment service.
struct ngx_http_cp_event_thread_ctx_t
{
ngx_http_request_t *request; ///< NGINX request.
ngx_http_cp_session_data *session_data_p; ///< Provided session data.
ngx_chain_t *chain; ///< only relevant to body filters
/// Connection results with the attachment service
/// - #NGX_OK
/// - #NGX_ERROR
ngx_int_t res;
/// Sets if the context should return and not continue to the next filter.
int should_return;
/// Should context continue to the next filter.
int should_return_next_filter;
ngx_http_cp_modification_list *modifications; ///< Context's modification.
};
///
/// @brief Modifies already_registered value.
/// already_registered is a value that symbolize a successful registeration of the thread context with the nano service.
/// @param[in] value set registration with the nano service:
/// - #0 Thread is not registered with the nano service.
/// - #1 Thread is registered with the nano service.
/// @returns current registration status.
///
void set_already_registered(ngx_int_t value);
///
/// @brief Returns already_registered value.
/// already_registered is a value that symbolize a successful registeration of the thread context with the nano service.
/// @returns ngx_in_t get registration value with the nano service:
/// - #0 Thread is not registered with the nano service.
/// - #1 Thread is registered with the nano service.
///
ngx_int_t get_already_registered(void);
///
/// @brief Initates ngx_http_cp_event_thread_ctx_t struct.
/// @param[in, out] ctx struct to initiate.
/// @param[in] request
/// @param[in] session_data_p
/// @param[in] chain
/// Modifies _ctx res to the following values:
/// - #NGX_OK
/// - #NGX_ERROR
/// @return NULL.
///
void
init_thread_ctx(
struct ngx_http_cp_event_thread_ctx_t *ctx,
ngx_http_request_t *request,
ngx_http_cp_session_data *session_data_p,
ngx_chain_t *chain
);
///
/// @brief Registers the context against the nano agent.
/// @note _ctx needs to be properly initialized by init_thread_ctx().
/// @param[in, out] _ctx is of type ngx_http_cp_event_thread_ctx_t.
/// Modifies _ctx res to the following values:
/// - #NGX_OK
/// - #NGX_ERROR
/// @return NULL.
///
void * ngx_http_cp_registration_thread(void *_ctx);
///
/// @brief Sends request headers to the attachment's service.
/// @details Communicates with the attachment service by sending request headers to the attachment's service
/// and modifies _ctx by the received response.
/// @note _ctx needs to be properly initialized by init_thread_ctx().
/// @param[in, out] _ctx is of type ngx_http_cp_event_thread_ctx_t.
/// Modifies _ctx res to the following values:
/// - #NGX_OK
/// - #NGX_ERROR
/// @return NULL.
///
void * ngx_http_cp_req_header_handler_thread(void *_ctx);
///
/// @brief Sends request body to the attachment's service.
/// @details Communicates with the attachment service by sending request body to the attachment's service
/// and modifies _ctx by the received response.
/// @note _ctx needs to be properly initialized by init_thread_ctx() and ngx_chain_t needs of not NULL.
/// @param[in, out] _ctx is of type ngx_http_cp_event_thread_ctx_t.
/// Modifies _ctx res to the following values:
/// - #NGX_OK
/// - #NGX_ERROR
/// @return NULL.
///
void * ngx_http_cp_req_body_filter_thread(void *_ctx);
///
/// @brief Sends response headers to the attachment's service.
/// @details Communicates with the attachment service by sending response headers to the attachment's service
/// and modifies _ctx by the received response.
/// @note _ctx needs to be properly initialized by init_thread_ctx().
/// @param[in, out] _ctx is of type ngx_http_cp_event_thread_ctx_t.
/// Modifies _ctx res to the following values:
/// - #NGX_OK
/// - #NGX_ERROR
/// @return NULL.
///
void * ngx_http_cp_res_header_filter_thread(void *_ctx);
///
/// @brief Sends response body to the attachment's service.
/// @details Communicates with the attachment service by sending response bodies to the attachment's service
/// and modifies _ctx by the received response.
/// @note _ctx needs to be properly initialized by init_thread_ctx() and ngx_chain_t needs to be defined.
/// @param[in, out] _ctx is of type ngx_http_cp_event_thread_ctx_t.
/// Modifies _ctx res to the following values:
/// - #NGX_OK
/// - #NGX_ERROR
/// @return NULL.
///
void * ngx_http_cp_res_body_filter_thread(void *_ctx);
///
/// @brief Check if transaction contains headers.
/// @param[in] headers ngx_http_headers_in_t struct.
/// @returns 1 if the transaction contains headers, otherwise 0.
///
ngx_int_t does_contain_body(ngx_http_headers_in_t *headers);
#endif // __NGX_CP_HOOK_THREADS_H__

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,119 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_hooks.h
#ifndef __NGX_CP_HOOKS_H__
#define __NGX_CP_HOOKS_H__
#include <time.h>
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <unistd.h>
#include "ngx_cp_http_parser.h"
#include "nginx_attachment_common.h"
static const int registration_failure_weight = 2; ///< Registration failure weight.
static const int inspection_failure_weight = 1; ///< Inspection failure weight.
static const ngx_int_t METRIC_TIMEOUT_VAL = METRIC_PERIODIC_TIMEOUT;
/// @struct ngx_http_cp_session_data
/// @brief Holds all the session's information needed to communicate with the nano service.
/// @details Such as to save verdict and session ID between the request and the response
typedef struct ngx_http_cp_session_data {
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.
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_http_response_data response_data; ///< Holds session's response data.
struct timespec session_start_time; ///< Holds session's start time.
double req_proccesing_time; ///< Holds session's request processing time.
double res_proccesing_time; ///< Holds session's response processing time.
uint64_t processed_req_body_size; ///< Holds session's request body's size.
uint64_t processed_res_body_size; ///< Holds session's response body's size'.
} ngx_http_cp_session_data;
///
/// @brief Sends response body to the nano service.
/// @details Initiates all the needed context data and session data and calls the relevant threads
/// to communicate the response bodies to the nano service and fetch the response back to the NGINX.
/// @param[in, out] request NGINX request.
/// @param[in, out] input 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 *input);
///
/// @brief Sends request body to the nano service.
/// @details Initiates all the needed context data and session data and calls the relevant threads
/// to communicate the request bodies to the nano service and fetch the response back to the NGINX.
/// @param[in, out] request NGINX request.
/// @param[in, out] input_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 *input_body_chain);
///
/// @brief Sends response headers to the nano service.
/// @details Initiates all the needed context data and session data and calls the relevant threads
/// to communicate the response headers to the nano service and fetch the response back to the NGINX.
/// @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);
///
/// @brief Sends request headers to the nano service.
/// @details Initiates all the needed context data and session data and calls the relevant threads
/// to communicate the request headers to the nano service and fetch the response back to the NGINX.
/// @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);
///
/// @brief Checks if transaction was timed out.
/// @param[in, out] ctx
/// @note ctx needs to be properly intialized.
/// @returns ngx_int_t
/// - #0 - Timed out did not occure.
/// - #1 - Timed out occured.
///
ngx_int_t was_transaction_timedout(ngx_http_cp_session_data *ctx);
///
/// @brief Enforces the sessions rate.
/// @returns ngx_http_cp_verdict_e
/// - #TRAFFIC_VERDICT_INSPECT
/// - #TRAFFIC_VERDICT_ACCEPT
/// - #TRAFFIC_VERDICT_DROP
///
ngx_http_cp_verdict_e enforce_sessions_rate();
#endif // __NGX_CP_HOOKS_H__

View File

@@ -0,0 +1,88 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_http_parser.c
#include "ngx_cp_http_parser.h"
#include "ngx_cp_utils.h"
static const char *gzip_encoding_string = "gzip";
static const char *zlib_encoding_string = "deflate";
static const char *identity_encoding_string = "identity";
ngx_int_t
parse_content_encoding(CompressionType *response_encoding, const ngx_str_t *content_encoding_header_value)
{
if (ngx_strcmp(content_encoding_header_value->data, gzip_encoding_string) == 0) {
/// Sets GZIP encoding.
*response_encoding = GZIP;
return NGX_OK;
}
if (ngx_strcmp(content_encoding_header_value->data, zlib_encoding_string) == 0) {
/// Sets GZIP encoding.
*response_encoding = ZLIB;
return NGX_OK;
}
if (ngx_strcmp(content_encoding_header_value->data, identity_encoding_string) == 0) {
/// Sets NO_COMPRESSION encoding.
*response_encoding = NO_COMPRESSION;
return NGX_OK;
}
write_dbg(
DBG_LEVEL_WARNING,
"Unsupported response content encoding: %.*s",
content_encoding_header_value->len,
content_encoding_header_value->data
);
return NGX_ERROR;
}
ngx_int_t
set_response_content_encoding(CompressionType *content_encoding, const ngx_table_elt_t *content_encoding_header)
{
ngx_int_t parse_content_encoding_result;
write_dbg(DBG_LEVEL_TRACE, "Determining response body's content encoding");
if (content_encoding_header == NULL) {
*content_encoding = NO_COMPRESSION;
write_dbg(DBG_LEVEL_TRACE, "Response body is not encoded");
return NGX_OK;
}
write_dbg(
DBG_LEVEL_TRACE,
"Detected Content-Encoding header: key: %.*s, value: %.*s",
content_encoding_header->key.len,
content_encoding_header->key.data,
content_encoding_header->value.len,
content_encoding_header->value.data
);
/// Parses content header's value into content_encoding variable.
parse_content_encoding_result = parse_content_encoding(content_encoding, &content_encoding_header->value);
if (parse_content_encoding_result != NGX_OK) return NGX_ERROR;
write_dbg(
DBG_LEVEL_TRACE,
"Parsed content encoding: %.*s",
content_encoding_header->value.len,
content_encoding_header->value.data
);
return NGX_OK;
}

View File

@@ -0,0 +1,84 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_http_parser.h
#ifndef __NGX_CP_HTTP_PARSER_H__
#define __NGX_CP_HTTP_PARSER_H__
#include <ngx_core.h>
#include "compression_utils.h"
/// @struct ngx_http_response_data
/// Holds all the data for NGINX CP http response data.
typedef struct {
uint32_t num_body_chunk; ///< Number of body chunks.
/// NGINX Response data status
/// - #NGX_OK
/// - #NGX_ERROR
ngx_int_t response_data_status;
/// Original compression type, can hold the following values:
/// - #GZIP
/// - #ZLIB
CompressionType original_compression_type;
/// A new compression type, can hold the following values:
/// - #GZIP
/// - #ZLIB
CompressionType new_compression_type;
/// Compression stream
CompressionStream *compression_stream;
/// Decompression stream
CompressionStream *decompression_stream;
} ngx_http_response_data;
///
/// @brief Parses content encoding and returns it in response_encoding.
/// @param[in, out] response_encoding Returns value of one of the supported encoding:
/// - #GZIP
/// - #ZLIB
/// - #NO_COMPRESSION
/// @param[in, out] content_encoding_header_value Encoded value.
/// @return ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR - Unsupported encoding.
///
ngx_int_t
parse_content_encoding(
CompressionType *response_encoding,
const ngx_str_t *content_encoding_header_value
);
///
/// @brief Sets the content encoding type of the provided encoding header.
/// @param[in, out] content_encoding Returns variable of one of the supported encoding:
/// - #GZIP
/// - #ZLIB
/// - #NO_COMPRESSION
/// @param[in, out] content_encoding_header NGINX table Encoding header.
/// @return ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR - Unsupported encoding.
///
ngx_int_t
set_response_content_encoding(
CompressionType *content_encoding,
const ngx_table_elt_t *content_encoding_header
);
#endif // __NGX_CP_HTTP_PARSER_H__

View File

@@ -0,0 +1,496 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_http_usercheck.h
/// \brief This file contains the string for the block-page returned instead of the normal server response
"<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n"
"\t\t<meta name=\"robots\" content=\"noindex,nofollow\">\n"
"\t\t<meta content=\"yes\" name=\"apple-mobile-web-app-capable\">\n"
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=0.75\">\t\n"
" <div id=\"portal_top_pane_div\" class=\"usercheck_header_class\" >\n"
"<img class=\"usercheck_company_logo_class\" id=\"usercheck_company_logo\"><table height=\"108px\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" class=\"usercheck_header_table_class usercheck_content_table_class\">\n"
"\t\t<img style='display:block; width:75px;height:50px;' class='usercheck_company_logo_class' id='usercheck_company_logo' \n"
" src='data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAAJMAAABuCAYAAAAj6SHSAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAEFkb2JlIEltYWdlUmVhZHlxyWU8AAADIWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkFCRkI4ODI2MzM2RDExRTg5MDg5RjkxMTgwQTA1QTZDIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkFCRkI4ODI3MzM2RDExRTg5MDg5RjkxMTgwQTA1QTZDIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QUJGQjg4MjQzMzZEMTFFODkwODlGOTExODBBMDVBNkMiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QUJGQjg4MjUzMzZEMTFFODkwODlGOTExODBBMDVBNkMiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94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' /> \t\t\n"
" <p style=\"font-size: 22px;font-family: 'Segoe UI'\">Check Point Infinity Next</p>\n"
"</table>\n"
" </div>\n"
" <style>\n"
"p\n"
"{\n"
"\tmargin: 0px;\n"
"}\n"
"\n"
"/* SDB_PREVIEW_EXCLUDE_BEGIN */\n"
".portalMessage_title\n"
"{\n"
"\tfont-size: 18px;\n"
"\tfont-weight: bold;\n"
"\tcolor: #FFFFFF;\n"
"\tbackground: url('/UserCheck/img/pop_up_banner.png') repeat-x center center;\n"
" height: 60px;\n"
"}\n"
"\n"
".portalMessage_title_td\n"
"{\n"
" padding-top: 13px;\n"
"}\n"
"\n"
".portalMessageTableContainer\n"
"{\n"
"\tfont-family: \"Segoe UI\";\n"
"\twidth: 100%;\n"
"\tcursor: default;\n"
"}\n"
"\n"
"/* SDB_PREVIEW_EXCLUDE_END */\n"
"\n"
".usercheck_body_background_class\n"
"{\n"
"\tbackground: #f7f7f7 repeat scroll center center;\t\n"
"}\n"
"\n"
".usercheck_main_background_class\n"
"{\n"
"\tbackground: repeat-y scroll center center;\t\n"
"}\n"
"\n"
".usercheck_content_table_class\n"
"{\n"
"\tposition: relative;\n"
"\twidth:550px;\n"
"\ttable-layout: fixed;\n"
"\ttext-wrap: suppress;\n"
"\tmargin-left:auto;\n"
"\tmargin-right:auto;\n"
"\tmargin-top: 35px;\n"
"\ttext-align:center;\t\n"
"}\n"
"\n"
".usercheck_buttons_table_class\n"
"{\t\n"
"\tposition: relative;\n"
"\tmargin-top: -15px;\n"
"\twidth:400px;\n"
"\ttable-layout: fixed;\n"
"\ttext-wrap: suppress;\n"
"\tmargin-left:auto;\n"
"\tmargin-right:auto;\n"
"\ttext-align:center;\n"
"}\n"
"\n"
".usercheck_title_class\n"
"{\n"
"\tposition: relative;\n"
"\tmargin-top: 20px;\n"
"\tmargin-left:auto;\n"
"\tmargin-right:auto;\n"
"\tfont-family: \"Segoe UI\";\n"
"\tfont-size: 40px;\n"
"\tfont-weight: 100;\n"
"\tcolor: #333333;\n"
"\tword-wrap:break-word;\n"
"\twidth: expression( document.body.clientWidth > 599 ? \"600px\" : \"auto\" ); /* sets max-width for IE */\n"
"\tmax-width: 600px; /* this sets the max-width value for all standards-compliant browsers */\n"
"\ttext-align: center;\n"
"}\n"
"\n"
".usercheck_message_icon_class\n"
"{\t\n"
"\tmargin-right: auto;\n"
"\tmargin-left: auto;\n"
"\tposition: relative;\n"
"\tmargin-top: 35px;\n"
"\twidth: auto;\n"
"\theight: auto;\n"
"\ttext-align: center;\n"
"}\n"
"\n"
".usercheck_message_icon_column\n"
"{\n"
"\twidth:70px;\n"
"}\n"
"\n"
".usercheck_red_highlight\n"
"{\n"
"\tborder: 1px red solid;\n"
"}\n"
"\n"
".usercheck_red_no_highlight\n"
"{\n"
"\tborder: auto;\n"
"}\n"
"\n"
".usercheck_logo_img_div_class\n"
"{\n"
"\tbackground: transparent url('cpnewlogo_1.png') no-repeat scroll;\n"
"\tbackground-position: center;\n"
"\tdisplay: block;\n"
"\t/*width: auto; \n"
"\theight: auto; \n"
"\tmargin: 0;\n"
"\tpadding: 0;*/\n"
"\tposition: relative;\n"
"\tmargin-top: 30px;\n"
"\tmargin-right: auto;\n"
"\tmargin-left: auto;\n"
"\theight: 30px;\n"
"}\n"
"\n"
".smartField incident_id\n"
"{\n"
"\tcolor: #7c7c7c;\n"
"}\n"
"\n"
".smartField orig_url\n"
"{\n"
"\tcolor: 1b81ff; \n"
"}\n"
"\n"
".usercheck_message_class\n"
"{\n"
"\tmargin-right: auto;\n"
"\tmargin-left: auto;\n"
"\tposition: relative;\n"
"\tmargin-top: 0px;\n"
"\tfont-family: \"Segoe UI\";\n"
"\tfont-size: 17px;\n"
"\tcolor: #333333;\n"
"\tline-height: 20px;\n"
"\ttext-align: center;\n"
"\twidth: expression( document.body.clientWidth > 499 ? \"550px\" : \"auto\" ); /* sets max-width for IE */\n"
"\tmax-width: 550px; /* this sets the max-width value for all standards-compliant browsers */\n"
"\tmin-width: 550px; /* this sets the max-width value for all standards-compliant browsers */\n"
"\ttext-wrap: suppress;\n"
"\tword-wrap: break-word;\n"
"\twhite-space: nowrap;\n"
"}\n"
"\n"
"p.instance_id_uuid {\n"
"\tfont-size: 12;\n"
"}\n"
".usercheck_url_class\n"
"{\n"
"\tfont-family: \"Segoe UI\";\n"
"\tfont-size: 17px;\n"
"\t/* padding: 10px 15px 0px 15px; */\n"
"\tline-height: 20px;\n"
"\ttext-align: left;\n"
"\twidth: expression( document.body.clientWidth > 334 ? \"335px\" : \"auto\" ); /* sets max-width for IE */\n"
"\tmax-width: 335px; /* this sets the max-width value for all standards-compliant browsers */\n"
"\ttext-decoration: underline;\n"
"\tcolor: #3778FF;\n"
"\ttext-wrap: suppress;\n"
" word-wrap: break-word;\n"
"}\n"
"\n"
".usercheck_url_class:hover\n"
"{\n"
"\tcolor: #1B2F5B;\n"
"\ttext-decoration: underline;\n"
"}\n"
"\n"
".usercheck_disabled_url_class\n"
"{\n"
"\tcolor: #1B2F5B;\n"
"\ttext-decoration: none;\n"
"\tcursor: text;\n"
"}\n"
"\n"
".usercheck_disabled_url_class:hover\n"
"{\n"
"\tcolor: #1B2F5B;\n"
"\ttext-decoration: none;\n"
"\tcursor: text;\n"
"}\n"
"\n"
".usercheck_textual_input_class\n"
"{\n"
"\t\n"
"\tline-height: normal;\n"
" padding: 2px 2px 2px 2px;\n"
"\tmargin-left: -1px;\n"
"\tmargin-top: 30px;\n"
" width: 328px;\n"
"\tmax-width: 328px;\n"
"\tmin-width: 328px;\n"
"\toverflow-y: auto;\n"
"\toverflow-x: hidden;\n"
"\ttext-align: left;\n"
"\t\n"
"}\n"
"\n"
".usercheck_textual_input_class_required\n"
"{\n"
"\tcolor: #8F8F8F;\n"
"\t/*border: 1px solid #92979b;*/\n"
"}\n"
"\n"
".usercheck_header_class\n"
"{\n"
"\tvertical-align: middle;\n"
"\ttext-align: center;\n"
"\tbackground-color: #ffffff;\n"
" height: 108px;\n"
"\tborder-bottom: 1px solid #c7c7c7;\n"
"}\n"
"\n"
".usercheck_header_table_class\n"
"{\n"
"\tmargin-left:auto;\n"
"\tmargin-right:auto;\n"
"\tmargin-top: 0px;\n"
"\twidth: 100%;\n"
"\theight: 60px;\n"
"\t/*direction:ltr;*/\n"
"}\n"
"\n"
".usercheck_company_logo_class\n"
"{\n"
"\tposition:relative;\n"
"\tmargin-left: auto;\n"
"\tmargin-right: auto;\n"
"\tmargin-bottom: 6px;\n"
"\tmargin-top: 3px;\n"
"\ttext-align: center;\n"
"\tmax-width: 300px;\n"
"\tmax-height: 300px;\n"
" font-size: 22px;\n"
"\t/* SDB_PREVIEW_EXCLUDE_BEGIN */\n"
"\twidth: expression( this.width > 175 ? ( this.width / this.height > 176/52 ? \"176px\" : \"auto\" ) : \"auto\" ); /* sets max-width for IE6 */\n"
"\theight: expression( this.height > 51 ? ( this.width / this.height > 176/52 ? \"auto\" : \"52px\" ) : \"auto\" ); /* sets max-height for IE6 */\n"
"\t/* SDB_PREVIEW_EXCLUDE_END */\n"
"}\n"
"\n"
".usercheck_header_left_class\n"
"{\n"
"}\n"
"\n"
".usercheck_header_center_class\n"
"{\t\n"
"}\n"
"\n"
".usercheck_header_right_class\n"
"{\t\n"
"}\n"
"\n"
".usercheck_header_right_empty_class\n"
"{\t\n"
"}\n"
"\n"
".usercheck_page_title_class\n"
"{\n"
"\tposition: relative;\n"
"\tmargin-left:auto;\n"
"\tmargin-right:auto;\n"
"\t/*width: 1100px;*/\n"
"\ttext-align: left;\n"
"\tfont-family: \"Segoe UI\";\n"
"\tfont-size: 23px;\n"
"\tfont-weight: 100;\n"
"\tcolor: #FFFFFF;\n"
"\ttop: -12px;\n"
"\t\n"
"\t-webkit-user-select: text;\n"
"\t-khtml-user-select: text;\n"
"\t-moz-user-select: text;\n"
"\t-o-user-select: text;\n"
"\tuser-select: text;\n"
"\t\n"
"}\n"
"\n"
".usercheck_footer_table_class\n"
"{\n"
"\tmargin-left:auto;\n"
"\tmargin-right:auto;\n"
"\twidth: 100%;\n"
"\twidth: 100%;\n"
"}\n"
"\n"
".footer_logo\n"
"{\t\n"
"}\n"
"\n"
".footer_no_logo\n"
"{\n"
"}\n"
"\n"
".usercheck_footer_height_class\n"
"{\n"
"\theight: 40px;\n"
"\tvertical-align: bottom;\n"
"}\n"
"\n"
".usercheck_footer_class\n"
"{\t\n"
"\tposition: relative;\n"
"\tmargin-top: 80px;\n"
"\tmargin-bottom: 10px;\n"
"\tvertical-align: bottom;\n"
"\ttext-align: right;\n"
"}\n"
"\n"
".usercheck_lang_select_div_class\n"
"{\n"
"\tmargin-left: 60%;\n"
"\tfont-family: \"Segoe UI\";\n"
"\tfont-size: 17px;\n"
"\tcolor: #333333;\n"
"}\n"
"\n"
".usercheck_lang_select_class\n"
"{\t\n"
"\tfloat: right;\n"
"\tfont-family: \"Segoe UI\";\n"
"\tfont-size: 17px;\n"
"\tcolor: #333333;\n"
"}\n"
"\n"
".usercheck_ok_button_div_class\n"
"{\n"
"\twidth: 110px;\n"
"}\n"
"\n"
".usercheck_middle_button_div_class\n"
"{\n"
"\twidth: 110px;\n"
"\t/*margin-left:auto;*/\n"
"}\n"
"\n"
"/* Buttons section - split into several portions for IE support. */\n"
"\n"
"a.button {\n"
"\tbackground: #4a4a4a;\n"
" display: block;\n"
" height: 28px;\n"
"\tmargin-top: 15px;\n"
" padding-right: 14px; /* Sliding doors padding. */\n"
" text-decoration: none;\n"
"\tcursor: pointer;\n"
"\ttext-align: center;\n"
"\tborder: solid 1px #2c2c2c;\n"
"\tborder-radius: 2px;\n"
"\t/* Normal state right image position. */\n"
"\tbackground-position: right 0px;\n"
"}\n"
"a.button span {\n"
"\tbackground: #4a4a4a;\n"
"\tdisplay: block;\n"
" line-height: 28px;\n"
" padding: 0px 22px 0px 35px;\n"
"\t/* Font styling. */\n"
"\tfont-family: \"Segoe UI\";\n"
"\tfont-weight: 600;\n"
"\tfont-size: 17px;\n"
"\tcolor: #FFFFFF;\n"
"\t/* Cursor styling. */\n"
"\tcursor: pointer;\n"
"\t/* Normal state left image position. */\n"
"\tbackground-position: left 0px;\n"
"}\n"
"\n"
"a.cancel_button span {\n"
" padding: 0px 20px 0px 30px;\n"
"}\n"
"\n"
"a.buttonHover {\n"
"\t/* Normal state right image position. */\n"
"\tbackground-position: right -41px;\n"
"}\n"
"\n"
"a.buttonHover span {\n"
"\t/* Normal state left image position. */\n"
"\tbackground-position: left -41px;\n"
"}\n"
"\n"
"a.buttonActive {\n"
"\t/* Normal state right image position. */\n"
"\tbackground-position: right -82px;\n"
"}\n"
"\n"
"a.buttonActive span {\n"
"\t/* Normal state left image position. */\n"
"\tbackground-position: left -82px;\n"
"}\n"
"\n"
"a.buttonDisabled {\n"
"\t/* Normal state right image position. */\n"
"\tbackground-position: right -123px;\n"
"\tcursor: default;\n"
"}\n"
"\n"
"a.buttonDisabled span {\n"
"\t/* Text color. */\n"
"\tcolor: #BABABA;\n"
"\tdisplay: block;\n"
"\tline-height: 41px;\n"
"\tpadding: 0px 22px 0px 25px;\n"
"\t/* Cursor styling. */\n"
"\tcursor: default;\n"
"\t/* Normal state left image position. */\n"
"\tbackground-position: left -123px;\n"
"}\n"
"\n"
"a.buttonDisabled:hover {\n"
"\tcolor: #BABABA;\n"
"}\n"
" </style>\n"
"\t</head>\n"
"\t<body class=\"usercheck_body_background_class\" unselectable=\"on\">\n"
"\t\t<div id=\"portal_main_view\" class=\"\" unselectable=\"on\">\n"
"\t\t\t<table height=\"100%\" width=\"100%\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"\">\n"
"\t\t\t\t<tbody><tr>\n"
"\t\t\t\t\t<td id=\"portal_center_div\" valign=\"top\" unselectable=\"on\" class=\"usercheck_main_background_class\">\n"
"\t\t\t\t\t\t\n"
"\t\t\t\t\t\t<div class=\"usercheck_message_icon_class\" id=\"usercheck_img_div\" unselectable=\"on\">\n"
" <img style='display:block; width:60px;height:60px;' class=\"usercheck_message_icon_class\" id='usercheck_img_div' \n"
" src='data:image/jpeg;base64, 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' />\n"
" </div>\n"
"\t\t\t\t\t\t<div class=\"locale_message usercheck_title_class\" data-locale-message-constnat=\"LOCALE_PHISHING_TITLE\" unselectable=\"on\" style=\"color: rgb(237, 85, 82);font-size: 31px;\"><!-- CHECK_POINT_USERCHECK_TITLE_PLACEHOLDER--></div>\t\t\t\t\t\t\n"
"\t\t\t\t\t\t<div style=\"text-align: center;\" unselectable=\"on\">\n"
"\t\t\t\t\t\t\t<table cellpadding=\"0\" cellspacing=\"0\" class=\"usercheck_content_table_class\">\n"
"\t\t\t\t\t\t\t\t<tbody><tr>\n"
"\t\t\t\t\t\t\t\t\t<td>\n"
"\t\t\t\t\t\t\t\t\t\t<div class=\"usercheck_message_class\" unselectable=\"on\" style=\"FONT-FAMILY: &#39;Arial&#39;, &#39;sans-serif&#39;\"><span class=\"locale_message\" data-locale-message-constnat=\"LOCALE_PHISHING_DOMAINS\" ><p><!-- CHECK_POINT_USERCHECK_BODY_PLACEHOLDER--></p> <p class=\"instance_id_uuid\"><br><!-- CHECK_POINT_USERCHECK_UUID_PLACEHOLDER--></p></span>\n"
"</body></html>";

View File

@@ -0,0 +1,664 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_initializer.c
#include "ngx_cp_initializer.h"
#include <poll.h>
#include <stdint.h>
#include <dirent.h>
#include <arpa/inet.h>
#include <ngx_log.h>
#include <ngx_core.h>
#include <ngx_string.h>
#include <ngx_files.h>
#include "nginx_attachment_common.h"
#include "ngx_cp_io.h"
#include "ngx_cp_utils.h"
#include "ngx_cp_static_content.h"
#include "ngx_cp_compression.h"
#include "attachment_types.h"
#include "ngx_http_cp_attachment_module.h"
typedef enum ngx_cp_attachment_registration_state {
SET_UNIQUE_ID,
RESGISTER_TO_NODE,
LOAD_CONFIG,
LOAD_IPC,
DONE
} ngx_cp_attachment_registration_state_e; ///< Indicates the current initialization stage.
char unique_id[MAX_NGINX_UID_LEN] = ""; // Holds the unique identifier for this instance.
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.
struct sockaddr_un server;
uint32_t nginx_user_id, nginx_group_id; // Hold the process UID and GID respectively.
int
exchange_communication_data_with_service(
int socket,
void *data,
uint32_t size,
ngx_cp_comm_direction_e direction,
struct timeval *remaining_timeout)
{
int res = 0;
ngx_int_t retry;
// `remaining_size` and `cur_data_ptr` are used to keep track of where we are in the memory.
// This allows us to read to\write from the socket in parts (if we need to).
int remaining_size = size;
char *cur_data_ptr = data;
while (remaining_size > 0) {
if (direction == WRITE_TO_SOCKET){
res = write(socket, (void *)cur_data_ptr, remaining_size);
} else {
// The read operation must not block the attachment indefinitely.
// To avoid that we check whether the socket has data to be read prior to the read operation.
// If the socket doesn't have data to be read from within a reasonable time, we treat this as an error.
for (retry = 0; retry < 3; retry++) {
struct pollfd s_poll;
s_poll.fd = socket;
s_poll.events = POLLIN;
s_poll.revents = 0;
res = poll(&s_poll, 1, 1000);
if (res > 0 && (s_poll.revents & POLLIN) != 0) break; // Socket is ready to be read from
res = -1;
}
if (res != -1) {
res = read(socket, (void *)cur_data_ptr, remaining_size);
}
}
// `res` is -1 in case of an error: either write or read functions failed or socket wasn't available.
if (res < 0) {
close(socket);
socket = -1;
write_dbg_if_needed(DBG_LEVEL_TRACE, "Failed to communicate with the socket");
break;
}
remaining_size -= res;
cur_data_ptr += res;
// If the operation exceeded the allowed time, treat it as a failure.
if (is_timeout_reached(remaining_timeout)) {
close(socket);
socket = -1;
write_dbg_if_needed(DBG_LEVEL_TRACE, "Reached timeout while communicating with the socket");
break;
}
}
return res;
}
///
/// @brief Initialize socket communication with the serive.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
static ngx_int_t
init_signaling_socket()
{
uint8_t initialization_ack;
int res = 0;
uint8_t uid_size_to_send = strlen(unique_id);
struct timeval timeout = get_timeout_val_sec(1);
// Close the old socket if there was one.
if (comm_socket > 0) {
close(comm_socket);
comm_socket = -1;
}
// Setup a new socket
comm_socket = socket(AF_UNIX, SOCK_STREAM, 0);
if (comm_socket < 0) {
write_dbg_if_needed(DBG_LEVEL_WARNING, "Could not create socket");
return NGX_ERROR;
}
server.sun_family = AF_UNIX;
strncpy(server.sun_path, shared_verdict_signal_path, sizeof(server.sun_path) - 1);
if (connect(comm_socket, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) == -1) {
close(comm_socket);
comm_socket = -1;
write_dbg_if_needed(
DBG_LEVEL_DEBUG,
"Could not connect to nano service. Path: %s, Error: %s",
server.sun_path,
strerror(errno)
);
return NGX_ERROR;
}
// Pass the following information to the service (in this order):
// 1. The length of the unique identifier for this instance.
// 2. The unique identifier for this instance.
// 3. The process UID.
// 4. The process GID.
// If any of them fail - return an error.
res = exchange_communication_data_with_service(
comm_socket,
&uid_size_to_send,
sizeof(uid_size_to_send),
WRITE_TO_SOCKET,
&timeout
);
if (res <= 0) {
write_dbg_if_needed(DBG_LEVEL_WARNING, "Failed to send unique id size");
return NGX_ERROR;
}
res = exchange_communication_data_with_service(
comm_socket,
unique_id,
uid_size_to_send,
WRITE_TO_SOCKET,
&timeout
);
if (res <= 0) {
write_dbg_if_needed(DBG_LEVEL_WARNING, "Failed to send unique id %s", unique_id);
return NGX_ERROR;
}
res = exchange_communication_data_with_service(comm_socket, &nginx_user_id, sizeof(uint32_t), WRITE_TO_SOCKET, &timeout);
if (res <= 0) {
write_dbg_if_needed(DBG_LEVEL_WARNING, "Failed to send nginx user id");
return NGX_ERROR;
}
res = exchange_communication_data_with_service(
comm_socket,
&nginx_group_id,
sizeof(uint32_t),
WRITE_TO_SOCKET,
&timeout
);
if (res <= 0) {
write_dbg_if_needed(DBG_LEVEL_WARNING, "Failed to send nginx group id");
return NGX_ERROR;
}
// Get an acknowledgement form the service that communication has been established.
timeout = get_timeout_val_sec(1);
res = exchange_communication_data_with_service(
comm_socket,
&initialization_ack,
sizeof(initialization_ack),
READ_FROM_SOCKET,
&timeout
);
if (res <= 0) {
write_dbg_if_needed(DBG_LEVEL_WARNING, "Failed to read registration ack");
return NGX_ERROR;
}
write_dbg_if_needed(DBG_LEVEL_DEBUG, "Successfully connected on client socket %d", comm_socket);
return NGX_OK;
}
#define MAX_CONTAINER_LEN 12 // Maximum size for container identifier
ngx_int_t
get_docker_id(char **_docker_id)
{
// We keep the container ID as a static variable so we won't have to read it multiple times.
// The `already_evaluated` variable indicate if we already have the identifier.
static char docker_id[MAX_CONTAINER_LEN + 1];
static int already_evaluated = 0;
const char *container_id_file_path = "/proc/self/cgroup";
if (already_evaluated) {
// Already found the identifier before, just return the answer.
*_docker_id = docker_id;
return NGX_OK;
}
docker_id[0] = '\0';
FILE *file = fopen(container_id_file_path, "r");
if (file == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to open %s", container_id_file_path);
return NGX_ERROR;
}
write_dbg(DBG_LEVEL_DEBUG, "opened file %s", container_id_file_path);
// Reading the file line by line.
char *line = NULL;
size_t len = 0;
while (getline(&line, &len, file) != -1) {
char *docker_ptr = strstr(line, "docker/");
if (docker_ptr == NULL) continue;
// We've found a line with "docker/" so the identifier will be right after that.
docker_ptr += strlen("docker/");
snprintf(docker_id, MAX_CONTAINER_LEN + 1, "%s", docker_ptr);
break;
}
free(line);
fclose(file);
// Return the answer and set the indication so we won't have to
*_docker_id = docker_id;
already_evaluated = 1;
return NGX_OK;
}
///
/// @brief Register the attachment instance with the attachment manager to associate it with a service.
/// @param[in] request Points to an HTTP request, needed to get the number of workers.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
static ngx_int_t
register_to_attachments_manager(ngx_http_request_t *request)
{
uint8_t path_length;
int res = 0;
uint8_t family_name_size = strlen(unique_id);
uint8_t attachment_type = NGINX_ATT_ID;
uint8_t worker_id = ngx_worker + 1;
uint8_t workers_amount = get_num_of_workers(request);
char *family_name = NULL;
int cur_errno = 0; // temp fix for errno changing during print
struct timeval timeout = get_timeout_val_sec(1);
if (get_docker_id(&family_name) == NGX_ERROR) {
write_dbg(DBG_LEVEL_WARNING, "Could not evaluate family name");
return NGX_ERROR;
}
family_name_size = strlen(family_name);
// If there was an old socket, close it.
if (registration_socket > 0) {
close(registration_socket);
registration_socket = -1;
}
// Connect a new socket.
registration_socket = socket(AF_UNIX, SOCK_STREAM, 0);
if (registration_socket < 0) {
write_dbg(DBG_LEVEL_WARNING, "Could not create socket");
return NGX_ERROR;
}
server.sun_family = AF_UNIX;
strncpy(server.sun_path, SHARED_REGISTRATION_SIGNAL_PATH, sizeof(server.sun_path) - 1);
if (connect(registration_socket, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) == -1) {
cur_errno = errno;
close(registration_socket);
registration_socket = -1;
write_dbg_if_needed(
DBG_LEVEL_DEBUG,
"Could not connect to nano service. Path: %s, Error: %s, Errno: %d",
server.sun_path,
strerror(errno),
cur_errno
);
if (cur_errno == ENOENT) {
strncpy(shared_verdict_signal_path, SHARED_VERDICT_SIGNAL_PATH, 128);
return NGX_OK;
}
return NGX_ERROR;
}
// Send to the attachment manager the following details:
// 1. The type of the attachment (fixed NGINX).
// 2. The number of this worker.
// 3. The total amount of workers.
// 4. The size of the docker ID.
// 5. If the docker ID isn't empty (size 0), the docker id itself.
// If any of these fail - return an error.
res = exchange_communication_data_with_service(
registration_socket,
&attachment_type,
sizeof(attachment_type),
WRITE_TO_SOCKET,
&timeout
);
if (res <= 0) {
write_dbg_if_needed(DBG_LEVEL_WARNING, "Failed to send attachment type");
close(registration_socket);
registration_socket = -1;
return NGX_ERROR;
}
res = exchange_communication_data_with_service(
registration_socket,
&worker_id,
sizeof(worker_id),
WRITE_TO_SOCKET,
&timeout
);
if (res <= 0) {
write_dbg_if_needed(DBG_LEVEL_WARNING, "Failed to send worker ID");
close(registration_socket);
registration_socket = -1;
return NGX_ERROR;
}
res = exchange_communication_data_with_service(
registration_socket,
&workers_amount,
sizeof(workers_amount),
WRITE_TO_SOCKET,
&timeout
);
if (res <= 0) {
write_dbg_if_needed(DBG_LEVEL_WARNING, "Failed to send workers amount");
close(registration_socket);
registration_socket = -1;
return NGX_ERROR;
}
res = exchange_communication_data_with_service(
registration_socket,
&family_name_size,
sizeof(family_name_size),
WRITE_TO_SOCKET,
&timeout
);
if (res <= 0) {
write_dbg_if_needed(DBG_LEVEL_WARNING, "Failed to send family name size");
close(registration_socket);
registration_socket = -1;
return NGX_ERROR;
}
if (family_name_size > 0) {
res = exchange_communication_data_with_service(
registration_socket,
family_name,
family_name_size,
WRITE_TO_SOCKET,
&timeout
);
if (res <= 0) {
write_dbg_if_needed(DBG_LEVEL_WARNING, "Failed to send family name");
close(registration_socket);
registration_socket = -1;
return NGX_ERROR;
}
}
// Read from the attachment manager:
// 1. The length of signal path.
// 2. The signal path itsel.
// If that fails - return an error.
timeout = get_timeout_val_sec(1);
res = exchange_communication_data_with_service(
registration_socket,
&path_length,
sizeof(path_length),
READ_FROM_SOCKET,
&timeout
);
if (res <= 0) {
write_dbg_if_needed(DBG_LEVEL_WARNING, "Failed to read path length");
close(registration_socket);
registration_socket = -1;
return NGX_ERROR;
}
res = exchange_communication_data_with_service(
registration_socket,
shared_verdict_signal_path,
path_length,
READ_FROM_SOCKET,
&timeout
);
if (res <= 0) {
write_dbg_if_needed(DBG_LEVEL_WARNING, "Failed to read socket path");
close(registration_socket);
registration_socket = -1;
return NGX_ERROR;
}
// Successfully go the shared communication path - add null termination and exit.
shared_verdict_signal_path[path_length] = '\0';
int32_t dbg_id = worker_id;
int32_t dbg_size = workers_amount_to_send;
write_dbg_if_needed(
DBG_LEVEL_DEBUG,
"Successfully registered on client. socket: %d, instance ID: %d, instances amount: %d, received path: %s",
registration_socket,
dbg_id,
dbg_size,
shared_verdict_signal_path
);
close(registration_socket);
registration_socket = -1;
return NGX_OK;
}
const char *
get_unique_id()
{
return unique_id;
}
static ngx_int_t
set_unique_id()
{
int is_container_env = 0;
static const int max_container_id_len = 12;
const char *container_id_file_path = "/proc/self/cgroup";
unsigned int unique_id_size = 0;
if (strlen(unique_id) > 0) return NGX_OK;
FILE *file = fopen(container_id_file_path, "r");
if (file == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to open %s", container_id_file_path);
return NGX_ERROR;
}
char *line = NULL;
char docker_id[max_container_id_len + 1];
memset(docker_id, '\0', max_container_id_len + 1);
size_t len = 0;
while (getline(&line, &len, file) != -1) {
char *docker_ptr = strstr(line, "docker/");
if (docker_ptr == NULL) continue;
is_container_env = 1;
docker_ptr += strlen("docker/");
snprintf(docker_id, max_container_id_len + 1, "%s", docker_ptr);
break;
}
free(line);
fclose(file);
long unsigned int ngx_worker_id = ngx_worker + 1;
if (is_container_env) {
unique_id_size += strlen(docker_id) + 1 + get_number_of_digits(ngx_worker_id) + 1;
snprintf(unique_id, unique_id_size, "%s_%lu", docker_id, ngx_worker_id);
} else {
unique_id_size += get_number_of_digits(ngx_worker_id) + 1;
snprintf(unique_id, unique_id_size, "%lu", ngx_worker_id);
}
write_dbg(DBG_LEVEL_INFO, "Successfully set attachment's unique_id: '%s'", unique_id);
return NGX_OK;
}
ngx_int_t
ngx_cp_attachment_init_process(ngx_http_request_t *request)
{
ngx_pool_t *memory_pool;
nginx_user_id = getuid();
nginx_group_id = getgid();
static int need_registration = 1;
num_of_connection_attempts++;
// Best-effort attempt to read the configuration before we start.
init_general_config(SHARED_ATTACHMENT_CONF_PATH);
// Initalizing the various elements of the system (if needed):
// 1. Get the unique identifier.
// 2. Register with the attachment manager.
// 3. Signal to the service to start communication.
// 4. Make sure that the configuration is up-to-date.
if (access(SHARED_REGISTRATION_SIGNAL_PATH, F_OK) != 0) {
write_dbg(DBG_LEVEL_TRACE, "Attachment registration manager is turned off");
return NGX_ABORT;
}
if (strncmp(unique_id, "", 1) == 0) {
write_dbg(DBG_LEVEL_DEBUG, "Setting attachment's unique id");
if (set_unique_id() == NGX_ERROR) {
write_dbg(DBG_LEVEL_INFO, "Failed to set attachment's unique_id");
return NGX_ERROR;
}
}
if (need_registration) {
if (register_to_attachments_manager(request) == NGX_ERROR) {
write_dbg_if_needed(DBG_LEVEL_INFO, "Failed to register to Attachments Manager service");
return NGX_ERROR;
}
need_registration = 0;
}
if (comm_socket < 0) {
write_dbg_if_needed(DBG_LEVEL_DEBUG, "Registering to nano service");
if (init_signaling_socket() == NGX_ERROR) {
write_dbg_if_needed(DBG_LEVEL_DEBUG, "Failed to register to the Nano Service");
need_registration = 1;
return NGX_ERROR;
}
}
if (init_general_config(SHARED_ATTACHMENT_CONF_PATH) == NGX_ERROR) {
write_dbg_if_needed(DBG_LEVEL_INFO, "Failed to initialize attachment's configuration");
return NGX_ERROR;
}
// Initalize the the communication channel with the service.
// If we encounter repeated failures - we will restart the whole communication.
static const int max_ipc_init_retry_count = 10;
static int max_retry_count = max_ipc_init_retry_count;
if (nano_service_ipc == NULL) {
write_dbg_if_needed(DBG_LEVEL_INFO, "Initializing IPC channel");
nano_service_ipc = initIpc(
unique_id,
nginx_user_id,
nginx_group_id,
0,
num_of_nginx_ipc_elements,
write_dbg_impl
);
if (nano_service_ipc == NULL) {
if (max_retry_count-- == 0) {
restart_communication(request);
max_retry_count = max_ipc_init_retry_count;
}
write_dbg_if_needed(DBG_LEVEL_INFO, "Failed to initialize IPC with nano service");
return NGX_ERROR;
}
}
max_retry_count = max_ipc_init_retry_count;
// Initialize internal resources.
if (!is_static_resources_table_initialized()) {
memory_pool = get_memory_pool();
if (memory_pool == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Cannot initialize static resources. No memory pool has been allocated.");
return NGX_ERROR;
}
write_dbg(DBG_LEVEL_DEBUG, "Initializing static resources");
if (init_static_resources(memory_pool) != NGX_OK) {
write_dbg(DBG_LEVEL_WARNING, "Failed to initialize static resources");
return NGX_ERROR;
}
}
if (!is_compression_debug_printing_initialized()) {
write_dbg(DBG_LEVEL_DEBUG, "Initializing compression debug message printing");
initialize_compression_debug_printing();
}
// 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);
dbg_is_needed = 1;
num_of_connection_attempts = 0;
return NGX_OK;
}
int
restart_communication(ngx_http_request_t *request)
{
write_dbg_if_needed(DBG_LEVEL_TRACE, "Restarting communication channels with nano service");
if (nano_service_ipc != NULL) {
destroyIpc(nano_service_ipc, 0);
nano_service_ipc = NULL;
}
if (init_signaling_socket() == NGX_ERROR) {
if (register_to_attachments_manager(request) == NGX_ERROR) {
write_dbg_if_needed(DBG_LEVEL_DEBUG, "Failed to register to Attachments Manager service");
return -1;
}
if (init_signaling_socket() == NGX_ERROR) return -1;
}
nano_service_ipc = initIpc(unique_id, nginx_user_id, nginx_group_id, 0, num_of_nginx_ipc_elements, write_dbg_impl);
if (nano_service_ipc == NULL) return -2;
return 0;
}
void
disconnect_communication()
{
if (comm_socket > 0) {
close(comm_socket);
comm_socket = -1;
}
if (nano_service_ipc != NULL) {
destroyIpc(nano_service_ipc, 0);
nano_service_ipc = NULL;
}
}
ngx_int_t
handle_shmem_corruption()
{
if (nano_service_ipc == NULL) return NGX_OK;
if (isCorruptedShmem(nano_service_ipc, 0)) {
write_dbg(DBG_LEVEL_WARNING, "Shared memory is corrupted! restarting communication");
disconnect_communication();
return NGX_ERROR;
}
return NGX_OK;
}
ngx_int_t
isIpcReady()
{
return nano_service_ipc != NULL && comm_socket > 0;
}

View File

@@ -0,0 +1,96 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_initializer.h
#ifndef __NGX_CP_INITIALIZER_H__
#define __NGX_CP_INITIALIZER_H__
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
typedef enum ngx_cp_comm_direction {
READ_FROM_SOCKET,
WRITE_TO_SOCKET
} ngx_cp_comm_direction_e; ///< Indicate whether communication exchange is to read or to write from a socket.
///
/// @brief Initialize all the attachments resources and communication channels.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
/// - #NGX_ABORT
///
ngx_int_t ngx_cp_attachment_init_process();
///
/// @brief Preforms send\receive information to\from the service via a socket.
/// @param[in] socket The file descriptor of the socket to work with.
/// @param[in, out] data An allocated memory which data will be read from (if sending) or written to (if recieving).
/// @param[in] size The number of bytes in the allocated memoty.
/// @param[in] direction Sets whether we read from the socket or write to it:
/// - #READ_FROM_SOCKET
/// - #WRITE_TO_SOCKET
/// @param[in] remaining_timeout Points to the maximal time point the function is allowed to reach.
/// @return int - positive if successful, other values indicate an error.
///
int exchange_communication_data_with_service(
int socket,
void *data,
uint32_t size,
ngx_cp_comm_direction_e direction,
struct timeval *remaining_timeout);
///
/// @brief Get an identifier that distinguish between different instances running on the same machine.
/// @return A null-terminated string that is unique to this instance.
///
const char * get_unique_id();
///
/// @brief Get an identifier for the current docker instance.
/// @param[out] _docker_id Will point to the null terminated string (which shouldn't be freed) indentifier
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
ngx_int_t get_docker_id(char **_docker_id);
///
/// @brief Closes any existing communication to the service and tries to open a new one.
/// @param[in] request Points to an HTTP request, needed to get the number of workers.
/// @returns int - 0 on success, negative number on failure.
///
int restart_communication(ngx_http_request_t *request);
///
/// @brief Checks that the shared memory with the service isn't corrupted, disconnect if it is.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
ngx_int_t handle_shmem_corruption();
///
/// @brief Closes all the communication channels with the service.
///
void disconnect_communication();
///
/// @brief Checks if communication with the service is up and running.
/// @returns ngx_int_t - 1 if communication is active, otherwise 0.
///
ngx_int_t isIpcReady();
#endif // __NGX_CP_INITIALIZER_H__

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,201 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_io.h
#ifndef __NGX_CP_IO_H__
#define __NGX_CP_IO_H__
#include <ngx_config.h>
#include <ngx_http.h>
#include <unistd.h>
#include "shmem_ipc.h"
#include "nginx_attachment_common.h"
#include "ngx_cp_custom_response.h"
#include "ngx_cp_hooks.h"
#define INSPECTION_IRRELEVANT NGX_DECLINED
extern SharedMemoryIPC *nano_service_ipc; ///< Nano service's IPC.
extern int comm_socket; ///< Communication socket.
///
/// @brief Receives nano service's response.
/// @details The function awaits for the expected_replies of replies from the nano service.
/// The recieved verdict is saved onto the verdict argument and depends on the reply
/// one of the ngx_int_t returns.
/// @param[in, out] expected_replies Amount of expected replies.
/// @param[in, out] verdict Value to save the verdict onto:
/// - #TRAFFIC_VERDICT_INSPECT
/// - #TRAFFIC_VERDICT_INJECT
/// - #TRAFFIC_VERDICT_DROP
/// - #TRAFFIC_VERDICT_ACCEPT
/// - #TRAFFIC_VERDICT_IRRELEVANT
/// - #TRAFFIC_VERDICT_RECONF
/// @param[in] cur_session_id Session's Id.
/// @param[in, out] request NGINX request.
/// @param[in] modification_list
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_HTTP_FORBIDDEN
/// - #NGX_ERROR
///
ngx_int_t
ngx_http_cp_reply_receiver(
ngx_int_t *expected_replies,
ngx_http_cp_verdict_e *verdict,
uint32_t cur_session_id,
ngx_http_request_t *request,
ngx_http_cp_modification_list **modification_list
);
///
/// @brief Sends meta data to the nano service.
/// @param[in, out] request NGINX request.
/// @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.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
ngx_int_t
ngx_http_cp_meta_data_sender(
ngx_http_request_t *request,
uint32_t cur_request_id,
ngx_uint_t *num_messages_sent
);
///
/// @brief Sends end of a transaction to the nano service.
/// @param[in] end_transaction_type Sets the transaction type, can be of the values:
/// - #REQUEST_END
/// - #RESPONSE_END
/// @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.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
ngx_int_t
ngx_http_cp_end_transaction_sender(
ngx_http_chunk_type_e end_transaction_type,
uint32_t cur_request_id,
ngx_uint_t *num_messages_sent
);
///
/// @brief Sends response code to the nano service.
/// @param[in] response_code response code to send.
/// @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.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
ngx_int_t ngx_http_cp_res_code_sender(
uint16_t response_code,
uint32_t cur_request_id,
ngx_uint_t *num_messages_sent
);
///
/// @brief Sends content length to the nano service.
/// @param[in] content_length_n content length to send.
/// @param[in] cur_req_id Request session's Id.
/// @param[in, out] num_messages_sent Number of messages sent will be saved onto this parameter.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
ngx_int_t
ngx_http_cp_content_length_sender(
uint64_t content_length_n,
uint32_t cur_req_id,
ngx_uint_t *num_messages_sent
);
///
/// @brief Sends request/response headers to the nano service.
/// @param[in] headers Headers to be sent.
/// @param[in, out] header_type Sets the header type, can be of the values:
/// - #REQUEST_HEADER
/// - #RESPONSE_HEADER
/// @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.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
ngx_int_t
ngx_http_cp_header_sender(
ngx_list_part_t *headers,
ngx_http_chunk_type_e header_type,
uint32_t cur_request_id,
ngx_uint_t *num_messages_sent
);
///
/// @brief Sends request/response bodies to the nano service.
/// @param[in] input NGX chain.
/// @param[in] body_type Sets the body type, can be of the values:
/// - #REQUEST_BODY
/// - #RESPONSE_BODY
/// @param[in, out] session_data Session's data.
/// @param[in, out] is_last_part If the last part will be saved onto this parameter.
/// @param[in, out] num_messages_sent Number of messages sent will be saved onto this parameter.
/// @param[in, out] next_elem_to_inspect Next NGX chain to inspect.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
ngx_int_t
ngx_http_cp_body_sender(
ngx_chain_t *input,
ngx_http_chunk_type_e body_type,
ngx_http_cp_session_data *session_data,
ngx_int_t *is_last_part,
ngx_uint_t *num_messages_sent,
ngx_chain_t **next_elem_to_inspect
);
///
/// @brief Checks if reconf is needed and reconfigs if necessary.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
ngx_int_t ngx_http_cp_is_reconf_needed();
///
/// @brief Sends metric data to the server.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
ngx_int_t ngx_http_cp_metric_data_sender();
///
/// @brief Updates session related metric fields.
/// @param[in] session_start_time Session's start time.
/// @param[in] req_proccesing_time Session's request processing time.
/// @param[in] res_proccesing_time Session's response processing time.
///
void ngx_http_cp_report_time_metrics(
clock_t session_start_time,
double req_proccesing_time,
double res_proccesing_time
);
#endif // __NGX_CP_IO_H__

View File

@@ -0,0 +1,121 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_metric.c
#include <ngx_config.h>
#include "ngx_cp_metric.h"
#include "ngx_cp_utils.h"
uint64_t metric_data[METRIC_TYPES_COUNT];
uint64_t metric_average_data_divisor[METRIC_TYPES_COUNT];
void
reset_metric_data()
{
int i;
for (i = 0 ; i < METRIC_TYPES_COUNT ; i++) {
metric_data[i] = 0;
metric_average_data_divisor[i] = 0;
}
}
///
/// @brief Increment the provided metric type by the value.
/// @param[in] metric_type Metric type.
/// @param[in] value Value to increment the metric type.
///
static void
updateCounterMetricField(ngx_http_plugin_metric_type_e metric_type, uint64_t value)
{
metric_data[metric_type] += value;
}
///
/// @brief Update the average metric field.
/// @param[in] metric_type Metric type.
/// @param[in] value Value to add to the average metric.
///
static void
updateAverageMetricField(ngx_http_plugin_metric_type_e metric_type, uint64_t value)
{
metric_data[metric_type] =
(((metric_data[metric_type] * metric_average_data_divisor[metric_type]) + value) / (metric_average_data_divisor[metric_type] + 1));
metric_average_data_divisor[metric_type] += 1;
}
///
/// @brief Sets the value as metric if it is higher than the current metric's value.
/// @param[in] metric_type Metric type to set the value in.
/// @param[in] value Value to set.
///
static void
updateMaxMetricField(ngx_http_plugin_metric_type_e metric_type, uint64_t value)
{
if (metric_data[metric_type] < value) metric_data[metric_type] = value;
}
///
/// @brief Sets the value as metric if it is lower than the current metric's value.
/// @param[in] metric_type Metric type to set the value in.
/// @param[in] value Value to set.
///
static void
updateMinMetricField(ngx_http_plugin_metric_type_e metric_type, uint64_t value)
{
if (metric_data[metric_type] == 0) {
metric_data[metric_type] = value;
} else if (metric_data[metric_type] > value) {
metric_data[metric_type] = value;
}
}
void
updateMetricField(ngx_http_plugin_metric_type_e metric_type, uint64_t value)
{
switch (metric_type) {
case CPU_USAGE:
case AVERAGE_VM_MEMORY_USAGE:
case AVERAGE_RSS_MEMORY_USAGE:
case AVERAGE_REQ_BODY_SIZE_UPON_TIMEOUT:
case AVERAGE_RES_BODY_SIZE_UPON_TIMEOUT:
case AVERAGE_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT:
case AVERAGE_REQ_PPROCESSING_TIME_UNTIL_VERDICT:
case AVERAGE_RES_PPROCESSING_TIME_UNTIL_VERDICT: {
if (value != 0) updateAverageMetricField(metric_type, value);
break;
}
case MAX_VM_MEMORY_USAGE:
case MAX_RSS_MEMORY_USAGE:
case MAX_REQ_BODY_SIZE_UPON_TIMEOUT:
case MAX_RES_BODY_SIZE_UPON_TIMEOUT:
case MAX_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT:
case MAX_REQ_PPROCESSING_TIME_UNTIL_VERDICT:
case MAX_RES_PPROCESSING_TIME_UNTIL_VERDICT: {
if (value != 0) updateMaxMetricField(metric_type, value);
break;
}
case MIN_REQ_BODY_SIZE_UPON_TIMEOUT:
case MIN_RES_BODY_SIZE_UPON_TIMEOUT:
case MIN_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT:
case MIN_REQ_PPROCESSING_TIME_UNTIL_VERDICT:
case MIN_RES_PPROCESSING_TIME_UNTIL_VERDICT: {
if (value != 0) updateMinMetricField(metric_type, value);
break;
}
default:
updateCounterMetricField(metric_type, value);
}
}

View File

@@ -0,0 +1,35 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_metric.h
#ifndef __NGX_CP_METRIC_H__
#define __NGX_CP_METRIC_H__
#include <nginx_attachment_common.h>
#include <ngx_config.h>
#include <ngx_core.h>
///
/// @brief Depending on the metric type, set the provided value in the metric.
/// @param[in] metric_type Metric type to update.
/// @param[in] value Value to set.
///
void updateMetricField(ngx_http_plugin_metric_type_e metric_type, uint64_t value);
///
/// @brief Goes over all the metrics and resets them to 0.
///
void reset_metric_data();
#endif // __NGX_CP_METRIC_H__

View File

@@ -0,0 +1,546 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ngx_cp_static_content.h"
#include <ngx_hash.h>
#include "ngx_cp_utils.h"
#include "ngx_cp_custom_response.h"
ngx_hash_t static_resources_hash_table; ///< Holds all the static resources data.
ngx_int_t is_static_resources_table_initialized_var = 0; ///< A flag if static resources hash table was initialized.
static const ngx_int_t not_a_static_resource = NOT_A_STATIC_RESOURCE; ///< Not a static resource variable.
static const unsigned int static_resource_res_headers_allow_ranges = 1; ///< Static resource result allow ranges.
static const unsigned int static_resource_res_headers_keepalive = 1; ///< Static resource result headers keep alive configuration.
static const ngx_int_t static_resource_res_headers_response_code = NGX_HTTP_OK; ///< Static resource good result response.
static const ngx_int_t default_max_hash_table_size = 512; ///< Maximum hash table size configuration.
static const ngx_int_t default_hash_table_bucket_size = 64;
static const ngx_int_t default_part_items_num = 100; ///< Default number of elements to be allocated for a static resources list during static resources initialization.
static const ngx_int_t initial_hash_table_data_value = 1; ///< Initial value of initiated table data.
static ngx_hash_init_t static_resources_hash_initializer; ///< NGINX Hash initialization settings.
///
/// @brief Get the static resources into the static_resources and return the number of resources.
/// @details Given a memory poll and static sources list. Attempts to open the static resource directory.
/// for each static data in the directory, allocate a new element in the static_resources list and load the
/// data into the new element.
/// @param[in, out] static_resources NGINX list of the static allocated resources.
/// @param[in, out] memory_pool NGINX pool used to allocate data into.
/// @return ngx_int_t - The number of allocated static resources.
///
static ngx_uint_t
get_static_resources(ngx_list_t *static_resources, ngx_pool_t *memory_pool)
{
size_t num_static_resources = 0;
struct dirent *current_entry = NULL;
ngx_str_t *current_resource = NULL;
const char *static_resources_path = get_static_resources_path();
DIR *static_resources_directory = opendir(static_resources_path);
if (static_resources_directory == NULL) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to open the static resources directory. Path: %s, error: %s",
static_resources_path,
strerror(errno)
);
return -1;
}
write_dbg(
DBG_LEVEL_TRACE,
"Successfully opened the static resources' directory: %s",
static_resources_path
);
while ((current_entry = readdir (static_resources_directory)) != NULL) {
// Iterates over the files in the static resources directory.
// Allocates a new element to the list and initialize it given the resources data.
if (strcmp(current_entry->d_name, ".") == 0 || strcmp(current_entry->d_name, "..") == 0) continue;
current_resource = ngx_list_push(static_resources);
if (current_resource == NULL) {
write_dbg(
DBG_LEVEL_ERROR,
"Failed to allocate memory for a static resource path. Path: %s",
static_resources_path
);
return -1;
}
write_dbg(DBG_LEVEL_TRACE, "Found static resource: %s", current_entry->d_name);
// Load the read data from the file onto the current resource element.
current_resource->len = strlen(current_entry->d_name);
current_resource->data = ngx_palloc(memory_pool, current_resource->len + 1);
ngx_memcpy(current_resource->data, current_entry->d_name, current_resource->len);
current_resource->data[current_resource->len] = '\0';
num_static_resources++;
}
closedir(static_resources_directory);
return num_static_resources;
}
///
/// @brief Initiates static resources hash table with the provided data in static_resources.
/// @details Takes the provided variables and initiates the static hash table that
/// is used throughout the attachment.
/// @param[in, out] static_resources NGINX list - data to be initiated
/// @param[in] num_static_resources The number of provided resources in static_resources.
/// @param[in, out] memory_pool NGINX pool used to allocate data into.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
static ngx_int_t
init_static_web_resources_hash_table(
ngx_list_t *static_resources,
const size_t num_static_resources,
ngx_pool_t *memory_pool
)
{
ngx_int_t init_hash_table_result;
write_dbg(
DBG_LEVEL_TRACE,
"Received %u static web resources. Initializing the static resources table.",
num_static_resources
);
// initiate the static hash table with the provided data in static_resources.
init_hash_table_result = init_hash_table(
memory_pool,
&static_resources_hash_initializer,
&static_resources_hash_table,
"static resources",
default_max_hash_table_size,
default_hash_table_bucket_size,
static_resources,
&initial_hash_table_data_value,
sizeof(initial_hash_table_data_value)
);
if (init_hash_table_result != NGX_OK) {
write_dbg(DBG_LEVEL_WARNING, "Failed to initialize static resources table");
free_list_from_pool(memory_pool, static_resources);
return NGX_ERROR;
}
is_static_resources_table_initialized_var = 1;
write_dbg(DBG_LEVEL_TRACE, "Successfully initialized the static resources table");
return NGX_OK;
}
ngx_int_t
init_static_resources(ngx_pool_t *memory_pool)
{
size_t num_static_resources;
ngx_list_t *static_resources = ngx_list_create(memory_pool, default_part_items_num, sizeof(ngx_str_t));
if (static_resources == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to allocate the list of static resource paths");
return NGX_ERROR;
}
// Read the static data saved into static_resources.
num_static_resources = get_static_resources(static_resources, memory_pool);
if (num_static_resources == (size_t)-1) {
free_list_from_pool(memory_pool, static_resources);
return NGX_ERROR;
}
if (num_static_resources == 0) {
free_list_from_pool(memory_pool, static_resources);
return NGX_OK;
}
// Initiate the static_resources_hash_table with static_resources data.
if (init_static_web_resources_hash_table(static_resources, num_static_resources, memory_pool) != NGX_OK) {
free_list_from_pool(memory_pool, static_resources);
return NGX_ERROR;
}
free_list_from_pool(memory_pool, static_resources);
return NGX_OK;
}
ngx_int_t
is_static_resources_table_initialized(void)
{
return is_static_resources_table_initialized_var;
}
///
/// @brief
/// @param[in] null_terminated_uri Null terminated uri that holds the resource name.
/// @param[in, out] static_resource_name A variable to save data and len of the extracted resource name.
/// @returns ngx_int_t
/// - #1 Successed in getting a static resource name.
/// - #0 Failed to get static resource name.
///
ngx_int_t
get_static_resource_name(const ngx_str_t *null_terminated_uri, ngx_str_t *static_resource_name)
{
size_t uri_prefix_length;
u_char *last_uri_separator;
// Skip past last "/"
last_uri_separator = reverse_strnchr(null_terminated_uri->data, '/', null_terminated_uri->len);
if (last_uri_separator == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Invalid URI in HTTP request, URI: %s", null_terminated_uri->data);
return 0;
}
uri_prefix_length = last_uri_separator - null_terminated_uri->data + 1;
static_resource_name->data = last_uri_separator + 1;
static_resource_name->len = null_terminated_uri->len - uri_prefix_length;
return 1;
}
///
/// @brief Validates that the provided static resource name is a valid.
/// @details The function checks if the static resources table has been properly intiliazed.
/// If it has, it initates a hash key and searchs for it in the static resources hash table.
/// If it finds it, returns 1, in any other case returns 0.
/// @param[in, out] static_resource_name NGINX string - resource name to be checked to be valid.
/// @returns ngx_int_t
/// - #1 Successed in getting a static resource.
/// - #0 Failed to get static resource.
///
static ngx_int_t
is_static_resource_request(ngx_str_t *static_resource_name)
{
char *data;
ngx_uint_t key;
if (!is_static_resources_table_initialized()) {
write_dbg(
DBG_LEVEL_WARNING,
"Cannot determine whether request is for a static resource: static resources' table is not initialized"
);
return 0;
}
write_dbg(
DBG_LEVEL_TRACE,
"Checking whether requested resource %s (name length: %u) is a static resource",
static_resource_name->data, static_resource_name->len
);
key = ngx_hash_key(static_resource_name->data, static_resource_name->len);
data = ngx_hash_find(&static_resources_hash_table, key, static_resource_name->data, static_resource_name->len);
if (data == NULL) {
write_dbg(DBG_LEVEL_TRACE, "Requested resource %s is not a static resource", static_resource_name->data);
return 0;
}
return 1;
}
static void
set_location_conf_root(
ngx_http_core_loc_conf_t *location_conf,
const ngx_str_t *root_name,
ngx_array_t *root_lengths,
ngx_array_t *root_values
)
{
location_conf->root.len = root_name->len;
location_conf->root.data = root_name->data;
location_conf->root_lengths = root_lengths;
location_conf->root_values = root_values;
}
///
/// @brief Sends a static content response header.
/// @param[in, out] request
/// @param[in] static_resource_size
/// @param[in] static_resource_last_modified_time
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
static ngx_int_t
send_static_content_response_headers(
ngx_http_request_t *request,
const off_t static_resource_size,
const time_t static_resource_last_modified_time
)
{
ngx_int_t send_headers_res = ngx_http_cp_response_headers_sender(
request,
static_resource_res_headers_response_code,
static_resource_size,
static_resource_last_modified_time,
static_resource_res_headers_allow_ranges,
static_resource_res_headers_keepalive
);
if (send_headers_res != NGX_OK) {
write_dbg(DBG_LEVEL_WARNING, "Failed to send headers for static content response");
return NGX_ERROR;
}
return NGX_OK;
}
///
/// @brief Open a cached file at the location configuration.
/// @param[in] location_conf Location configuration regarding the file to be handled.
/// @param[in, out] open_files_cache A cache of opened files.
/// @param[in, out] file_path File path to the file to be read.
/// @param[in, out] open_file_info Information regarding the file to be read.
/// @param[in, out] memory_pool NGINX pool.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
static ngx_int_t
open_cached_file(
ngx_http_core_loc_conf_t *location_conf,
ngx_open_file_cache_t *open_files_cache,
ngx_str_t *file_path,
ngx_open_file_info_t *open_file_info,
ngx_pool_t *memory_pool
)
{
open_file_info->read_ahead = location_conf->read_ahead;
open_file_info->directio = location_conf->directio;
open_file_info->valid = location_conf->open_file_cache_valid;
open_file_info->min_uses = location_conf->open_file_cache_min_uses;
open_file_info->errors = location_conf->open_file_cache_errors;
open_file_info->events = location_conf->open_file_cache_events;
return ngx_open_cached_file(open_files_cache, file_path, open_file_info, memory_pool);
}
///
/// @brief Sends the static resource.
/// @param[in, out] request NGINX request static resource request to be sent.
/// @param[in, out] static_resource_name A path to the static resource.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
static ngx_int_t
send_static_resource(ngx_http_request_t *request, ngx_str_t *static_resource_name)
{
const char *static_resources_root_path = get_static_resources_path();
ngx_int_t open_file_res;
ngx_int_t send_headers_res;
ngx_open_file_info_t open_file_info;
ngx_str_t static_resource_path;
ngx_http_core_loc_conf_t *core_module_loc_conf = ngx_http_get_module_loc_conf(request, ngx_http_core_module);
ngx_str_t old_root = core_module_loc_conf->root;
ngx_array_t *old_root_lengths = core_module_loc_conf->root_lengths;
ngx_array_t *old_root_values = core_module_loc_conf->root_values;
ngx_str_t old_uri = request->uri;
ngx_str_t new_root = { strlen(static_resources_root_path), (u_char *)static_resources_root_path };
set_location_conf_root(core_module_loc_conf, &new_root, NULL, NULL);
request->uri = *static_resource_name;
// Map static_resource_path URI to file path.
if (ngx_http_map_uri_to_path(request, &static_resource_path, &new_root.len, 0) == NULL) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to map static resource's URI to file path. URI: %.*s",
request->uri.len,
request->uri.data
);
return NGX_ERROR;
}
static_resource_path.len = new_root.len + static_resource_name->len;
ngx_memzero(&open_file_info, sizeof(ngx_open_file_info_t));
// Open static resource's file.
open_file_res = open_cached_file(
core_module_loc_conf,
core_module_loc_conf->open_file_cache,
&static_resource_path,
&open_file_info,
request->pool
);
if (open_file_res != NGX_OK) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to open static resource's file. File path: %.*s",
static_resource_path.len,
static_resource_path.data
);
return NGX_ERROR;
}
// Send static content response headers.
send_headers_res = send_static_content_response_headers(request, open_file_info.size, open_file_info.mtime);
if (send_headers_res != NGX_OK) return send_headers_res;
if (request != request->main && open_file_info.size == 0) {
write_dbg(DBG_LEVEL_DEBUG, "Tried to send empty file, sent only headers");
return NGX_OK;
}
set_location_conf_root(core_module_loc_conf, &old_root, old_root_lengths, old_root_values);
request->uri = old_uri;
return ngx_http_cp_file_response_sender(
request,
&static_resource_path,
&open_file_info,
request == request->main,
request->connection->log,
request->pool
);
}
///
/// @brief
/// @param[in, out] request NGINX request.
/// @param[in, out] null_terminated_uri Null terminated Uri.
/// @param[in, out] static_resource_name Static resource name.
/// @param[in] handle_static_resource_result Results in handling the static resource request.
///
void
finalize_static_resource_response(
ngx_http_request_t *request,
ngx_str_t *null_terminated_uri,
ngx_str_t *static_resource_name,
const ngx_int_t handle_static_resource_result
)
{
ngx_int_t finalize_request_response_code;
// Frees null_terminated_uri data.
if (null_terminated_uri->data != NULL) {
ngx_pfree(request->pool, null_terminated_uri->data);
null_terminated_uri->data = NULL;
}
if (handle_static_resource_result == not_a_static_resource) {
write_dbg(
DBG_LEVEL_DEBUG,
"Request is not for a static resource. Request's URI: %.*s",
request->uri.len,
request->uri.data
);
return;
}
// Debug printing of request result response.
switch (handle_static_resource_result) {
case NGX_OK:
case NGX_DONE: {
write_dbg(
DBG_LEVEL_DEBUG,
"Successfully sent requested static resource: %.*s",
static_resource_name->len,
static_resource_name->data
);
break;
}
case NGX_AGAIN: {
write_dbg(
DBG_LEVEL_DEBUG,
"Failed to finish sending requested static resource, retrying. Static resource: %.*s",
static_resource_name->len,
static_resource_name->data
);
break;
}
default: {
write_dbg(
DBG_LEVEL_WARNING, "Failed to send requested static resource: %.*s",
static_resource_name->len,
static_resource_name->data
);
break;
}
}
// Finalize response of the static response request.
if (
handle_static_resource_result == NGX_OK ||
handle_static_resource_result == NGX_DONE ||
handle_static_resource_result == NGX_AGAIN
) {
finalize_request_response_code = handle_static_resource_result;
} else {
finalize_request_response_code = NGX_HTTP_FORBIDDEN;
}
ngx_http_finalize_request(request, finalize_request_response_code);
}
ngx_int_t
handle_static_resource_request(uint32_t session_id, ngx_http_cp_verdict_e *verdict, ngx_http_request_t *request)
{
ngx_str_t null_terminated_uri;
ngx_str_t static_resource_name;
ngx_int_t send_static_resource_res;
write_dbg(
DBG_LEVEL_TRACE,
"Trying to serve requested resource as static content. URI: %.*s",
request->uri.len,
request->uri.data
);
if (duplicate_ngx_string(&null_terminated_uri, &request->uri, request->pool) != NGX_OK) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to create a null terminated duplicate of URI. URI: %.*s",
request->uri.len,
request->uri.data
);
finalize_static_resource_response(request, &null_terminated_uri, &static_resource_name, NGX_ERROR);
return NGX_ERROR;
}
// Get static resource name in static_resource_name.
if (!get_static_resource_name(&null_terminated_uri, &static_resource_name)) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to create a null terminated duplicate of URI. URI: %.*s",
request->uri.len,
request->uri.data
);
finalize_static_resource_response(request, &null_terminated_uri, &static_resource_name, NGX_ERROR);
return NGX_ERROR;
}
// Validates that static_resource_name is a valid request.
if (!is_static_resource_request(&static_resource_name)) {
finalize_static_resource_response(
request,
&null_terminated_uri,
&static_resource_name,
not_a_static_resource
);
return not_a_static_resource;
}
*verdict = TRAFFIC_VERDICT_IRRELEVANT;
write_dbg(
DBG_LEVEL_DEBUG,
"Request is for a static resource, inspection is not needed (session ID = %d)",
session_id
);
// Sends the static request.
send_static_resource_res = send_static_resource(request, &static_resource_name);
finalize_static_resource_response(request, &null_terminated_uri, &static_resource_name, send_static_resource_res);
return NGX_DONE;
}

View File

@@ -0,0 +1,62 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_static_content.h
#ifndef __NGX_CP_STATIC_CONTENT_H__
#define __NGX_CP_STATIC_CONTENT_H__
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include "nginx_attachment_common.h"
#define NOT_A_STATIC_RESOURCE NGX_DECLINED
///
/// @brief Initiates the static resources hash table.
/// @details Read the data from the static resources directory, load it into static_resources_hash_table.
/// @param[in, out] memory_pool NGINX pool used to allocate data into.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
ngx_int_t init_static_resources(ngx_pool_t *memory_pool);
///
/// @brief Returns if static resources hash has been initialized.
/// @returns ngx_int_t
/// - #1 Is intialized.
/// - #0 Is not intialized.
///
ngx_int_t is_static_resources_table_initialized(void);
///
/// @brief Handles a resource request.
/// @details Recieves a static resource name, get the data out of the static resource hash table and sends it.
/// @param[in] session_id Session ID, used for debug message.
/// @param[in, out] verdict Verdict to be returned back to the callee.
/// - #TRAFFIC_VERDICT_IRRELEVANT If the function returns a static resource.
/// @param[in, out] request NGINX request.
/// @return ngx_int_t
/// - #NOT_A_STATIC_RESOURCE
/// - #NGX_DONE
///
ngx_int_t handle_static_resource_request(
uint32_t session_id,
ngx_http_cp_verdict_e *verdict,
ngx_http_request_t *request
);
#endif // __NGX_CP_STATIC_CONTENT_H__

View File

@@ -0,0 +1,133 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_thread.c
#include "ngx_cp_thread.h"
#include "ngx_cp_utils.h"
#include "ngx_cp_hook_threads.h"
#include "ngx_cp_failing_state.h"
#include "ngx_cp_hooks.h"
#include "ngx_cp_metric.h"
#include <pthread.h>
#include <sched.h>
#include <errno.h>
#include <unistd.h>
static int success_count = 0;
///
/// @brief runs the provided routine with the arguments in a non thread and without a timeout.
/// @param[in, out] thread_func A pointer to the provided routine to run in a thread.
/// @param[in, out] arg Routine's arguments.
/// @param[in, out] func_name Called thread timeout.
/// @returns 1
///
int
ngx_cp_run_without_thread_timeout(CpThreadRoutine thread_func, void *arg, char *func_name)
{
write_dbg(DBG_LEVEL_TRACE, "Executing cb in blocking mode, fn=%s", func_name);
thread_func(arg);
return 1;
}
int
ngx_cp_run_in_thread_timeout(CpThreadRoutine thread_func, void *arg, int timeout_msecs, char *func_name)
{
int status = 0;
int ret = 0;
void *res = NULL;
pthread_t thread;
struct timespec ts;
struct ngx_http_cp_event_thread_ctx_t *ctx = (struct ngx_http_cp_event_thread_ctx_t*)arg;
if (inspection_mode == NO_THREAD) return ngx_cp_run_without_thread_timeout(thread_func, arg, func_name);
/// Runs the routine in a dedicated thread.
write_dbg(DBG_LEVEL_TRACE, "Executing cb in dedicated thread, fn=%s", func_name);
if (pthread_create(&thread, NULL, thread_func, arg) != 0) {
updateMetricField(THREAD_FAILURE, 1);
write_dbg(DBG_LEVEL_TRACE, "pthread_create failed with errno=%d, fn=%s", errno, func_name);
return 0;
}
if (inspection_mode == BLOCKING_THREAD) {
// Runs the function in a blocking thread.
status = pthread_join(thread, &res);
write_dbg(DBG_LEVEL_TRACE, "pthread_join returned from blocking call. status=%d, fn=%s", status, func_name);
return status == 0;
}
if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
updateMetricField(THREAD_FAILURE, 1);
write_dbg(
DBG_LEVEL_ERROR,
"clock_gettime(CLOCK_REALTIME) failed. Status: %s",
strerror(errno)
);
return 0;
}
// Convert milliseconds to timespec
long long tv_nsec = ts.tv_nsec + (timeout_msecs % 1000) * 1000000;
ts.tv_sec += timeout_msecs / 1000 + tv_nsec / 1000000000;
ts.tv_nsec = tv_nsec % 1000000000;
status = pthread_timedjoin_np(thread, NULL, &ts);
if (status != 0) {
/// Handling failed thread.
handle_inspection_failure(inspection_failure_weight, fail_mode_verdict, ctx->session_data_p);
write_dbg(
status == ETIMEDOUT ? DBG_LEVEL_DEBUG : DBG_LEVEL_WARNING,
"pthread_timejoin_np returns with %d (%s), successes so far %d, fn=%s",
status,
strerror(status),
success_count,
func_name
);
ret = pthread_cancel(thread);
write_dbg(DBG_LEVEL_DEBUG, "pthread_cancel returns with ret=%d, fn=%s", ret, func_name);
ret = pthread_join(thread, &res);
if (ret != 0) {
updateMetricField(THREAD_FAILURE, 1);
write_dbg(DBG_LEVEL_WARNING, "pthread_join failed while fail open is enabled. RET=%d, fn=%s", ret, func_name);
return ret != 0;
}
if (res == PTHREAD_CANCELED) {
updateMetricField(THREAD_TIMEOUT, 1);
write_dbg(DBG_LEVEL_DEBUG, "thread was canceled, fn=%s", func_name);
} else {
updateMetricField(THREAD_FAILURE, 1);
}
write_dbg(DBG_LEVEL_DEBUG, "pthread_join returns with ret=%d", ret);
}
else {
write_dbg(
DBG_LEVEL_TRACE,
"Successfully executed thread. successes so far=%d, fn=%s",
success_count,
func_name
);
success_count++;
}
return status == 0;
}

View File

@@ -0,0 +1,38 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_thread.h
#ifndef __NGX_CP_THREAD_H__
#define __NGX_CP_THREAD_H__
#include <stdbool.h>
typedef void *(*CpThreadRoutine)(void *); ///< Func
///
/// @brief Runs the provided routine with the provided arguments as a thread
/// @details Runs the provided routine as thread_func(args) in a thread. Depending on the inspection
/// mode runs it with a timeout.
/// This provided routine updates metrics if needed such as THREAD_TIMEOUT and THREAD_FAILURE.
/// @param[in, out] thread_func A pointer to the provided routine to run in a thread.
/// @param[in, out] arg Routine's arguments.
/// @param[in, out] timeout_msecs Called thread timeout.
/// @param[in, out] func_name The name of the provided routine.
/// @return int
/// - #0 Thread success.
/// - #1 Thread fail.
///
int ngx_cp_run_in_thread_timeout(CpThreadRoutine thread_func, void *arg, int timeout_msecs, char *);
#endif // __NGX_CP_THREAD_H__

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,429 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_utils.h
#ifndef __NGX_CP_UTILS_H__
#define __NGX_CP_UTILS_H__
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <sys/time.h>
#include <assert.h>
#include "nginx_attachment_common.h"
#ifndef __FILENAME__
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#endif
#define write_dbg(_dbg_level, fmt, ...) \
{ \
write_dbg_impl(_dbg_level, __func__, __FILENAME__, __LINE__, fmt, ##__VA_ARGS__); \
if ((_dbg_level) == DBG_LEVEL_ASSERT) assert(0); \
}
#define write_dbg_if_needed(_dbg_level, fmt, ...) \
{ \
if ((dbg_is_needed) == 0) { \
write_dbg_impl(DBG_LEVEL_TRACE, __func__, __FILENAME__, __LINE__, fmt, ##__VA_ARGS__); \
} else { \
write_dbg_impl(_dbg_level, __func__, __FILENAME__, __LINE__, fmt, ##__VA_ARGS__); \
} \
if ((_dbg_level) == DBG_LEVEL_ASSERT) assert(0); \
}
extern ngx_int_t fail_mode_verdict;
extern ngx_int_t dbg_is_needed;
extern ngx_int_t num_of_connection_attempts;
extern ngx_uint_t content_length_would_change;
extern ngx_uint_t fail_open_timeout;
extern ngx_uint_t req_max_proccessing_ms_time;
extern ngx_uint_t res_max_proccessing_ms_time;
extern ngx_uint_t registration_thread_timeout_msec;
extern ngx_uint_t req_header_thread_timeout_msec;
extern ngx_uint_t req_body_thread_timeout_msec;
extern ngx_uint_t res_header_thread_timeout_msec;
extern ngx_uint_t res_body_thread_timeout_msec;
extern ngx_http_inspection_mode_e inspection_mode;
extern ngx_uint_t num_of_nginx_ipc_elements;
///
/// @struct ngx_http_cp_list_iterator
/// @brief NGINX list iterator's data.
///
typedef struct {
ngx_list_part_t *current_part; ///< Iterator's current node.
size_t current_part_element_index; ///< Current part index.
size_t current_list_element_index; ///< Current list index.
size_t list_element_size; ///< The total size of the list that the iterator belongs to.
} ngx_http_cp_list_iterator;
///
/// @struct ngx_http_cp_sessions_per_minute_limit
/// @brief Holds sessions per minute related limitations.
///
typedef struct {
/// After more than a minute has passed, we reset all session monitoring data.
/// sessions_per_second array helps keeping track with sessions that need to be closed.
unsigned int sessions_per_second[60];
unsigned int last_minute_sessions_sum; ///< Sum of all the last minutes sessions' sum.
unsigned int last_session_time; ///< The length of the latest session.
} ngx_http_cp_sessions_per_minute_limit;
///
/// @brief Set debug context.
/// @param[in, out] client_ip Client IP to set in the debug.
/// @param[in, out] listening_ip Listening IP to set in the debug.
/// @param[in, out] uri_prefix Uri prefix to set in the debug.
/// @param[in, out] hostname Hostname to set in the debug.
/// @param[in, out] method Method to set in the debug.
/// @param[in] listening_port Listening port to set in the debug.
///
void set_dbg_by_ctx(
char *client_ip,
char *listening_ip,
char *uri_prefix,
char *hostname,
char *method,
unsigned int listening_port);
///
/// @brief Reset debug context.
///
void reset_dbg_ctx();
///
/// @brief Initiate list iterator of the provided list.
/// @param[in, out] list to get the iterator of.
/// @param[in, out] iterator the iterator to be initiated
///
void init_list_iterator(ngx_list_t *list, ngx_http_cp_list_iterator *iterator);
///
/// @brief Get list element
/// @param[in, out] iterator the iterator to be initiated.
/// @param[in] index.
/// @returns void*
/// - #A pointer to the element.
/// - #NULL if failed to get or reached the end of the list.
///
void *get_list_element(ngx_http_cp_list_iterator *iterator, const size_t index);
///
/// @brief Free a list from NGINX pool.
/// @param[in, out] memory_pool NGINX pool.
/// @param[in, out] list A pointer to a list to free.
/// @returns void*
/// - #NGX_OK.
/// - #NGX_ERROR.
///
ngx_int_t free_list_from_pool(ngx_pool_t *memory_pool, ngx_list_t *list);
///
/// @brief Initiate a provided hash table with the provided values.
/// @param[in, out] memory_pool NGINX pool.
/// @param[in, out] hash_table_initializer NGINX hash initializator.
/// @param[in, out] hash_table Hash table to init.
/// @param[in] hash_table_name Hash table name.
/// @param[in] max_size Maximum size to set the hash table.
/// @param[in] bucket_size Bucket size to set in the hash table.
/// @param[in, out] keys Keys initiate and put into the hash_table.
/// @param[in, out] initial_value Initial hash value.
/// @param[in, out] initial_value_size Initial hash value's size.
/// @return ngx_int_t
/// - #NGX_OK.
/// - #NGX_ERROR.
///
ngx_int_t init_hash_table(
ngx_pool_t *memory_pool,
ngx_hash_init_t *hash_table_initializer,
ngx_hash_t *hash_table,
char *hash_table_name,
ngx_uint_t max_size,
ngx_uint_t bucket_size,
ngx_list_t *keys,
const void *initial_value,
const size_t initial_value_size
);
///
/// @brief Copy the src buffer to the dest.
/// @param[in, out] dest NGINX chain to be copied into.
/// @param[in] src NGINX chain to come from.
///
void copy_chain_buffers(ngx_chain_t *dest, ngx_chain_t *src);
///
/// @brief Adds a new chain element before current list element.
/// @param[in, out] current_elem NGINX chain to be copied into.
/// @param[in] new_elem NGINX chain to come from.
///
void prepend_chain_elem(ngx_chain_t *current_elem, ngx_chain_t *new_elem);
///
/// @brief Adds a new chain element after current list element.
/// @param[in, out] current_elem NGINX chain to be copied into.
/// @param[in] new_elem NGINX chain to come from.
///
void append_chain_elem(ngx_chain_t *current_elem, ngx_chain_t *new_elem);
///
/// @brief Split chain element.
/// @param[in, out] elem NGINX chain to be split.
/// @param[in, out] split_index Index to split from.
/// @param[in, out] pool NGINX pool.
/// @returns ngx_int_t
/// - #NGX_OK.
/// - #NGX_ERROR.
///
ngx_int_t split_chain_elem(ngx_chain_t *elem, uint16_t split_index, ngx_pool_t *pool);
///
/// @brief Create chain element
/// @param[in, out] elem NGINX chain to be split.
/// @param[in, out] split_index Index to split from.
/// @param[in, out] pool NGINX pool.
/// @returns ngx_chain_t
/// - #A valid pointer to a ngx_chain_t.
/// - #NULL if failed to create a chain element.
///
ngx_chain_t * create_chain_elem(uint32_t data_size, char *data, ngx_pool_t *pool);
///
/// @brief Free a NGINX chain.
/// @param[in, out] pool NGINX pool that free the resources into.
/// @param[in, out] chain NGINX chain to free.
/// @returns ngx_chain_t
///
void free_chain(ngx_pool_t *pool, ngx_chain_t *chain);
///
/// @brief Get currently set response uuid.
/// @returns char * of set web_response_uuid variable.
///
const char *get_web_response_uuid(void);
///y
/// @brief Get currently set response code.
/// @returns Returns the size of web_response_uuid variable.
///
ngx_uint_t get_web_response_uuid_size(void);
///
/// @brief Sets a custom response page by modifying web_response_title/body/uuid variables.
/// @param[in] title Sets the web response title.
/// @param[in] message Sets the response body.
/// @param[in] uuid Sets the uuid of the custom response.
/// @param[in, out] 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);
///
/// @brief Sets a redirect response by modifying redirect triggers, redirect_location and web_response_uuid.
/// @param[in] location Redirect location to set to.
/// @param[in] uuid Redirection's response uuid to set.
/// @param[in, out] add_event_id_to_header Event ID to add to the response header.
///
void set_redirect_response(const ngx_str_t *location, const ngx_str_t *uuid, uint add_event_id_to_header);
///
/// @brief Get the redirect location.
/// @returns redirect_location variable.
///
u_char *get_redirect_location();
///
/// @brief Get the redirect location.
/// @returns redirect_location_size variable.
///
ngx_uint_t get_redirect_location_size();
///
/// @brief Get the redirect location.
/// @returns add_event_id variable.
///
ngx_uint_t get_add_event_id();
///
/// @brief Returns if timeout has been reached.
/// @param[in, out] timeout NGINX pool that free the resources into.
/// @returns Returns 1 it timeout reached, otherwise 0.
///
int is_timeout_reached(struct timeval *timeout);
///
/// @brief Get delta current time + delta_time_in_sec value in seconds.
/// @param[in] delta_time_in_sec Delta time to return
/// @returns timeval struct with tv_sec value of += delta_time_in_sec.
///
struct timeval get_timeout_val_sec(const int delta_time_in_sec);
///
/// @brief Get delta current time + delta_time_in_usec value in seconds.
/// @param[in] delta_time_in_usec Delta time to return
/// @returns timeval struct with tv_sec value of += delta_time_in_usec.
///
struct timeval get_timeout_val_usec(const int delta_time_in_usec);
///
/// @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.
/// @returns ngx_uint_t web_triggers_response_code variable.
///
ngx_uint_t get_response_code(void);
///
/// @brief Get currently set static resource path.
/// @returns char * get static_resources_path variable.
///
const char * get_static_resources_path(void);
///
/// @brief Get currently set memory_pool.
/// @returns ngx_pool_t * get memory_pool.
///
ngx_pool_t *get_memory_pool(void);
///
/// @brief Set memory_pool.
/// @param[in, out] new_memory_pool A new NGINX pool to be set.
///
void set_memory_pool(ngx_pool_t *new_memory_pool);
///
/// @brief Get number of digits of the provided num variable.
/// @param[in] num The number variable to get the number of digits from.
/// @returns Returns the number of digits.
///
unsigned int get_number_of_digits(int num);
///
/// @brief Get sessions per minute limit verdict.
/// @returns ngx_http_cp_verdict_e sessions_per_minute_limit_verdict variable.
///
ngx_http_cp_verdict_e get_sessions_per_minute_limit_verdict(void);
///
/// @brief Get maximum sessions per minute.
/// @returns unsigned int max_sessions_per_minute variable.
///
unsigned int get_max_sessions_per_minute(void);
///
/// @brief Get periodic session limit info..
/// @returns ngx_http_cp_sessions_per_minute_limit * Session per minute limit info.
///
ngx_http_cp_sessions_per_minute_limit *get_periodic_sessions_limit_info(void);
///
/// @brief Writing into debug implementation.
/// @param[in] _dbg_level Debug level to write into.
/// @param[in] func Function name from which the write debug was called from.
/// @param[in] file File from which the debug function was called from.
/// @param[in] line_num Line number of the write debug was called on.
/// @param[in] fmt Debug formatter.
/// @param[in] ... Extra values to write into the debug using the formatter.
///
void ngx_cdecl write_dbg_impl(int _dbg_level, const char *func, const char *file, int line_num, const char *fmt, ...);
///
/// @brief Sets a new debug level.
/// @param[in] _dbg_level New debug level to be set.
///
void set_cp_ngx_attachment_debug_level(int _dbg_level);
///
/// @brief Sets a new session ID.
/// @param[in] _dbg_level New session ID to be set.
///
void set_current_session_id(uint32_t cur_session_id);
///
/// @brief Checks if inspection required for a provided source IP.
/// @param[in] src_ip Provided source IP to be checked.
/// @returns 1 if inspection required, otherwise 0.
///
int is_inspection_required_for_source(const char *src_ip);
///
/// @brief Initiates general configuration with the provided file path.
/// @param[in] conf_path Configuration path to a file of general configuration to initiate.
/// @returns ngx_int_t
/// - #NGX_OK.
/// - #NGX_ERROR.
///
ngx_int_t init_general_config(const char *conf_path);
///
/// @brief Resets attachment configuration and loads them again from the file path in SHARED_ATTACMENT_CONF_PATH.
/// @returns ngx_int_t
/// - #NGX_OK.
/// - #NGX_ERROR.
///
ngx_int_t reset_attachment_config(void);
///
/// @brief Resets attachment configuration and loads them again from the file path in SHARED_ATTACMENT_CONF_PATH.
/// @param[in] null_terminated_string null terminated string that the original string will be copied into.
/// @param[in] original_string String to be copied into the null_terminated_string.
/// @param[in] memory_pool NGINX pool for allocation the needed buffer for null_terminated_string.
/// @returns ngx_int_t
/// - #NGX_OK.
/// - #NGX_ERROR.
///
ngx_int_t duplicate_ngx_string(ngx_str_t *null_terminated_string, ngx_str_t *original_string, ngx_pool_t *memory_pool);
///
/// @brief Reverse implementation to strnchr - finding a character in a length limited string from the end.
/// @param[in] string
/// @param[in] char_to_find
/// @param[in] string_length
/// @returns u_char* pointer to the first u_char that was found.
///
u_char *reverse_strnchr(u_char *string, const u_char char_to_find, const size_t string_length);
///
/// @brief Get keep alive internal milliseconds.
/// @returns ngx_msec_t keep_alive_interval_msec variable.
///
ngx_msec_t get_keep_alive_interval_msec(void);
///
/// @brief Update CPU's max, average metrics and time usage metric.
///
void set_metric_cpu_usage(void);
///
/// @brief Update memory's max, average metrics and time usage metric.
///
void set_metric_memory_usage(void);
#endif // __NGX_CP_UTILS_H__

View File

@@ -0,0 +1,503 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_http_cp_attachment_module.c
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_files.h>
#include "ngx_cp_hooks.h"
#include "ngx_cp_utils.h"
#include "ngx_cp_initializer.h"
#include "ngx_http_cp_attachment_module.h"
#include "nginx_attachment_common.h"
extern ngx_uint_t current_config_version; ///< NGINX configuration version.
typedef struct {
ngx_flag_t enable; ///< Flags if the configuration enabled.
ngx_int_t num_of_workers; ///< Number of workers.
ngx_uint_t current_loc_config_version; ///< NGINX configuration version.
} ngx_cp_attachment_conf_t;
///
/// @brief Creates NGINX cp attachment configuration.
/// @param[in, out] conf NGINX configuration.
/// @return
/// - #ngx_cp_attachment_conf_t if successed to create conf.
/// - #NULL if failed to create conf.
///
static void * ngx_cp_attachment_create_conf(ngx_conf_t *conf);
///
/// @brief Sets attachment's module configuration in NGINX configuration chain.
/// @param[in, out] configure NGINX configuration.
/// @param[in] curr ngx_cp_attachment_conf_t Pointer to the configuration.
/// @param[in] next ngx_cp_attachment_conf_t Pointer to the configuration.
/// @return NGX_CONF_OK.
///
static char * ngx_cp_attachment_merge_conf(ngx_conf_t *conf, void *curr, void *next);
///
/// @brief Sets numbers of workers configuration.
/// @param[in, out] cf NGINX configuration.
/// @param[in, out] cmd NGINX command.
/// @param[in, out] conf NGINX CP configuration.
/// @return NGX_CONF_OK
///
static char * ngx_cp_attachment_set_num_workers_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
///
/// @brief Inits NGINX CP attachment.
/// @param[in] conf NGINX configuration.
/// @returns ngx_int_t
/// - #NGX_OK
/// - #NGX_ERROR
///
static ngx_int_t ngx_cp_attachment_init(ngx_conf_t *conf);
///
/// @brief Creates NGINX cp attachment configuration.
/// @param[in, out] cf NGINX configuration.
/// @return ngx_cp_attachment_conf_t
/// - #ngx_cp_attachment_conf_t pointer if successed.
/// - #NULL if failed.
///
static void * ngx_cp_attachment_create_main_conf(ngx_conf_t *cf);
///
/// @brief Inits a NGINX CP worker.
/// @param[in] cycle NGINX cycle.
/// @returns NGX_OK.
///
static ngx_int_t ngx_cp_attachment_init_worker(ngx_cycle_t *cycle);
///
/// @brief Finis a NGINX CP worker.
/// @param[in] cycle NGINX cycle.
///
static void ngx_cp_attachment_fini_worker(ngx_cycle_t *cycle);
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_output_body_filter_pt ngx_http_next_response_body_filter; ///< NGINX output body filter.
struct sockaddr_un attachment_server; ///< NGINX CP attachments server socket.
static ngx_event_t ngx_keep_alive_event;
static ngx_int_t is_timer_active = 0;
static ngx_connection_t dumb_connection;
static ngx_msec_t keep_alive_interval_msec = 0;
static ngx_msec_t timer_interval_msec = 10000;
/// NGINX CP attachment command array.
static ngx_command_t ngx_cp_attachment_commands[] = {
{
ngx_string("cp-nano-nginx-attachment"),
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, enable),
NULL
},
{
ngx_string("cp_worker_processes"),
NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
ngx_cp_attachment_set_num_workers_conf,
NGX_HTTP_MAIN_CONF_OFFSET,
offsetof(ngx_cp_attachment_conf_t, num_of_workers),
NULL
},
ngx_null_command
};
/// NGINX CP attachment module context.
static ngx_http_module_t ngx_cp_attachment_module_ctx = {
NULL,
ngx_cp_attachment_init,
ngx_cp_attachment_create_main_conf,
NULL,
NULL,
NULL,
ngx_cp_attachment_create_conf,
ngx_cp_attachment_merge_conf
};
/// NGINX attachment module.
ngx_module_t ngx_http_cp_attachment_module = {
NGX_MODULE_V1, ///< NGINX CP module version.
&ngx_cp_attachment_module_ctx, ///< module context.
ngx_cp_attachment_commands, ///< module directives.
NGX_HTTP_MODULE, ///< module type.
NULL,
NULL,
ngx_cp_attachment_init_worker, ///< init process.
NULL,
NULL,
ngx_cp_attachment_fini_worker, ///< exit process.
NULL,
NGX_MODULE_V1_PADDING
};
int workers_amount_to_send = 0;
static void *
ngx_cp_attachment_create_main_conf(ngx_conf_t *cf)
{
ngx_cp_attachment_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_cp_attachment_conf_t));
if (conf == NULL) {
return NULL;
}
return conf;
}
static char *
ngx_cp_attachment_set_num_workers_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
(void) cmd;
ngx_str_t *value;
ngx_cp_attachment_conf_t *ccf;
ccf = (ngx_cp_attachment_conf_t *)conf;
value = cf->args->elts;
if (ngx_strcmp(value[1].data, "auto") == 0) {
ccf->num_of_workers = ngx_ncpu;
return NGX_CONF_OK;
}
ccf->num_of_workers = ngx_atoi(value[1].data, value[1].len);
if (ccf->num_of_workers == NGX_ERROR) {
ccf->num_of_workers = 0;
return "invalid value";
}
return NGX_CONF_OK;
}
static void *
ngx_cp_attachment_create_conf(ngx_conf_t *conf)
{
ngx_cp_attachment_conf_t *module_conf;
if (conf == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to create attachment module configuration: input argument is NULL");
return NULL;
}
module_conf = ngx_pcalloc(conf->pool, sizeof(ngx_cp_attachment_conf_t));
if (module_conf == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to allocate attachment module configuration");
return NULL;
}
module_conf->enable = NGX_CONF_UNSET;
module_conf->num_of_workers = 0;
module_conf->current_loc_config_version = current_config_version;
write_dbg(DBG_LEVEL_TRACE, "Successfully created attachment module configuration");
return module_conf;
}
ngx_uint_t
get_num_of_workers(ngx_http_request_t *request)
{
if (workers_amount_to_send > 0) return workers_amount_to_send;
ngx_cp_attachment_conf_t *conf = ngx_http_get_module_main_conf(request, ngx_http_cp_attachment_module);
if (conf == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to fetch the local NGINX attachment state");
return 0;
}
write_dbg(DBG_LEVEL_INFO, "num_of_workers=%d", conf->num_of_workers);
workers_amount_to_send = conf->num_of_workers;
return conf->num_of_workers;
}
ngx_int_t
is_ngx_cp_attachment_disabled(ngx_http_request_t *request)
{
ngx_cp_attachment_conf_t *conf = ngx_http_get_module_loc_conf(request, ngx_http_cp_attachment_module);
if (conf == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to fetch the local NGINX attachment state");
return NGX_CONF_UNSET;
}
if (conf->current_loc_config_version != current_config_version) {
conf->current_loc_config_version = current_config_version;
write_dbg(DBG_LEVEL_INFO, "Reconfiguring the local NGINX attachment state");
}
write_dbg(DBG_LEVEL_TRACE, "NGINX attachment state: %s", conf->enable ? "enabled" : "disabled");
return !conf->enable;
}
void
ngx_cp_set_module_loc_conf(ngx_http_request_t *request, ngx_flag_t new_state)
{
ngx_cp_attachment_conf_t *conf;
conf = ngx_http_get_module_loc_conf(request, ngx_http_cp_attachment_module);
conf->enable = new_state;
write_dbg(DBG_LEVEL_INFO, "Configuration set to be %s", conf->enable ? "enabled" : "disabled");
}
static char *
ngx_cp_attachment_merge_conf(ngx_conf_t *configure, void *curr, void *next)
{
(void)configure;
ngx_cp_attachment_conf_t *prev = curr;
ngx_cp_attachment_conf_t *conf = next;
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);
write_dbg(DBG_LEVEL_TRACE, "Successfully set attachment module configuration in nginx configuration chain");
return NGX_CONF_OK;
}
///
/// @brief Sends keep alive to the nano agent.
/// @param[in] event NGINX event.
///
static void
ngx_send_keep_alive(ngx_event_t *event)
{
char *family_name = NULL;
uint8_t family_name_size = 0;
uint8_t worker_id = 0;
int keep_alive_socket = -1;
struct timeval timeout = get_timeout_val_sec(1);
int res = 0;
int connected = 0;
static ngx_msec_t keep_alive_wait_period = 0;
if (ngx_exiting) {
is_timer_active = 0;
write_dbg(DBG_LEVEL_INFO, "exiting without re-sched of ngx_send_keep_alive . ngx_exiting=%d", ngx_exiting);
return;
}
keep_alive_interval_msec = get_keep_alive_interval_msec();
if (keep_alive_interval_msec <= 0) {
// Received invalid interval size, using default.
write_dbg(
DBG_LEVEL_WARNING,
"Received invalid interval size, using default value instead. Received value: %d, Default value: %u",
keep_alive_interval_msec,
DEFAULT_KEEP_ALIVE_INTERVAL_MSEC
);
keep_alive_interval_msec = DEFAULT_KEEP_ALIVE_INTERVAL_MSEC;
}
keep_alive_wait_period += timer_interval_msec;
if (keep_alive_wait_period < keep_alive_interval_msec) {
// Wait alive period is still within interval size.
goto keep_alive_handler_out;
}
get_docker_id(&family_name);
family_name_size = strlen(family_name);
write_dbg(DBG_LEVEL_DEBUG, "Keep alive signal. Family id: %s, UID: %u", family_name, worker_id);
if (keep_alive_socket > 0) {
close(keep_alive_socket);
keep_alive_socket = -1;
}
// Attempting to create a socket.
keep_alive_socket = socket(AF_UNIX, SOCK_STREAM, 0);
if (keep_alive_socket < 0) {
// Failed to create a socket.
write_dbg(
DBG_LEVEL_WARNING,
"Could not create socket due to error. Socket number: %d, error: %s, errno: %d",
keep_alive_socket,
strerror(errno),
errno
);
goto keep_alive_handler_out;
}
attachment_server.sun_family = AF_UNIX;
strncpy(attachment_server.sun_path, SHARED_KEEP_ALIVE_PATH, sizeof(attachment_server.sun_path) - 1);
// Attempting to connect to the nano service.
connected = connect(keep_alive_socket, (struct sockaddr *)&attachment_server, sizeof(struct sockaddr_un));
if (connected == -1) {
// Failed to connect to the nano service.
write_dbg(
DBG_LEVEL_DEBUG,
"Could not connect to nano service. Path: %s, Error: %s, Errno: %d",
attachment_server.sun_path,
strerror(errno),
errno
);
goto keep_alive_handler_out;
}
write_dbg(DBG_LEVEL_DEBUG, "connected to socket: %d. sending keep alive signals");
// Exchanging worker id with the nano service.
res = exchange_communication_data_with_service(
keep_alive_socket,
&worker_id,
sizeof(worker_id),
WRITE_TO_SOCKET,
&timeout
);
if (res <= 0) {
// Failed to send worker id
write_dbg_if_needed(DBG_LEVEL_WARNING, "Failed to send worker id");
goto keep_alive_handler_out;
}
// Exchanging family name size with the nano service.
res = exchange_communication_data_with_service(
keep_alive_socket,
&family_name_size,
sizeof(family_name_size),
WRITE_TO_SOCKET,
&timeout
);
if (res <= 0) {
// Failed to send family name size.
write_dbg_if_needed(DBG_LEVEL_WARNING, "Failed to send family name size");
goto keep_alive_handler_out;
}
if (family_name_size > 0) {
// Exchanging family name with the nano service.
res = exchange_communication_data_with_service(
keep_alive_socket,
family_name,
family_name_size,
WRITE_TO_SOCKET,
&timeout
);
if (res <= 0) {
// Failed to send family name.
write_dbg_if_needed(DBG_LEVEL_WARNING, "Failed to send family name");
goto keep_alive_handler_out;
}
}
keep_alive_wait_period = 0;
keep_alive_handler_out:
// Sends another signal.
write_dbg(DBG_LEVEL_DEBUG, "send signal again in %u sec", (timer_interval_msec / 1000));
ngx_add_timer(event, timer_interval_msec);
if (keep_alive_socket > 0) {
close(keep_alive_socket);
keep_alive_socket = -1;
}
}
static ngx_int_t
ngx_cp_attachment_init_worker(ngx_cycle_t *cycle)
{
ngx_core_conf_t *core_main_conf;
ngx_cp_attachment_conf_t *attachment_conf;
write_dbg(DBG_LEVEL_INFO, "entering init worker. ngx_exiting=%d", ngx_exiting);
attachment_conf = (ngx_cp_attachment_conf_t *)ngx_get_conf(cycle->conf_ctx, ngx_http_cp_attachment_module);
if (attachment_conf && attachment_conf->num_of_workers) workers_amount_to_send = attachment_conf->num_of_workers;
if (!workers_amount_to_send) {
core_main_conf = (ngx_core_conf_t *)ngx_get_conf(cycle->conf_ctx, ngx_core_module);
workers_amount_to_send = core_main_conf->worker_processes;
}
// Worker number 0 will always exist.
// Therefore the single instance of the timer will be created and destroyed by it.
if (ngx_worker == 0) {
write_dbg(DBG_LEVEL_INFO, "Configured workers amount: %d", workers_amount_to_send);
ngx_keep_alive_event.handler = ngx_send_keep_alive;
ngx_keep_alive_event.log = cycle->log;
ngx_keep_alive_event.data = &dumb_connection;
dumb_connection.fd = (ngx_socket_t) -1;
keep_alive_interval_msec = get_keep_alive_interval_msec();
if (keep_alive_interval_msec == 0) {
write_dbg(DBG_LEVEL_WARNING, "Invalid interval size: %u, set to default value: %d ", keep_alive_interval_msec, DEFAULT_KEEP_ALIVE_INTERVAL_MSEC);
keep_alive_interval_msec = DEFAULT_KEEP_ALIVE_INTERVAL_MSEC;
}
ngx_add_timer(&ngx_keep_alive_event, timer_interval_msec);
is_timer_active = 1;
write_dbg(
DBG_LEVEL_INFO,
"Timer successfully added. Keep alive interval: %d, timer interval: %d",
keep_alive_interval_msec,
timer_interval_msec
);
}
return NGX_OK;
}
static void
ngx_cp_attachment_fini_worker(ngx_cycle_t *cycle)
{
write_dbg(DBG_LEVEL_INFO, "entering fini worker. is_timer_active=%d, ngx_exiting=%d", is_timer_active, ngx_exiting);
// only worker number 0 (always exists since it is worker number 1 is allowed to create
// the single instance of the timer and destroy it)
if (ngx_worker != 0) return;
(void)cycle;
if (is_timer_active) ngx_del_timer(&ngx_keep_alive_event);
write_dbg(DBG_LEVEL_INFO, "Timer successfully deleted");
is_timer_active = 0;
}
static ngx_int_t
ngx_cp_attachment_init(ngx_conf_t *conf)
{
ngx_http_handler_pt *handler;
ngx_http_core_main_conf_t *http_core_main_conf;
write_dbg(DBG_LEVEL_TRACE, "Setting the memory pool used in the current context");
if (conf->pool == NULL) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to set memory pool in the current context, "
"no memory pool has been allocated for the current configuration"
);
return NGX_ERROR;
}
set_memory_pool(conf->pool);
write_dbg(
DBG_LEVEL_TRACE,
"Successfully set the memory pool in the current context. Setting attachment module's hooks."
);
http_core_main_conf = ngx_http_conf_get_module_main_conf(conf, ngx_http_core_module);
handler = ngx_array_push(&http_core_main_conf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
if (handler == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to set HTTP request headers' handler");
return NGX_ERROR;
}
*handler = ngx_http_cp_req_header_handler;
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_request_body_filter = ngx_http_top_request_body_filter;
ngx_http_top_request_body_filter = ngx_http_cp_req_body_filter;
ngx_http_next_response_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_cp_res_body_filter;
write_dbg(DBG_LEVEL_TRACE, "Successfully set attachment module's hooks");
return NGX_OK;
}

View File

@@ -0,0 +1,53 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_http_cp_attachment_module.h
#ifndef __NGX_HTTP_CP_ATTACHMENT_MODULE_H__
#define __NGX_HTTP_CP_ATTACHMENT_MODULE_H__
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
extern ngx_http_output_header_filter_pt ngx_http_next_response_header_filter; ///< NGINX response header filter.
extern ngx_http_request_body_filter_pt ngx_http_next_request_body_filter; ///< NGINX request body filter.
extern ngx_http_output_body_filter_pt ngx_http_next_response_body_filter; ///< NGINX output body filter.
extern ngx_module_t ngx_http_cp_attachment_module; ///< NGINX Module.
///
/// @brief Returns if NGINX CP attachment is disabled.
/// @param[in] request NGINX request.
/// @returns ngx_int_t
/// - #0 attachment is enabled.
/// - #1 attachment is disabled.
///
ngx_int_t is_ngx_cp_attachment_disabled(ngx_http_request_t *request);
///
/// @brief Get the number of workers.
/// @param[in] request NGINX request.
/// @returns ngx_uint_t returns number of workers.
///
ngx_uint_t get_num_of_workers(ngx_http_request_t *request);
///
/// @brief Set module config.
/// @param[in] request NGINX request.
/// @param[in] new_state NGINX flag to set.
///
void ngx_cp_set_module_loc_conf(ngx_http_request_t *request, ngx_flag_t new_state);
#endif // __NGX_HTTP_CP_ATTACHMENT_MODULE_H__