From b72c82f5d78cbe2bd02022d09811270190bd43c4 Mon Sep 17 00:00:00 2001 From: Ezequiel Lopes dos Reis Junior Date: Thu, 19 Jun 2025 18:44:27 -0300 Subject: [PATCH 01/18] feat: add main functionality for handling rate limit requests --- log_zone/main.go | 118 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 log_zone/main.go diff --git a/log_zone/main.go b/log_zone/main.go new file mode 100644 index 0000000..7bd0ff4 --- /dev/null +++ b/log_zone/main.go @@ -0,0 +1,118 @@ +package main + +import ( + "fmt" + "io" + "net" + "net/http" + "time" + + "github.com/sirupsen/logrus" + "github.com/vmihailenco/msgpack/v5" +) + +func main() { + for { + zone, err := handleRequest("one") + if err != nil { + logrus.Error("Error handling request", "error", err) + return + } + fmt.Print("\033[H\033[2J") + logrus.Infof("Zone: %s, RateLimitHeader: %+v, RateLimitEntries: %d", + zone.Name, zone.RateLimitHeader, len(zone.RateLimitEntries)) + for _, entry := range zone.RateLimitEntries { + logrus.Infof("Entry Key: %s, Last: %d, Excess: %d", + entry.Key.String(zone.RateLimitHeader), entry.Last, entry.Excess) + } + fmt.Println("\nPress Ctrl+C to exit") + time.Sleep(2 * time.Second) + } +} + +func handleRequest(zone string) (Zone, error) { + endpoint := "http://localhost:9000/api/one" + req, err := http.NewRequest(http.MethodGet, endpoint, nil) + if err != nil { + return Zone{}, err + } + + response, err := http.DefaultClient.Do(req) + if err != nil { + return Zone{}, fmt.Errorf("error making request to %s: %w", endpoint, err) + } + defer response.Body.Close() + decoder := msgpack.NewDecoder(response.Body) + var rateLimitHeader RateLimitHeader + rateLimitEntries := []RateLimitEntry{} + log := logrus.New() + if err := decoder.Decode(&rateLimitHeader); err != nil { + if err == io.EOF { + return Zone{ + Name: zone, + RateLimitHeader: rateLimitHeader, + RateLimitEntries: rateLimitEntries, + }, nil + } + log.Error("Error decoding header", "error", err) + return Zone{}, err + } + for { + var message RateLimitEntry + if err := decoder.Decode(&message); err != nil { + if err == io.EOF { + break + } + log.Error("Error decoding entry", "error", err) + return Zone{}, err + } + message.Last = toNonMonotonic(message.Last, rateLimitHeader) + rateLimitEntries = append(rateLimitEntries, message) + } + log.Debug("Received rate limit entries", "zone", zone, "entries", len(rateLimitEntries)) + return Zone{ + Name: zone, + RateLimitHeader: rateLimitHeader, + RateLimitEntries: rateLimitEntries, + }, nil +} + +func toNonMonotonic(last int64, header RateLimitHeader) int64 { + return header.Now - (header.NowMonotonic - last) +} + +type Zone struct { + Name string + RateLimitHeader RateLimitHeader + RateLimitEntries []RateLimitEntry +} + +type RateLimitHeader struct { + Key string + Now int64 + NowMonotonic int64 +} + +type RateLimitEntry struct { + Key Key + Last int64 + Excess int64 +} + +const ( + BinaryRemoteAddress = "$binary_remote_addr" + RemoteAddress = "$remote_addr" +) + +type Key []byte + +func (r Key) String(header RateLimitHeader) string { + switch header.Key { + case BinaryRemoteAddress: + return net.IP(r).String() + case RemoteAddress: + fallthrough + default: + return string(r) + } +} From c3b82215b34ce444f625e4d3e23cfb96df797532 Mon Sep 17 00:00:00 2001 From: Ezequiel Lopes dos Reis Junior Date: Thu, 19 Jun 2025 18:44:37 -0300 Subject: [PATCH 02/18] feat: initialize go.mod and go.sum with required dependencies --- go.mod | 13 +++++++++++++ go.sum | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0f83b49 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module github.com/tsuru/ngx-http-limit-req-rw-module + +go 1.24.3 + +require ( + github.com/sirupsen/logrus v1.9.3 + github.com/vmihailenco/msgpack/v5 v5.4.1 +) + +require ( + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5611ca6 --- /dev/null +++ b/go.sum @@ -0,0 +1,19 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From c069b2557e1d5c4d1a9a1f4b74b28d534d96fcac Mon Sep 17 00:00:00 2001 From: Ezequiel Lopes dos Reis Junior Date: Thu, 19 Jun 2025 18:45:00 -0300 Subject: [PATCH 03/18] feat: add log-zone target to run log_zone main.go --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index f3132c6..151b8bc 100644 --- a/Makefile +++ b/Makefile @@ -29,3 +29,6 @@ debug: cd ./reader-go; go build -o debugger main.go; mv ./debugger .. ./debugger one.bin ./debugger two.bin + +log-zone: + go run log_zone/main.go \ No newline at end of file From 9eee529ec13448177dbd9d772b8408a368e104d9 Mon Sep 17 00:00:00 2001 From: Ezequiel Lopes dos Reis Junior Date: Thu, 19 Jun 2025 18:45:05 -0300 Subject: [PATCH 04/18] fix: correct module source path in config --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index c35c6ac..7a59c8a 100644 --- a/config +++ b/config @@ -1,7 +1,7 @@ ngx_addon_name=ngx_http_limit_req_rw_module ngx_module_type=HTTP ngx_module_name=ngx_http_limit_req_rw_module -ngx_module_srcs="$ngx_addon_dir/ngx_http_limit_req_rw_module.c" +ngx_module_srcs="$ngx_addon_dir/src/ngx_http_limit_req_rw_module.c" CFLAGS="$CFLAGS `pkg-config --cflags 'msgpack-c = 6.1.0'`" CORE_LIBS="$CORE_LIBS `pkg-config --libs 'msgpack-c = 6.1.0'`" From c239619a7536616370c49d0c51870f32bfa1f220 Mon Sep 17 00:00:00 2001 From: Ezequiel Lopes dos Reis Junior Date: Thu, 19 Jun 2025 18:47:23 -0300 Subject: [PATCH 05/18] feat: add ngx_http_limit_req_module header file with necessary structures --- ngx_http_limit_req_rw_module.c | 379 ------------------ .../includes/ngx_http_limit_req_module.h | 0 2 files changed, 379 deletions(-) delete mode 100644 ngx_http_limit_req_rw_module.c rename ngx_http_limit_req_module.h => src/includes/ngx_http_limit_req_module.h (100%) diff --git a/ngx_http_limit_req_rw_module.c b/ngx_http_limit_req_rw_module.c deleted file mode 100644 index e67c95d..0000000 --- a/ngx_http_limit_req_rw_module.c +++ /dev/null @@ -1,379 +0,0 @@ -/* -TODO: copyright -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ngx_http_limit_req_module.h" - -const int MAX_NUMBER_OF_RATE_LIMIT_ELEMENTS = 30 * 1000; - -static ngx_int_t ngx_http_limit_req_read_handler(ngx_http_request_t *r); - -static char *ngx_http_limit_req_rw_handler(ngx_conf_t *cf, ngx_command_t *cmd, - void *conf); -static void strip_zone_name_from_uri(ngx_str_t *uri, ngx_str_t *zone_name); -static ngx_int_t dump_rate_limit_zones(ngx_http_request_t *r, ngx_buf_t *b); -static ngx_int_t dump_req_zone(ngx_pool_t *pool, ngx_buf_t *b, - ngx_str_t *zone_name, ngx_uint_t last_greater_equal); -static ngx_int_t dump_req_limits(ngx_pool_t *pool, ngx_shm_zone_t *shm_zone, - ngx_buf_t *buf, ngx_uint_t last_greater_equal); -static ngx_command_t ngx_http_limit_req_rw_commands[] = { - {ngx_string("limit_req_rw_handler"), - NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | - NGX_CONF_NOARGS | NGX_CONF_TAKE1, - ngx_http_limit_req_rw_handler, 0, 0, NULL}, - ngx_null_command}; - -static ngx_http_module_t ngx_http_limit_req_rw_module_ctx = { - NULL, /* preconfiguration */ - NULL, /* postconfiguration */ - - NULL, /* create main configuration */ - NULL, /* init main configuration */ - - NULL, /* create server configuration */ - NULL, /* merge server configuration */ - - NULL, /* create location configuration */ - NULL /* merge location configuration */ -}; - -ngx_module_t ngx_http_limit_req_rw_module = {NGX_MODULE_V1, - &ngx_http_limit_req_rw_module_ctx, - ngx_http_limit_req_rw_commands, - NGX_HTTP_MODULE, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NGX_MODULE_V1_PADDING}; - -static ngx_int_t ngx_http_limit_req_handler(ngx_http_request_t *r) { - if (r->method != NGX_HTTP_GET) { - return NGX_HTTP_NOT_ALLOWED; - } - - return ngx_http_limit_req_read_handler(r); -} - -static ngx_int_t ngx_http_limit_req_read_handler(ngx_http_request_t *r) { - ngx_str_t content_type, zone_name, last_greater_equal_arg; - ngx_int_t last_greater_equal; - ngx_int_t rc; - ngx_buf_t *b; - ngx_chain_t out; - ngx_http_core_loc_conf_t *clcf; - - if (r->method != NGX_HTTP_GET) { - return NGX_HTTP_NOT_ALLOWED; - } - - clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); - - b = ngx_create_temp_buf(r->pool, 1024 * 1024); - if (b == NULL) { - return NGX_HTTP_INTERNAL_SERVER_ERROR; - } - - if (clcf == NULL) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "ngx_http_limit_req_rw_module: clcf is NULL"); - return NGX_HTTP_INTERNAL_SERVER_ERROR; - } - ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, - "ngx_http_limit_req_rw_module: Processing request for URI: %V", &r->uri); - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: clcf->name: %V", &clcf->name); - // When request location is /api - if (clcf->name.len == r->uri.len) { - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: dumping rate limit zones"); - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: r: %p", r); - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: r->pool: %p", r->pool); - rc = dump_rate_limit_zones(r, b); - // When request location is /api/{zone_name} - } else { - strip_zone_name_from_uri(&r->uri, &zone_name); - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "zone name: %.*s", - (int)zone_name.len, zone_name.data); - last_greater_equal_arg.len = 0; - last_greater_equal = 0; - if (r->args.len) { - if (ngx_http_arg(r, (u_char *) "last_greater_equal", 18, &last_greater_equal_arg) == NGX_OK) { - last_greater_equal = ngx_atoi(last_greater_equal_arg.data, last_greater_equal_arg.len); - if (last_greater_equal == NGX_ERROR || last_greater_equal < 0) { - return NGX_HTTP_BAD_REQUEST; - } - } - } - rc = dump_req_zone(r->pool, b, &zone_name, (ngx_uint_t) last_greater_equal); - } - - if (rc != NGX_OK) { - return rc; - } - - ngx_str_set(&content_type, "application/vnd.msgpack"); - r->headers_out.content_type = content_type; - r->headers_out.content_length_n = b->last - b->pos; - r->headers_out.status = NGX_HTTP_OK; /* 200 OK */ - - b->last_buf = (r == r->main) ? 1 : 0; /* if subrequest 0 else 1 */ - b->last_in_chain = 1; - - out.buf = b; - out.next = NULL; - - rc = ngx_http_send_header(r); - if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { - return rc; - } - - return ngx_http_output_filter(r, &out); -} - -static char *ngx_http_limit_req_rw_handler(ngx_conf_t *cf, ngx_command_t *cmd, - void *conf) { - ngx_http_core_loc_conf_t *clcf; - - clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); - clcf->handler = ngx_http_limit_req_handler; - - return NGX_CONF_OK; -} - -static void strip_zone_name_from_uri(ngx_str_t *uri, ngx_str_t *zone_name) { - zone_name->data = (u_char *)ngx_strlchr(uri->data, uri->data + uri->len, '/'); - zone_name->len = 0; - - if (zone_name->data) { - zone_name->data = - (u_char *)(ngx_strlchr(zone_name->data + 1, uri->data + uri->len, '/') + - 1); - zone_name->len = uri->len - (zone_name->data - uri->data); - } -} - -static inline int msgpack_ngx_buf_write(void *data, const char *buf, - size_t len) { - ngx_buf_t *b = (ngx_buf_t *)data; - b->last = ngx_cpymem(b->last, buf, len); - return 0; -} - -static ngx_int_t dump_rate_limit_zones(ngx_http_request_t *r, ngx_buf_t *buf) { - ngx_array_t *zones; - ngx_str_t **zone_name; - ngx_uint_t i; - ngx_shm_zone_t *shm_zone; - volatile ngx_list_part_t *part; - - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: dump_rate_limit_zones called"); - - if (ngx_cycle == NULL) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "ngx_http_limit_req_rw_module: ngx_cycle is NULL"); - return NGX_HTTP_INTERNAL_SERVER_ERROR; - } - - msgpack_packer pk; - msgpack_packer_init(&pk, buf, msgpack_ngx_buf_write); - zones = ngx_array_create(r->pool, 0, sizeof(ngx_str_t*)); - if (zones == NULL) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "ngx_http_limit_req_rw_module: failed to create zones array"); - return NGX_HTTP_INTERNAL_SERVER_ERROR; - } - - part = &ngx_cycle->shared_memory.part; - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: part->nelts %d", part->nelts); - shm_zone = part->elts; - - for (i = 0; /* void */; i++) { - - if (i >= part->nelts) { - if (part->next == NULL) { - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: part->next is NULL, breaking out of loop"); - break; - } - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: part->next is not NULL, advancing"); - part = part->next; - shm_zone = part->elts; - i = 0; - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: new part->nelts %d", part->nelts); - } - - if (shm_zone == NULL) { - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: shm_zone is NULL, continuing"); - continue; - } - - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: comparing shm_zone tag"); - if (shm_zone[i].tag != &ngx_http_limit_req_module) { - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: shm_zone tag is not limit_req_module, continuing"); - continue; - } - - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: pushing new zone struct to array"); - zone_name = ngx_array_push(zones); - if (zone_name == NULL) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "ngx_http_limit_req_rw_module: failed to push zone name"); - return NGX_HTTP_INTERNAL_SERVER_ERROR; - } - - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: copying zone name"); - *zone_name = &shm_zone[i].shm.name; - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: zone name copied"); - } - - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: packing array of zones"); - msgpack_pack_array(&pk, zones->nelts); - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: packing zone name"); - zone_name = zones->elts; - for (i = 0; i < zones->nelts; i++) { - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: packing zone name %.*s", - (int)zone_name[i]->len, zone_name[i]->data); - msgpack_pack_str(&pk, zone_name[i]->len); - msgpack_pack_str_body(&pk, zone_name[i]->data, zone_name[i]->len); - } - - return NGX_OK; -} - -static ngx_int_t dump_req_zone(ngx_pool_t *pool, ngx_buf_t *b, - ngx_str_t *zone_name, ngx_uint_t last_greater_equal) { - ngx_uint_t i; - ngx_shm_zone_t *shm_zone; - volatile ngx_list_part_t *part; - - if (ngx_cycle == NULL) { - return NGX_HTTP_INTERNAL_SERVER_ERROR; - } - - part = &ngx_cycle->shared_memory.part; - shm_zone = part->elts; - - for (i = 0; /* void */; i++) { - - if (i >= part->nelts) { - if (part->next == NULL) { - break; - } - part = part->next; - shm_zone = part->elts; - i = 0; - } - - if (shm_zone == NULL) { - continue; - } - - if (shm_zone[i].tag != &ngx_http_limit_req_module) { - continue; - } - - if (shm_zone[i].shm.name.len != zone_name->len) { - continue; - } - - if (ngx_strncmp(zone_name->data, shm_zone[i].shm.name.data, zone_name->len) == 0) { - return dump_req_limits(pool, &shm_zone[i], b, last_greater_equal); - } - } - return NGX_HTTP_NOT_FOUND; -} - -static ngx_int_t dump_req_limits(ngx_pool_t *pool, ngx_shm_zone_t *shm_zone, - ngx_buf_t *buf, ngx_uint_t last_greater_equal) { - ngx_http_limit_req_ctx_t *ctx; - ngx_queue_t *head, *q, *last; - ngx_http_limit_req_node_t *lr; - time_t now, now_monotonic; - int i; - - now_monotonic = ngx_current_msec; - // retrieving current timestamp in milliseconds - now = ngx_cached_time->sec * 1000 + ngx_cached_time->msec; - - ctx = shm_zone->data; - - msgpack_packer pk; - msgpack_packer_init(&pk, buf, msgpack_ngx_buf_write); - - // Including header - msgpack_pack_array(&pk, 3); - msgpack_pack_str(&pk, ctx->key.value.len); - msgpack_pack_str_body(&pk, ctx->key.value.data, ctx->key.value.len); - msgpack_pack_uint64(&pk, now); - msgpack_pack_uint64(&pk, now_monotonic); - - ngx_log_debug4( - NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, "shm.name %p -> %.*s - rate: %lu", - ctx, (int)shm_zone->shm.name.len, shm_zone->shm.name.data, ctx->rate); - - ngx_shmtx_lock(&ctx->shpool->mutex); - - if (ngx_queue_empty(&ctx->sh->queue)) { - ngx_shmtx_unlock(&ctx->shpool->mutex); - return NGX_OK; - } - - head = ngx_queue_head(&ctx->sh->queue); - last = ngx_queue_last(head); - q = head; - - for (i = 0; q != last && i < MAX_NUMBER_OF_RATE_LIMIT_ELEMENTS; i++) { - lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue); - if (last_greater_equal != 0 && lr->last < last_greater_equal) { - break; - } - msgpack_pack_array(&pk, 3); - - msgpack_pack_str(&pk, lr->len); - msgpack_pack_str_body(&pk, lr->data, lr->len); - msgpack_pack_uint64(&pk, lr->last); - msgpack_pack_int(&pk, lr->excess); - - q = q->next; - } - - ngx_shmtx_unlock(&ctx->shpool->mutex); - - return NGX_OK; -} diff --git a/ngx_http_limit_req_module.h b/src/includes/ngx_http_limit_req_module.h similarity index 100% rename from ngx_http_limit_req_module.h rename to src/includes/ngx_http_limit_req_module.h From cbf1fd038f9033eeeed86ede9353c359a7582328 Mon Sep 17 00:00:00 2001 From: Ezequiel Lopes dos Reis Junior Date: Thu, 19 Jun 2025 18:52:31 -0300 Subject: [PATCH 06/18] feat: implement write function for shm --- src/ngx_http_limit_req_rw_module.c | 561 +++++++++++++++++++++++++++++ 1 file changed, 561 insertions(+) create mode 100644 src/ngx_http_limit_req_rw_module.c diff --git a/src/ngx_http_limit_req_rw_module.c b/src/ngx_http_limit_req_rw_module.c new file mode 100644 index 0000000..05e8672 --- /dev/null +++ b/src/ngx_http_limit_req_rw_module.c @@ -0,0 +1,561 @@ +/* +TODO: copyright +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "./includes/ngx_http_limit_req_module.h" + +const int MAX_NUMBER_OF_RATE_LIMIT_ELEMENTS = 30 * 1000; + +static ngx_int_t ngx_http_limit_req_read_handler(ngx_http_request_t *r); +static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r); + +static char *ngx_http_limit_req_rw_handler(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static void strip_zone_name_from_uri(ngx_str_t *uri, ngx_str_t *zone_name); +static ngx_int_t dump_rate_limit_zones(ngx_http_request_t *r, ngx_buf_t *b); +static ngx_int_t dump_req_zone(ngx_pool_t *pool, ngx_buf_t *b, + ngx_str_t *zone_name, ngx_uint_t last_greater_equal); +static ngx_int_t dump_req_limits(ngx_pool_t *pool, ngx_shm_zone_t *shm_zone, + ngx_buf_t *buf, ngx_uint_t last_greater_equal); +static ngx_command_t ngx_http_limit_req_rw_commands[] = { + {ngx_string("limit_req_rw_handler"), + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | + NGX_CONF_NOARGS | NGX_CONF_TAKE1, + ngx_http_limit_req_rw_handler, 0, 0, NULL}, + ngx_null_command}; + +static ngx_http_module_t ngx_http_limit_req_rw_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + +ngx_module_t ngx_http_limit_req_rw_module = {NGX_MODULE_V1, + &ngx_http_limit_req_rw_module_ctx, + ngx_http_limit_req_rw_commands, + NGX_HTTP_MODULE, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NGX_MODULE_V1_PADDING}; + +static ngx_int_t ngx_http_limit_req_handler(ngx_http_request_t *r) +{ + if (r->method == NGX_HTTP_GET) + { + return ngx_http_limit_req_read_handler(r); + } + if (r->method == NGX_HTTP_POST) + { + return ngx_http_limit_req_write_handler(r); + } + return NGX_HTTP_SERVICE_UNAVAILABLE; +} + +static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r) +{ + ngx_str_t zone_name; + ngx_shm_zone_t *shm_zone; + volatile ngx_list_part_t *part; + ngx_uint_t i; + + strip_zone_name_from_uri(&r->uri, &zone_name); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_http_limit_req_rw_module: for URI %V", &zone_name); + + part = &ngx_cycle->shared_memory.part; + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: part->nelts %d", part->nelts); + shm_zone = part->elts; + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: part->elts %p", shm_zone); + + for (i = 0; /* void */; i++) + { + if (i >= part->nelts) + { + if (part->next == NULL) + { + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: part->next is NULL, breaking out of loop"); + break; + } + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: part->next is not NULL, advancing"); + part = part->next; + shm_zone = part->elts; + i = 0; + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: new part->nelts %d", part->nelts); + } + + if (shm_zone == NULL) + { + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: shm_zone is NULL, continuing"); + continue; + } + + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: comparing shm_zone tag"); + if (shm_zone[i].tag != &ngx_http_limit_req_module) + { + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: shm_zone tag is not limit_req_module, continuing"); + continue; + } + + ngx_http_limit_req_ctx_t *ctx = shm_zone[i].data; + + struct in_addr addr; + inet_pton(AF_INET, "127.0.0.1", &addr); // TODO: Replace with actual client IP retrieval logic + ngx_str_t key; + key.data = (u_char *)&addr; + key.len = sizeof(addr); // 4 + + uint32_t hash = ngx_crc32_short(key.data, key.len); + + ngx_shmtx_lock(&ctx->shpool->mutex); + + ngx_rbtree_node_t *node = ctx->sh->rbtree.root; + ngx_rbtree_node_t *sentinel = ctx->sh->rbtree.sentinel; + ngx_http_limit_req_node_t *lr = NULL; + int found = 0; + + while (node != sentinel) + { + if (hash < node->key) + { + node = node->left; + continue; + } + if (hash > node->key) + { + node = node->right; + continue; + } + lr = (ngx_http_limit_req_node_t *)&node->color; + if (lr->len == key.len && ngx_memcmp(lr->data, key.data, key.len) == 0) + { + lr->excess = 777; + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: found existing node, excess set to 777"); + found = 1; + break; + } + node = (ngx_memn2cmp(key.data, lr->data, key.len, lr->len) < 0) ? node->left : node->right; + } + if (!found) + { + size_t size = offsetof(ngx_rbtree_node_t, color) + offsetof(ngx_http_limit_req_node_t, data) + key.len; + node = ngx_slab_alloc_locked(ctx->shpool, size); + if (node == NULL) + { + ngx_shmtx_unlock(&ctx->shpool->mutex); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for rate limit node"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + node->key = hash; + lr = (ngx_http_limit_req_node_t *)&node->color; + + lr->len = (u_short)key.len; + lr->excess = 777; // TODO: replace with the value received from msgPack + lr->count = 0; + ngx_memcpy(lr->data, key.data, key.len); + + ngx_rbtree_insert(&ctx->sh->rbtree, node); + ngx_queue_insert_head(&ctx->sh->queue, &lr->queue); + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: new node created, excess set to 777"); + } + ngx_shmtx_unlock(&ctx->shpool->mutex); + + ngx_str_t response = ngx_string("OK\n"); + + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = response.len; + ngx_str_set(&r->headers_out.content_type, "text/plain"); + + ngx_int_t rc = ngx_http_send_header(r); + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) + { + return rc; + } + + ngx_buf_t *b = ngx_create_temp_buf(r->pool, response.len); + if (b == NULL) + { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ngx_memcpy(b->pos, response.data, response.len); + b->last = b->pos + response.len; + b->last_buf = 1; + + ngx_chain_t out; + out.buf = b; + out.next = NULL; + + return ngx_http_output_filter(r, &out); + } + return NGX_HTTP_NOT_FOUND; +} + +static ngx_int_t ngx_http_limit_req_read_handler(ngx_http_request_t *r) +{ + ngx_str_t content_type, zone_name, last_greater_equal_arg; + ngx_int_t last_greater_equal; + ngx_int_t rc; + ngx_buf_t *b; + ngx_chain_t out; + ngx_http_core_loc_conf_t *clcf; + + if (r->method != NGX_HTTP_GET) + { + return NGX_HTTP_NOT_ALLOWED; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + b = ngx_create_temp_buf(r->pool, 1024 * 1024); + if (b == NULL) + { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (clcf == NULL) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "ngx_http_limit_req_rw_module: clcf is NULL"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "ngx_http_limit_req_rw_module: Processing request for URI: %V", &r->uri); + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: clcf->name: %V", &clcf->name); + // When request location is /api + if (clcf->name.len == r->uri.len) + { + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: dumping rate limit zones"); + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: r: %p", r); + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: r->pool: %p", r->pool); + rc = dump_rate_limit_zones(r, b); + // When request location is /api/{zone_name} + } + else + { + strip_zone_name_from_uri(&r->uri, &zone_name); + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "zone name: %.*s", + (int)zone_name.len, zone_name.data); + last_greater_equal_arg.len = 0; + last_greater_equal = 0; + if (r->args.len) + { + if (ngx_http_arg(r, (u_char *)"last_greater_equal", 18, &last_greater_equal_arg) == NGX_OK) + { + last_greater_equal = ngx_atoi(last_greater_equal_arg.data, last_greater_equal_arg.len); + if (last_greater_equal == NGX_ERROR || last_greater_equal < 0) + { + return NGX_HTTP_BAD_REQUEST; + } + } + } + rc = dump_req_zone(r->pool, b, &zone_name, (ngx_uint_t)last_greater_equal); + } + + if (rc != NGX_OK) + { + return rc; + } + + ngx_str_set(&content_type, "application/vnd.msgpack"); + r->headers_out.content_type = content_type; + r->headers_out.content_length_n = b->last - b->pos; + r->headers_out.status = NGX_HTTP_OK; /* 200 OK */ + + b->last_buf = (r == r->main) ? 1 : 0; /* if subrequest 0 else 1 */ + b->last_in_chain = 1; + + out.buf = b; + out.next = NULL; + + rc = ngx_http_send_header(r); + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) + { + return rc; + } + + return ngx_http_output_filter(r, &out); +} + +static char *ngx_http_limit_req_rw_handler(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_http_limit_req_handler; + + return NGX_CONF_OK; +} + +static void strip_zone_name_from_uri(ngx_str_t *uri, ngx_str_t *zone_name) +{ + zone_name->data = (u_char *)ngx_strlchr(uri->data, uri->data + uri->len, '/'); + zone_name->len = 0; + + if (zone_name->data) + { + zone_name->data = + (u_char *)(ngx_strlchr(zone_name->data + 1, uri->data + uri->len, '/') + + 1); + zone_name->len = uri->len - (zone_name->data - uri->data); + } +} + +static inline int msgpack_ngx_buf_write(void *data, const char *buf, + size_t len) +{ + ngx_buf_t *b = (ngx_buf_t *)data; + b->last = ngx_cpymem(b->last, buf, len); + return 0; +} + +static ngx_int_t dump_rate_limit_zones(ngx_http_request_t *r, ngx_buf_t *buf) +{ + ngx_array_t *zones; + ngx_str_t **zone_name; + ngx_uint_t i; + ngx_shm_zone_t *shm_zone; + volatile ngx_list_part_t *part; + + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: dump_rate_limit_zones called"); + + if (ngx_cycle == NULL) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "ngx_http_limit_req_rw_module: ngx_cycle is NULL"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + msgpack_packer pk; + msgpack_packer_init(&pk, buf, msgpack_ngx_buf_write); + zones = ngx_array_create(r->pool, 0, sizeof(ngx_str_t *)); + if (zones == NULL) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "ngx_http_limit_req_rw_module: failed to create zones array"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + part = &ngx_cycle->shared_memory.part; + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: part->nelts %d", part->nelts); + shm_zone = part->elts; + + for (i = 0; /* void */; i++) + { + + if (i >= part->nelts) + { + if (part->next == NULL) + { + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: part->next is NULL, breaking out of loop"); + break; + } + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: part->next is not NULL, advancing"); + part = part->next; + shm_zone = part->elts; + i = 0; + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: new part->nelts %d", part->nelts); + } + + if (shm_zone == NULL) + { + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: shm_zone is NULL, continuing"); + continue; + } + + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: comparing shm_zone tag"); + if (shm_zone[i].tag != &ngx_http_limit_req_module) + { + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: shm_zone tag is not limit_req_module, continuing"); + continue; + } + + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: pushing new zone struct to array"); + zone_name = ngx_array_push(zones); + if (zone_name == NULL) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "ngx_http_limit_req_rw_module: failed to push zone name"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: copying zone name"); + *zone_name = &shm_zone[i].shm.name; + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: zone name copied"); + } + + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: packing array of zones"); + msgpack_pack_array(&pk, zones->nelts); + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: packing zone name"); + zone_name = zones->elts; + for (i = 0; i < zones->nelts; i++) + { + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: packing zone name %.*s", + (int)zone_name[i]->len, zone_name[i]->data); + msgpack_pack_str(&pk, zone_name[i]->len); + msgpack_pack_str_body(&pk, zone_name[i]->data, zone_name[i]->len); + } + + return NGX_OK; +} + +static ngx_int_t dump_req_zone(ngx_pool_t *pool, ngx_buf_t *b, + ngx_str_t *zone_name, ngx_uint_t last_greater_equal) +{ + ngx_uint_t i; + ngx_shm_zone_t *shm_zone; + volatile ngx_list_part_t *part; + + if (ngx_cycle == NULL) + { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + part = &ngx_cycle->shared_memory.part; + shm_zone = part->elts; + + for (i = 0; /* void */; i++) + { + + if (i >= part->nelts) + { + if (part->next == NULL) + { + break; + } + part = part->next; + shm_zone = part->elts; + i = 0; + } + + if (shm_zone == NULL) + { + continue; + } + + if (shm_zone[i].tag != &ngx_http_limit_req_module) + { + continue; + } + + if (shm_zone[i].shm.name.len != zone_name->len) + { + continue; + } + + if (ngx_strncmp(zone_name->data, shm_zone[i].shm.name.data, zone_name->len) == 0) + { + return dump_req_limits(pool, &shm_zone[i], b, last_greater_equal); + } + } + return NGX_HTTP_NOT_FOUND; +} + +static ngx_int_t dump_req_limits(ngx_pool_t *pool, ngx_shm_zone_t *shm_zone, + ngx_buf_t *buf, ngx_uint_t last_greater_equal) +{ + ngx_http_limit_req_ctx_t *ctx; + ngx_queue_t *head, *q, *last; + ngx_http_limit_req_node_t *lr; + time_t now, now_monotonic; + int i; + + now_monotonic = ngx_current_msec; + // retrieving current timestamp in milliseconds + now = ngx_cached_time->sec * 1000 + ngx_cached_time->msec; + + ctx = shm_zone->data; + + msgpack_packer pk; + msgpack_packer_init(&pk, buf, msgpack_ngx_buf_write); + + // Including header + msgpack_pack_array(&pk, 3); + msgpack_pack_str(&pk, ctx->key.value.len); + msgpack_pack_str_body(&pk, ctx->key.value.data, ctx->key.value.len); + msgpack_pack_uint64(&pk, now); + msgpack_pack_uint64(&pk, now_monotonic); + + ngx_log_debug4( + NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, "shm.name %p -> %.*s - rate: %lu", + ctx, (int)shm_zone->shm.name.len, shm_zone->shm.name.data, ctx->rate); + + ngx_shmtx_lock(&ctx->shpool->mutex); + + if (ngx_queue_empty(&ctx->sh->queue)) + { + ngx_shmtx_unlock(&ctx->shpool->mutex); + return NGX_OK; + } + + head = ngx_queue_head(&ctx->sh->queue); + last = ngx_queue_last(head); + q = head; + + for (i = 0; q != last && i < MAX_NUMBER_OF_RATE_LIMIT_ELEMENTS; i++) + { + lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue); + if (last_greater_equal != 0 && lr->last < last_greater_equal) + { + break; + } + msgpack_pack_array(&pk, 3); + + msgpack_pack_str(&pk, lr->len); + msgpack_pack_str_body(&pk, lr->data, lr->len); + msgpack_pack_uint64(&pk, lr->last); + msgpack_pack_int(&pk, lr->excess); + + q = q->next; + } + + ngx_shmtx_unlock(&ctx->shpool->mutex); + + return NGX_OK; +} From 21582429cd6b3163fdad3eea40648c19a9103d2f Mon Sep 17 00:00:00 2001 From: Ezequiel Lopes dos Reis Junior Date: Tue, 24 Jun 2025 16:45:39 -0300 Subject: [PATCH 07/18] feat: enhance log-zone functionality with dynamic file execution and request handling Co-authored-by: Ravi Menezes --- src/ngx_http_limit_req_rw_module.c | 112 ++++++++++++++++++++++++++--- 1 file changed, 103 insertions(+), 9 deletions(-) diff --git a/src/ngx_http_limit_req_rw_module.c b/src/ngx_http_limit_req_rw_module.c index 05e8672..a73792f 100644 --- a/src/ngx_http_limit_req_rw_module.c +++ b/src/ngx_http_limit_req_rw_module.c @@ -81,8 +81,110 @@ static ngx_int_t ngx_http_limit_req_handler(ngx_http_request_t *r) return NGX_HTTP_SERVICE_UNAVAILABLE; } +static void ngx_http_my_handler(ngx_http_request_t *r); + +typedef struct +{ + ngx_str_t Key; + uint64_t Last; + uint64_t Excess; +} entities; + +static void ngx_http_my_handler(ngx_http_request_t *r) +{ + if (r->request_body == NULL || r->request_body->bufs == NULL) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no request body found"); + return; + } + ngx_chain_t *cl; + size_t len = 0; + + for (cl = r->request_body->bufs; cl; cl = cl->next) + { + len += cl->buf->last - cl->buf->pos; + } + u_char *data = ngx_pnalloc(r->pool, len); + if (data == NULL) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for request body"); + return; + } + + u_char *p = data; + for (cl = r->request_body->bufs; cl; cl = cl->next) + { + size_t size = cl->buf->last - cl->buf->pos; + ngx_memcpy(p, cl->buf->pos, size); + p += size; + } + + msgpack_zone mempool; + msgpack_zone_init(&mempool, 2048); + + msgpack_object deserialized; + msgpack_unpack((char *)data, len, NULL, &mempool, &deserialized); + + if (deserialized.type == MSGPACK_OBJECT_ARRAY) + { + uint32_t size = deserialized.via.array.size; + msgpack_object *items = deserialized.via.array.ptr; + + entities *arr = ngx_pnalloc(r->pool, size * sizeof(entities)); + if (data == NULL) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for array"); + return; + } + + for (uint32_t i = 1; i < items->via.array.size; i++) + { + uint32_t iItemSize = items[i].via.array.size; + msgpack_object iItemKey = items[i].via.array.ptr[0]; + msgpack_object iItemLast = items[i].via.array.ptr[1]; + msgpack_object iItemExcess = items[i].via.array.ptr[2]; + arr[i - 1].Key.len = iItemKey.via.str.size; + u_char *keyData = ngx_palloc(r->pool, iItemKey.via.str.size); + if (keyData == NULL) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for key"); + return; + } + ngx_memcpy(keyData, iItemKey.via.str.ptr, iItemKey.via.str.size); + arr[i - 1].Key.data = keyData; + arr[i - 1].Last = iItemLast.via.u64; + arr[i - 1].Excess = iItemExcess.via.u64; + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "Key [%d] value: %*s", i, arr[i - 1].Key); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "Last [%d] value: %ul", i, arr[i - 1].Last); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "Excess [%d] value: %ul", i, arr[i - 1].Excess); + } + } + + msgpack_zone_destroy(&mempool); + + ngx_str_t response = ngx_string("logged\n"); + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = response.len; + ngx_http_send_header(r); + + ngx_buf_t *b = ngx_create_temp_buf(r->pool, response.len); + ngx_memcpy(b->pos, response.data, response.len); + b->last = b->pos + response.len; + b->last_buf = 1; + + ngx_chain_t out = {.buf = b, .next = NULL}; + ngx_http_output_filter(r, &out); +} + static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r) { + r->request_body_in_single_buf = 1; + return ngx_http_read_client_request_body(r, ngx_http_my_handler); + ngx_str_t zone_name; ngx_shm_zone_t *shm_zone; volatile ngx_list_part_t *part; @@ -126,6 +228,7 @@ static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r) } ngx_http_limit_req_ctx_t *ctx = shm_zone[i].data; + ngx_shmtx_lock(&ctx->shpool->mutex); struct in_addr addr; inet_pton(AF_INET, "127.0.0.1", &addr); // TODO: Replace with actual client IP retrieval logic @@ -135,13 +238,10 @@ static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r) uint32_t hash = ngx_crc32_short(key.data, key.len); - ngx_shmtx_lock(&ctx->shpool->mutex); - ngx_rbtree_node_t *node = ctx->sh->rbtree.root; ngx_rbtree_node_t *sentinel = ctx->sh->rbtree.sentinel; ngx_http_limit_req_node_t *lr = NULL; int found = 0; - while (node != sentinel) { if (hash < node->key) @@ -227,13 +327,7 @@ static ngx_int_t ngx_http_limit_req_read_handler(ngx_http_request_t *r) ngx_chain_t out; ngx_http_core_loc_conf_t *clcf; - if (r->method != NGX_HTTP_GET) - { - return NGX_HTTP_NOT_ALLOWED; - } - clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); - b = ngx_create_temp_buf(r->pool, 1024 * 1024); if (b == NULL) { From 015bd7c4df7d70624c7d95a721ab6bec663cff74 Mon Sep 17 00:00:00 2001 From: Ezequiel Lopes dos Reis Junior Date: Tue, 24 Jun 2025 17:00:51 -0300 Subject: [PATCH 08/18] feat: implement logging and request handling for rate limit zones --- log_zone/logs.go | 77 +++++++++++++++++++++++++++++++++++++++++++++++ log_zone/main.go | 78 ++++++++---------------------------------------- log_zone/send.go | 72 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+), 66 deletions(-) create mode 100644 log_zone/logs.go create mode 100644 log_zone/send.go diff --git a/log_zone/logs.go b/log_zone/logs.go new file mode 100644 index 0000000..a08dbc0 --- /dev/null +++ b/log_zone/logs.go @@ -0,0 +1,77 @@ +package main + +import ( + "fmt" + "io" + "net/http" + "time" + + "github.com/sirupsen/logrus" + "github.com/vmihailenco/msgpack/v5" +) + +func logs(zone string) { + for { + zone, err := handleRequest(zone) + if err != nil { + logrus.Error("Error handling request", "error", err) + return + } + fmt.Print("\033[H\033[2J") + logrus.Infof("Zone: %s, RateLimitHeader: %+v, RateLimitEntries: %d", + zone.Name, zone.RateLimitHeader, len(zone.RateLimitEntries)) + for _, entry := range zone.RateLimitEntries { + logrus.Infof("Entry Key: %s, Last: %d, Excess: %d", + entry.Key.String(zone.RateLimitHeader), entry.Last, entry.Excess) + } + fmt.Println("\nPress Ctrl+C to exit") + time.Sleep(2 * time.Second) + } +} + +func handleRequest(zone string) (Zone, error) { + endpoint := fmt.Sprintf("http://localhost:9000/api/%s", zone) + req, err := http.NewRequest(http.MethodGet, endpoint, nil) + if err != nil { + return Zone{}, err + } + + response, err := http.DefaultClient.Do(req) + if err != nil { + return Zone{}, fmt.Errorf("error making request to %s: %w", endpoint, err) + } + defer response.Body.Close() + decoder := msgpack.NewDecoder(response.Body) + var rateLimitHeader RateLimitHeader + rateLimitEntries := []RateLimitEntry{} + log := logrus.New() + if err := decoder.Decode(&rateLimitHeader); err != nil { + if err == io.EOF { + return Zone{ + Name: zone, + RateLimitHeader: rateLimitHeader, + RateLimitEntries: rateLimitEntries, + }, nil + } + log.Error("Error decoding header", "error", err) + return Zone{}, err + } + for { + var message RateLimitEntry + if err := decoder.Decode(&message); err != nil { + if err == io.EOF { + break + } + log.Error("Error decoding entry", "error", err) + return Zone{}, err + } + message.Last = toNonMonotonic(message.Last, rateLimitHeader) + rateLimitEntries = append(rateLimitEntries, message) + } + log.Debug("Received rate limit entries", "zone", zone, "entries", len(rateLimitEntries)) + return Zone{ + Name: zone, + RateLimitHeader: rateLimitHeader, + RateLimitEntries: rateLimitEntries, + }, nil +} diff --git a/log_zone/main.go b/log_zone/main.go index 7bd0ff4..b3831a4 100644 --- a/log_zone/main.go +++ b/log_zone/main.go @@ -1,80 +1,26 @@ package main import ( - "fmt" - "io" "net" - "net/http" - "time" - - "github.com/sirupsen/logrus" - "github.com/vmihailenco/msgpack/v5" + "os" ) func main() { - for { - zone, err := handleRequest("one") - if err != nil { - logrus.Error("Error handling request", "error", err) - return - } - fmt.Print("\033[H\033[2J") - logrus.Infof("Zone: %s, RateLimitHeader: %+v, RateLimitEntries: %d", - zone.Name, zone.RateLimitHeader, len(zone.RateLimitEntries)) - for _, entry := range zone.RateLimitEntries { - logrus.Infof("Entry Key: %s, Last: %d, Excess: %d", - entry.Key.String(zone.RateLimitHeader), entry.Last, entry.Excess) - } - fmt.Println("\nPress Ctrl+C to exit") - time.Sleep(2 * time.Second) - } -} - -func handleRequest(zone string) (Zone, error) { - endpoint := "http://localhost:9000/api/one" - req, err := http.NewRequest(http.MethodGet, endpoint, nil) - if err != nil { - return Zone{}, err - } - - response, err := http.DefaultClient.Do(req) - if err != nil { - return Zone{}, fmt.Errorf("error making request to %s: %w", endpoint, err) - } - defer response.Body.Close() - decoder := msgpack.NewDecoder(response.Body) - var rateLimitHeader RateLimitHeader - rateLimitEntries := []RateLimitEntry{} - log := logrus.New() - if err := decoder.Decode(&rateLimitHeader); err != nil { - if err == io.EOF { - return Zone{ - Name: zone, - RateLimitHeader: rateLimitHeader, - RateLimitEntries: rateLimitEntries, - }, nil + arg := os.Args[1:] + zone := "one" + if arg[0] == "log" { + if len(arg) >= 2 { + zone = arg[1] } - log.Error("Error decoding header", "error", err) - return Zone{}, err + logs(zone) + return } - for { - var message RateLimitEntry - if err := decoder.Decode(&message); err != nil { - if err == io.EOF { - break - } - log.Error("Error decoding entry", "error", err) - return Zone{}, err + if arg[0] == "send" { + if len(arg) >= 2 { + zone = arg[1] } - message.Last = toNonMonotonic(message.Last, rateLimitHeader) - rateLimitEntries = append(rateLimitEntries, message) + send(zone) } - log.Debug("Received rate limit entries", "zone", zone, "entries", len(rateLimitEntries)) - return Zone{ - Name: zone, - RateLimitHeader: rateLimitHeader, - RateLimitEntries: rateLimitEntries, - }, nil } func toNonMonotonic(last int64, header RateLimitHeader) int64 { diff --git a/log_zone/send.go b/log_zone/send.go new file mode 100644 index 0000000..2509d5c --- /dev/null +++ b/log_zone/send.go @@ -0,0 +1,72 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "net/http" + "time" + + "github.com/sirupsen/logrus" + "github.com/vmihailenco/msgpack/v5" +) + +func send(zone string) { + err := sendRequest( + zone, + RateLimitHeader{ + Key: BinaryRemoteAddress, + Now: time.Now().Unix(), + NowMonotonic: time.Now().UnixNano() / int64(time.Millisecond), + }, []RateLimitEntry{ + {Key("127.0.0.0"), 7, 99}, + {Key("127.6.4.00"), 2, 98}, + }) + if err != nil { + logrus.Fatalf("Error sending request: %v", err) + } +} + +func sendRequest(zone string, header RateLimitHeader, entries []RateLimitEntry) error { + var buf bytes.Buffer + encoder := msgpack.NewEncoder(&buf) + var values []interface{} = []interface{}{ + headerToArray(header), + } + for _, entry := range entries { + values = append(values, entryToArray(entry, header)) + } + if err := encoder.Encode(values); err != nil { + return fmt.Errorf("error encoding entries: %w", err) + } + endpoint := fmt.Sprintf("http://localhost:9000/api/%s", zone) + req, err := http.NewRequest(http.MethodPost, endpoint, &buf) + if err != nil { + return fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Content-Type", "application/x-msgpack") + resp, err := http.DefaultClient.Do(req) + if err != nil { + return fmt.Errorf("error sending request to %s: %w", endpoint, err) + } + defer resp.Body.Close() + respBody, _ := io.ReadAll(resp.Body) + logrus.Infof("response status: %s, body: %s", resp.Status, respBody) + return nil +} + +func headerToArray(header RateLimitHeader) []interface{} { + return []interface{}{ + header.Key, + header.Now, + header.NowMonotonic, + } +} + +func entryToArray(entry RateLimitEntry, header RateLimitHeader) []interface{} { + return []interface{}{ + entry.Key, + entry.Last, + entry.Excess, + } +} From e01d0b9742d2fa19d9c27c6c174692a8c05617bc Mon Sep 17 00:00:00 2001 From: Ezequiel Lopes dos Reis Junior Date: Tue, 24 Jun 2025 17:00:58 -0300 Subject: [PATCH 09/18] feat: add log and send targets to Makefile for log zone processing --- Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 151b8bc..57d1f3b 100644 --- a/Makefile +++ b/Makefile @@ -30,5 +30,8 @@ debug: ./debugger one.bin ./debugger two.bin -log-zone: - go run log_zone/main.go \ No newline at end of file +log: + go run log_zone/*.go log + +send: + go run log_zone/*.go send \ No newline at end of file From d4213fb6e7103c1d5bb557ee5b7b7285b68f98f0 Mon Sep 17 00:00:00 2001 From: Ezequiel Lopes dos Reis Junior Date: Wed, 25 Jun 2025 16:03:09 -0300 Subject: [PATCH 10/18] feat: enhance ngx_http_limit_req_rw_module with improved data structures and error handling --- src/ngx_http_limit_req_rw_module.c | 100 ++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 31 deletions(-) diff --git a/src/ngx_http_limit_req_rw_module.c b/src/ngx_http_limit_req_rw_module.c index a73792f..583aa7a 100644 --- a/src/ngx_http_limit_req_rw_module.c +++ b/src/ngx_http_limit_req_rw_module.c @@ -23,6 +23,26 @@ TODO: copyright const int MAX_NUMBER_OF_RATE_LIMIT_ELEMENTS = 30 * 1000; +typedef struct +{ + ngx_str_t Key; + uint64_t Last; + uint64_t Excess; +} entities; + +typedef struct +{ + ngx_str_t Key; // Key of the rate limit zone + uint64_t Now; // Current timestamp in milliseconds + uint64_t NowMonotonic; // Current monotonic timestamp in milliseconds +} header; + +typedef struct +{ + header *Header; // Header information + entities *Entities; // Array of entities +} ngx_zone_data_t; + static ngx_int_t ngx_http_limit_req_read_handler(ngx_http_request_t *r); static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r); @@ -81,21 +101,13 @@ static ngx_int_t ngx_http_limit_req_handler(ngx_http_request_t *r) return NGX_HTTP_SERVICE_UNAVAILABLE; } -static void ngx_http_my_handler(ngx_http_request_t *r); - -typedef struct -{ - ngx_str_t Key; - uint64_t Last; - uint64_t Excess; -} entities; - -static void ngx_http_my_handler(ngx_http_request_t *r) +static ngx_zone_data_t *ngx_decode_msg_pack(ngx_http_request_t *r); +static ngx_zone_data_t *ngx_decode_msg_pack(ngx_http_request_t *r) { if (r->request_body == NULL || r->request_body->bufs == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no request body found"); - return; + return NULL; } ngx_chain_t *cl; size_t len = 0; @@ -108,7 +120,7 @@ static void ngx_http_my_handler(ngx_http_request_t *r) if (data == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for request body"); - return; + return NULL; } u_char *p = data; @@ -130,16 +142,49 @@ static void ngx_http_my_handler(ngx_http_request_t *r) uint32_t size = deserialized.via.array.size; msgpack_object *items = deserialized.via.array.ptr; + header *hdr = ngx_pnalloc(r->pool, sizeof(header)); + if (hdr == NULL) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for header"); + return NULL; + } + + if (items->via.array.size >= 1) + { + msgpack_object hdrKey = items[0].via.array.ptr[0]; + msgpack_object hdrNow = items[0].via.array.ptr[1]; + msgpack_object hdrNowMonotonic = items[0].via.array.ptr[2]; + + hdr->Key.len = hdrKey.via.str.size; + u_char *keyData = ngx_palloc(r->pool, hdrKey.via.str.size); + if (keyData == NULL) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for key"); + return NULL; + } + ngx_memcpy(keyData, hdrKey.via.str.ptr, hdrKey.via.str.size); + hdr->Key.data = keyData; + hdr->Now = hdrNow.via.u64; + hdr->NowMonotonic = hdrNowMonotonic.via.u64; + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "Header Key value: %*s", hdr->Key); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "Header Now value: %ul", hdr->Now); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "Header NowMonotonic value: %ul", hdr->NowMonotonic); + } + ngx_zone_data_t *output = ngx_pnalloc(r->pool, sizeof(ngx_zone_data_t)); + output->Header = hdr; + entities *arr = ngx_pnalloc(r->pool, size * sizeof(entities)); if (data == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for array"); - return; + return NULL; } - for (uint32_t i = 1; i < items->via.array.size; i++) { - uint32_t iItemSize = items[i].via.array.size; msgpack_object iItemKey = items[i].via.array.ptr[0]; msgpack_object iItemLast = items[i].via.array.ptr[1]; msgpack_object iItemExcess = items[i].via.array.ptr[2]; @@ -148,7 +193,7 @@ static void ngx_http_my_handler(ngx_http_request_t *r) if (keyData == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for key"); - return; + return NULL; } ngx_memcpy(keyData, iItemKey.via.str.ptr, iItemKey.via.str.size); arr[i - 1].Key.data = keyData; @@ -162,28 +207,21 @@ static void ngx_http_my_handler(ngx_http_request_t *r) ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Excess [%d] value: %ul", i, arr[i - 1].Excess); } + output->Entities = arr; + msgpack_zone_destroy(&mempool); + return output; } - msgpack_zone_destroy(&mempool); - - ngx_str_t response = ngx_string("logged\n"); - r->headers_out.status = NGX_HTTP_OK; - r->headers_out.content_length_n = response.len; - ngx_http_send_header(r); - - ngx_buf_t *b = ngx_create_temp_buf(r->pool, response.len); - ngx_memcpy(b->pos, response.data, response.len); - b->last = b->pos + response.len; - b->last_buf = 1; - - ngx_chain_t out = {.buf = b, .next = NULL}; - ngx_http_output_filter(r, &out); + return NULL; } static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r) { r->request_body_in_single_buf = 1; - return ngx_http_read_client_request_body(r, ngx_http_my_handler); + ngx_zone_data_t *msg_pack = ngx_decode_msg_pack(r); + + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: msg_pack is %p", msg_pack); ngx_str_t zone_name; ngx_shm_zone_t *shm_zone; From 4fb97cc306284f4b3a5043119e2196f116c1a424 Mon Sep 17 00:00:00 2001 From: Ezequiel Lopes dos Reis Junior Date: Thu, 26 Jun 2025 11:38:48 -0300 Subject: [PATCH 11/18] feat: enhance request handling in ngx_http_limit_req_rw_module with improved logging and error management --- src/ngx_http_limit_req_rw_module.c | 50 +++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/src/ngx_http_limit_req_rw_module.c b/src/ngx_http_limit_req_rw_module.c index 583aa7a..721f4d0 100644 --- a/src/ngx_http_limit_req_rw_module.c +++ b/src/ngx_http_limit_req_rw_module.c @@ -43,8 +43,10 @@ typedef struct entities *Entities; // Array of entities } ngx_zone_data_t; +static ngx_zone_data_t *ngx_decode_msg_pack(ngx_http_request_t *r); + static ngx_int_t ngx_http_limit_req_read_handler(ngx_http_request_t *r); -static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r); +static void ngx_http_limit_req_write_handler(ngx_http_request_t *r); static char *ngx_http_limit_req_rw_handler(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); @@ -96,19 +98,23 @@ static ngx_int_t ngx_http_limit_req_handler(ngx_http_request_t *r) } if (r->method == NGX_HTTP_POST) { - return ngx_http_limit_req_write_handler(r); + r->request_body_in_single_buf = 1; + return ngx_http_read_client_request_body(r, ngx_http_limit_req_write_handler); } return NGX_HTTP_SERVICE_UNAVAILABLE; } -static ngx_zone_data_t *ngx_decode_msg_pack(ngx_http_request_t *r); static ngx_zone_data_t *ngx_decode_msg_pack(ngx_http_request_t *r) { + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: start decoding msgpack"); + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "0.0"); if (r->request_body == NULL || r->request_body->bufs == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no request body found"); return NULL; } + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "1111111"); ngx_chain_t *cl; size_t len = 0; @@ -116,13 +122,14 @@ static ngx_zone_data_t *ngx_decode_msg_pack(ngx_http_request_t *r) { len += cl->buf->last - cl->buf->pos; } + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "2"); u_char *data = ngx_pnalloc(r->pool, len); if (data == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for request body"); return NULL; } - + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "3"); u_char *p = data; for (cl = r->request_body->bufs; cl; cl = cl->next) { @@ -130,13 +137,14 @@ static ngx_zone_data_t *ngx_decode_msg_pack(ngx_http_request_t *r) ngx_memcpy(p, cl->buf->pos, size); p += size; } - + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "4"); msgpack_zone mempool; msgpack_zone_init(&mempool, 2048); msgpack_object deserialized; msgpack_unpack((char *)data, len, NULL, &mempool, &deserialized); - + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: deserialized type: %d", deserialized.type); if (deserialized.type == MSGPACK_OBJECT_ARRAY) { uint32_t size = deserialized.via.array.size; @@ -154,7 +162,8 @@ static ngx_zone_data_t *ngx_decode_msg_pack(ngx_http_request_t *r) msgpack_object hdrKey = items[0].via.array.ptr[0]; msgpack_object hdrNow = items[0].via.array.ptr[1]; msgpack_object hdrNowMonotonic = items[0].via.array.ptr[2]; - + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "Header Key type: %d", hdrKey.type); hdr->Key.len = hdrKey.via.str.size; u_char *keyData = ngx_palloc(r->pool, hdrKey.via.str.size); if (keyData == NULL) @@ -215,13 +224,13 @@ static ngx_zone_data_t *ngx_decode_msg_pack(ngx_http_request_t *r) return NULL; } -static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r) +static void ngx_http_limit_req_write_handler(ngx_http_request_t *r) { r->request_body_in_single_buf = 1; ngx_zone_data_t *msg_pack = ngx_decode_msg_pack(r); ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: msg_pack is %p", msg_pack); + "ngx_http_limit_req_rw_module: msg_pack is %*s", msg_pack->Header->Key); ngx_str_t zone_name; ngx_shm_zone_t *shm_zone; @@ -310,7 +319,7 @@ static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r) { ngx_shmtx_unlock(&ctx->shpool->mutex); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for rate limit node"); - return NGX_HTTP_INTERNAL_SERVER_ERROR; + r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; } node->key = hash; lr = (ngx_http_limit_req_node_t *)&node->color; @@ -335,13 +344,12 @@ static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r) ngx_int_t rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { - return rc; + r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; } - ngx_buf_t *b = ngx_create_temp_buf(r->pool, response.len); if (b == NULL) { - return NGX_HTTP_INTERNAL_SERVER_ERROR; + r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_memcpy(b->pos, response.data, response.len); b->last = b->pos + response.len; @@ -351,9 +359,21 @@ static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r) out.buf = b; out.next = NULL; - return ngx_http_output_filter(r, &out); + ngx_http_output_filter(r, &out); } - return NGX_HTTP_NOT_FOUND; + + ngx_str_t response = ngx_string("logged\n"); + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = response.len; + ngx_http_send_header(r); + + ngx_buf_t *b = ngx_create_temp_buf(r->pool, response.len); + ngx_memcpy(b->pos, response.data, response.len); + b->last = b->pos + response.len; + b->last_buf = 1; + + ngx_chain_t out = {.buf = b, .next = NULL}; + ngx_http_output_filter(r, &out); } static ngx_int_t ngx_http_limit_req_read_handler(ngx_http_request_t *r) From 3ae8dcff1bf9c8b54089c1eeacf9bc5f32768a3d Mon Sep 17 00:00:00 2001 From: ravilock Date: Thu, 26 Jun 2025 14:33:30 -0300 Subject: [PATCH 12/18] ref: move source code files back to root dir --- config | 2 +- .../ngx_http_limit_req_module.h => ngx_http_limit_req_module.h | 0 ...http_limit_req_rw_module.c => ngx_http_limit_req_rw_module.c | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/includes/ngx_http_limit_req_module.h => ngx_http_limit_req_module.h (100%) rename src/ngx_http_limit_req_rw_module.c => ngx_http_limit_req_rw_module.c (99%) diff --git a/config b/config index 7a59c8a..c35c6ac 100644 --- a/config +++ b/config @@ -1,7 +1,7 @@ ngx_addon_name=ngx_http_limit_req_rw_module ngx_module_type=HTTP ngx_module_name=ngx_http_limit_req_rw_module -ngx_module_srcs="$ngx_addon_dir/src/ngx_http_limit_req_rw_module.c" +ngx_module_srcs="$ngx_addon_dir/ngx_http_limit_req_rw_module.c" CFLAGS="$CFLAGS `pkg-config --cflags 'msgpack-c = 6.1.0'`" CORE_LIBS="$CORE_LIBS `pkg-config --libs 'msgpack-c = 6.1.0'`" diff --git a/src/includes/ngx_http_limit_req_module.h b/ngx_http_limit_req_module.h similarity index 100% rename from src/includes/ngx_http_limit_req_module.h rename to ngx_http_limit_req_module.h diff --git a/src/ngx_http_limit_req_rw_module.c b/ngx_http_limit_req_rw_module.c similarity index 99% rename from src/ngx_http_limit_req_rw_module.c rename to ngx_http_limit_req_rw_module.c index 721f4d0..b5ecba5 100644 --- a/src/ngx_http_limit_req_rw_module.c +++ b/ngx_http_limit_req_rw_module.c @@ -19,7 +19,7 @@ TODO: copyright #include #include -#include "./includes/ngx_http_limit_req_module.h" +#include "ngx_http_limit_req_module.h" const int MAX_NUMBER_OF_RATE_LIMIT_ELEMENTS = 30 * 1000; From f3cde7a75b9ba8872c3621df41bbc3e603123bec Mon Sep 17 00:00:00 2001 From: ravilock Date: Thu, 26 Jun 2025 15:39:27 -0300 Subject: [PATCH 13/18] ref: post write handler --- Makefile | 8 +- log_zone/send.go | 1 + nginx.conf | 1 - ngx_http_limit_req_rw_module.c | 265 +++++++++++++++++---------------- 4 files changed, 145 insertions(+), 130 deletions(-) diff --git a/Makefile b/Makefile index 57d1f3b..f8e514d 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ .PHONY: build clean download-nginx build-nginx configure build-module run test debug -build: clean download-nginx build-nginx +setup: clean download-nginx configure + +build: build-nginx build-module download-nginx: curl https://nginx.org/download/nginx-1.26.3.tar.gz > nginx.tar.gz @@ -9,7 +11,7 @@ download-nginx: configure: cd nginx-1.26.3 && ./configure --prefix=$(PWD)/build --add-dynamic-module=.. -build-nginx: configure +build-nginx: cd nginx-1.26.3 && make && make install build-module: @@ -34,4 +36,4 @@ log: go run log_zone/*.go log send: - go run log_zone/*.go send \ No newline at end of file + go run log_zone/*.go send diff --git a/log_zone/send.go b/log_zone/send.go index 2509d5c..faa3fab 100644 --- a/log_zone/send.go +++ b/log_zone/send.go @@ -49,6 +49,7 @@ func sendRequest(zone string, header RateLimitHeader, entries []RateLimitEntry) if err != nil { return fmt.Errorf("error sending request to %s: %w", endpoint, err) } + fmt.Println(resp.Status) defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) logrus.Infof("response status: %s, body: %s", resp.Status, respBody) diff --git a/nginx.conf b/nginx.conf index 5d2cea3..2b896d1 100644 --- a/nginx.conf +++ b/nginx.conf @@ -1,6 +1,5 @@ worker_processes 1; master_process on; -daemon off; error_log ./error.log debug; diff --git a/ngx_http_limit_req_rw_module.c b/ngx_http_limit_req_rw_module.c index b5ecba5..40daee8 100644 --- a/ngx_http_limit_req_rw_module.c +++ b/ngx_http_limit_req_rw_module.c @@ -43,10 +43,11 @@ typedef struct entities *Entities; // Array of entities } ngx_zone_data_t; -static ngx_zone_data_t *ngx_decode_msg_pack(ngx_http_request_t *r); +static ngx_int_t ngx_decode_msg_pack(ngx_http_request_t *r, ngx_zone_data_t *msg_pack); static ngx_int_t ngx_http_limit_req_read_handler(ngx_http_request_t *r); -static void ngx_http_limit_req_write_handler(ngx_http_request_t *r); +static void ngx_http_limit_req_write_post_handler(ngx_http_request_t *r); +static ngx_int_t ngx_http_limit_req_write_handler (ngx_http_request_t *r); static char *ngx_http_limit_req_rw_handler(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); @@ -99,151 +100,185 @@ static ngx_int_t ngx_http_limit_req_handler(ngx_http_request_t *r) if (r->method == NGX_HTTP_POST) { r->request_body_in_single_buf = 1; - return ngx_http_read_client_request_body(r, ngx_http_limit_req_write_handler); + return ngx_http_read_client_request_body(r, ngx_http_limit_req_write_post_handler); } return NGX_HTTP_SERVICE_UNAVAILABLE; } -static ngx_zone_data_t *ngx_decode_msg_pack(ngx_http_request_t *r) +static ngx_int_t ngx_decode_msg_pack(ngx_http_request_t *r, ngx_zone_data_t *ngx_zone_data) { - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: start decoding msgpack"); - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "0.0"); + ngx_chain_t *cl; + size_t len = 0; + u_char *data, *p; + size_t size, deserialized_size; + msgpack_zone mempool; + msgpack_object deserialized; + if (r->request_body == NULL || r->request_body->bufs == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no request body found"); - return NULL; + return NGX_HTTP_INTERNAL_SERVER_ERROR; } - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "1111111"); - ngx_chain_t *cl; - size_t len = 0; for (cl = r->request_body->bufs; cl; cl = cl->next) { len += cl->buf->last - cl->buf->pos; } - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "2"); - u_char *data = ngx_pnalloc(r->pool, len); + + data = ngx_pnalloc(r->pool, len); if (data == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for request body"); - return NULL; + return NGX_HTTP_INTERNAL_SERVER_ERROR; } - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "3"); - u_char *p = data; + + p = data; for (cl = r->request_body->bufs; cl; cl = cl->next) { - size_t size = cl->buf->last - cl->buf->pos; + size = cl->buf->last - cl->buf->pos; ngx_memcpy(p, cl->buf->pos, size); p += size; } - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "4"); - msgpack_zone mempool; + msgpack_zone_init(&mempool, 2048); - msgpack_object deserialized; msgpack_unpack((char *)data, len, NULL, &mempool, &deserialized); ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: deserialized type: %d", deserialized.type); - if (deserialized.type == MSGPACK_OBJECT_ARRAY) + if (deserialized.type != MSGPACK_OBJECT_ARRAY) { - uint32_t size = deserialized.via.array.size; - msgpack_object *items = deserialized.via.array.ptr; + msgpack_zone_destroy(&mempool); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } - header *hdr = ngx_pnalloc(r->pool, sizeof(header)); - if (hdr == NULL) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for header"); - return NULL; - } + deserialized_size = deserialized.via.array.size; + msgpack_object *items = deserialized.via.array.ptr; - if (items->via.array.size >= 1) - { - msgpack_object hdrKey = items[0].via.array.ptr[0]; - msgpack_object hdrNow = items[0].via.array.ptr[1]; - msgpack_object hdrNowMonotonic = items[0].via.array.ptr[2]; - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "Header Key type: %d", hdrKey.type); - hdr->Key.len = hdrKey.via.str.size; - u_char *keyData = ngx_palloc(r->pool, hdrKey.via.str.size); - if (keyData == NULL) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for key"); - return NULL; - } - ngx_memcpy(keyData, hdrKey.via.str.ptr, hdrKey.via.str.size); - hdr->Key.data = keyData; - hdr->Now = hdrNow.via.u64; - hdr->NowMonotonic = hdrNowMonotonic.via.u64; + header *hdr = ngx_pnalloc(r->pool, sizeof(header)); + if (hdr == NULL) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for header"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "Header Key value: %*s", hdr->Key); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "Header Now value: %ul", hdr->Now); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "Header NowMonotonic value: %ul", hdr->NowMonotonic); + if (items->via.array.size >= 1) + { + msgpack_object hdrKey = items[0].via.array.ptr[0]; + msgpack_object hdrNow = items[0].via.array.ptr[1]; + msgpack_object hdrNowMonotonic = items[0].via.array.ptr[2]; + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "Header Key type: %d", hdrKey.type); + hdr->Key.len = hdrKey.via.str.size; + u_char *keyData = ngx_palloc(r->pool, hdrKey.via.str.size); + if (keyData == NULL) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for key"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; } - ngx_zone_data_t *output = ngx_pnalloc(r->pool, sizeof(ngx_zone_data_t)); - output->Header = hdr; + ngx_memcpy(keyData, hdrKey.via.str.ptr, hdrKey.via.str.size); + hdr->Key.data = keyData; + hdr->Now = hdrNow.via.u64; + hdr->NowMonotonic = hdrNowMonotonic.via.u64; - entities *arr = ngx_pnalloc(r->pool, size * sizeof(entities)); - if (data == NULL) + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "Header Key value: %*s", hdr->Key); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "Header Now value: %ul", hdr->Now); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "Header NowMonotonic value: %ul", hdr->NowMonotonic); + } + ngx_zone_data->Header = hdr; + + entities *arr = ngx_pnalloc(r->pool, deserialized_size * sizeof(entities)); + if (data == NULL) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for array"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + for (uint32_t i = 1; i < items->via.array.size; i++) + { + msgpack_object iItemKey = items[i].via.array.ptr[0]; + msgpack_object iItemLast = items[i].via.array.ptr[1]; + msgpack_object iItemExcess = items[i].via.array.ptr[2]; + arr[i - 1].Key.len = iItemKey.via.str.size; + u_char *keyData = ngx_palloc(r->pool, iItemKey.via.str.size); + if (keyData == NULL) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for array"); - return NULL; + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for key"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; } - for (uint32_t i = 1; i < items->via.array.size; i++) - { - msgpack_object iItemKey = items[i].via.array.ptr[0]; - msgpack_object iItemLast = items[i].via.array.ptr[1]; - msgpack_object iItemExcess = items[i].via.array.ptr[2]; - arr[i - 1].Key.len = iItemKey.via.str.size; - u_char *keyData = ngx_palloc(r->pool, iItemKey.via.str.size); - if (keyData == NULL) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for key"); - return NULL; - } - ngx_memcpy(keyData, iItemKey.via.str.ptr, iItemKey.via.str.size); - arr[i - 1].Key.data = keyData; - arr[i - 1].Last = iItemLast.via.u64; - arr[i - 1].Excess = iItemExcess.via.u64; + ngx_memcpy(keyData, iItemKey.via.str.ptr, iItemKey.via.str.size); + arr[i - 1].Key.data = keyData; + arr[i - 1].Last = iItemLast.via.u64; + arr[i - 1].Excess = iItemExcess.via.u64; - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "Key [%d] value: %*s", i, arr[i - 1].Key); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "Last [%d] value: %ul", i, arr[i - 1].Last); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "Excess [%d] value: %ul", i, arr[i - 1].Excess); - } - output->Entities = arr; - msgpack_zone_destroy(&mempool); - return output; + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "Key [%d] value: %*s", i, arr[i - 1].Key); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "Last [%d] value: %ul", i, arr[i - 1].Last); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "Excess [%d] value: %ul", i, arr[i - 1].Excess); } + ngx_zone_data->Entities = arr; msgpack_zone_destroy(&mempool); - return NULL; + return NGX_OK; } -static void ngx_http_limit_req_write_handler(ngx_http_request_t *r) +static void ngx_http_limit_req_write_post_handler(ngx_http_request_t *r) { - r->request_body_in_single_buf = 1; - ngx_zone_data_t *msg_pack = ngx_decode_msg_pack(r); + ngx_int_t rc; - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: msg_pack is %*s", msg_pack->Header->Key); + rc = ngx_http_limit_req_write_handler(r); + + if (rc != NGX_OK) { + ngx_http_finalize_request(r, rc); + return; + } + ngx_str_t response = ngx_string("OK\n"); + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = response.len; + ngx_http_send_header(r); + + ngx_buf_t *b = ngx_create_temp_buf(r->pool, response.len); + ngx_memcpy(b->pos, response.data, response.len); + b->last = b->pos + response.len; + b->last_buf = 1; + + ngx_chain_t out = {.buf = b, .next = NULL}; + ngx_http_output_filter(r, &out); +} + +static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + ngx_zone_data_t *msg_pack = NULL; ngx_str_t zone_name; ngx_shm_zone_t *shm_zone; volatile ngx_list_part_t *part; ngx_uint_t i; + if (r != r->main) { + return NGX_DECLINED; + } + + msg_pack = ngx_pnalloc(r->pool, sizeof(ngx_zone_data_t)); + if (msg_pack == NULL) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "ngx_http_limit_req_rw_module: failed to allocate memory for msg_pack"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + rc = ngx_decode_msg_pack(r, msg_pack); + if (rc != NGX_OK) + { + return rc; + } + strip_zone_name_from_uri(&r->uri, &zone_name); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_http_limit_req_rw_module: for URI %V", &zone_name); part = &ngx_cycle->shared_memory.part; - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: part->nelts %d", part->nelts); shm_zone = part->elts; - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: part->elts %p", shm_zone); for (i = 0; /* void */; i++) { @@ -362,18 +397,7 @@ static void ngx_http_limit_req_write_handler(ngx_http_request_t *r) ngx_http_output_filter(r, &out); } - ngx_str_t response = ngx_string("logged\n"); - r->headers_out.status = NGX_HTTP_OK; - r->headers_out.content_length_n = response.len; - ngx_http_send_header(r); - - ngx_buf_t *b = ngx_create_temp_buf(r->pool, response.len); - ngx_memcpy(b->pos, response.data, response.len); - b->last = b->pos + response.len; - b->last_buf = 1; - - ngx_chain_t out = {.buf = b, .next = NULL}; - ngx_http_output_filter(r, &out); + return NGX_OK; } static ngx_int_t ngx_http_limit_req_read_handler(ngx_http_request_t *r) @@ -398,27 +422,17 @@ static ngx_int_t ngx_http_limit_req_read_handler(ngx_http_request_t *r) "ngx_http_limit_req_rw_module: clcf is NULL"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } - ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, - "ngx_http_limit_req_rw_module: Processing request for URI: %V", &r->uri); - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: clcf->name: %V", &clcf->name); - // When request location is /api - if (clcf->name.len == r->uri.len) - { - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + ngx_log_debug1(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: Processing request for URI: %*s", &r->uri); + ngx_log_debug1(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: clcf->name: %*s", &clcf->name); + if (clcf->name.len == r->uri.len) { + ngx_log_debug(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: dumping rate limit zones"); - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: r: %p", r); - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: r->pool: %p", r->pool); rc = dump_rate_limit_zones(r, b); - // When request location is /api/{zone_name} - } - else - { + } else { strip_zone_name_from_uri(&r->uri, &zone_name); - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "zone name: %.*s", - (int)zone_name.len, zone_name.data); + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "zone name: %*s", zone_name); last_greater_equal_arg.len = 0; last_greater_equal = 0; if (r->args.len) @@ -435,8 +449,7 @@ static ngx_int_t ngx_http_limit_req_read_handler(ngx_http_request_t *r) rc = dump_req_zone(r->pool, b, &zone_name, (ngx_uint_t)last_greater_equal); } - if (rc != NGX_OK) - { + if (rc != NGX_OK) { return rc; } From c3ebb4edc03edb0cf5152a394ce6ef6e854d2efc Mon Sep 17 00:00:00 2001 From: ravilock Date: Thu, 26 Jun 2025 17:42:23 -0300 Subject: [PATCH 14/18] refactor(module): cleanup and modularize shared memory access Refactored ngx_http_limit_req_rw_module.c to modularize and clean up shared memory zone access. Introduced a helper function find_rate_limit_shm_zone_by_name to encapsulate the logic for locating rate limit zones by name, reducing code duplication and improving readability. Updated both read and write handlers to use this helper. Also improved logging and error handling for missing or invalid zones. Updated .gitignore to exclude .cache directory. Enhanced config script to detect both 'msgpack-c' and 'msgpack' pkg-config packages for better compatibility across platforms. --- .gitignore | 3 + config | 24 +- ngx_http_limit_req_module.h | 58 ++-- ngx_http_limit_req_rw_module.c | 516 +++++++++++++++------------------ 4 files changed, 280 insertions(+), 321 deletions(-) diff --git a/.gitignore b/.gitignore index 334bfef..7cd2ca3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Prerequisites *.d +# Cache +.cache + # Object files *.o *.ko diff --git a/config b/config index c35c6ac..6e8998b 100644 --- a/config +++ b/config @@ -3,7 +3,27 @@ ngx_module_type=HTTP ngx_module_name=ngx_http_limit_req_rw_module ngx_module_srcs="$ngx_addon_dir/ngx_http_limit_req_rw_module.c" -CFLAGS="$CFLAGS `pkg-config --cflags 'msgpack-c = 6.1.0'`" -CORE_LIBS="$CORE_LIBS `pkg-config --libs 'msgpack-c = 6.1.0'`" +# Detect platform +OS_NAME="$(uname -s)" + +# Default values +MSGPACK_PKG_NAME="" +MSGPACK_VERSION="" + +# Try to detect msgpack library +if pkg-config --exists 'msgpack-c'; then + MSGPACK_PKG_NAME="msgpack-c" + MSGPACK_VERSION="6.1.0" # Optional: only enforce version if strictly necessary +elif pkg-config --exists 'msgpack'; then + MSGPACK_PKG_NAME="msgpack" + MSGPACK_VERSION="3.1.0" +else + echo "Error: Neither 'msgpack-c' nor 'msgpack' pkg-config package found." + exit 1 +fi + +# Add flags +CFLAGS="$CFLAGS $(pkg-config --cflags "$MSGPACK_PKG_NAME")" +CORE_LIBS="$CORE_LIBS $(pkg-config --libs "$MSGPACK_PKG_NAME")" . auto/module diff --git a/ngx_http_limit_req_module.h b/ngx_http_limit_req_module.h index e301d4b..fbaf191 100644 --- a/ngx_http_limit_req_module.h +++ b/ngx_http_limit_req_module.h @@ -6,48 +6,46 @@ #include #include - typedef struct { - ngx_array_t limits; - ngx_uint_t limit_log_level; - ngx_uint_t delay_log_level; - ngx_uint_t status_code; - ngx_flag_t dry_run; +typedef struct { + ngx_array_t limits; + ngx_uint_t limit_log_level; + ngx_uint_t delay_log_level; + ngx_uint_t status_code; + ngx_flag_t dry_run; } ngx_http_limit_req_conf_t; typedef struct { - ngx_shm_zone_t *shm_zone; - /* integer value, 1 corresponds to 0.001 r/s */ - ngx_uint_t burst; - ngx_uint_t delay; + ngx_shm_zone_t *shm_zone; + /* integer value, 1 corresponds to 0.001 r/s */ + ngx_uint_t burst; + ngx_uint_t delay; } ngx_http_limit_req_limit_t; typedef struct { - u_char color; - u_char dummy; - u_short len; - ngx_queue_t queue; - ngx_msec_t last; - /* integer value, 1 corresponds to 0.001 r/s */ - ngx_uint_t excess; - ngx_uint_t count; - u_char data[1]; + u_char color; + u_char dummy; + u_short len; + ngx_queue_t queue; + ngx_msec_t last; + /* integer value, 1 corresponds to 0.001 r/s */ + ngx_uint_t excess; + ngx_uint_t count; + u_char data[1]; } ngx_http_limit_req_node_t; - typedef struct { - ngx_rbtree_t rbtree; - ngx_rbtree_node_t sentinel; - ngx_queue_t queue; + ngx_rbtree_t rbtree; + ngx_rbtree_node_t sentinel; + ngx_queue_t queue; } ngx_http_limit_req_shctx_t; typedef struct { - ngx_http_limit_req_shctx_t *sh; - ngx_slab_pool_t *shpool; - /* integer value, 1 corresponds to 0.001 r/s */ - ngx_uint_t rate; - ngx_http_complex_value_t key; - ngx_http_limit_req_node_t *node; + ngx_http_limit_req_shctx_t *sh; + ngx_slab_pool_t *shpool; + /* integer value, 1 corresponds to 0.001 r/s */ + ngx_uint_t rate; + ngx_http_complex_value_t key; + ngx_http_limit_req_node_t *node; } ngx_http_limit_req_ctx_t; - extern ngx_module_t ngx_http_limit_req_module; diff --git a/ngx_http_limit_req_rw_module.c b/ngx_http_limit_req_rw_module.c index 40daee8..d850baa 100644 --- a/ngx_http_limit_req_rw_module.c +++ b/ngx_http_limit_req_rw_module.c @@ -2,59 +2,49 @@ TODO: copyright */ +#include "ngx_http_limit_req_module.h" +#include #include -#include -#include -#include -#include #include #include -#include -#include +#include #include #include -#include -#include #include #include -#include - -#include "ngx_http_limit_req_module.h" const int MAX_NUMBER_OF_RATE_LIMIT_ELEMENTS = 30 * 1000; -typedef struct -{ +typedef struct { ngx_str_t Key; uint64_t Last; uint64_t Excess; } entities; -typedef struct -{ +typedef struct { ngx_str_t Key; // Key of the rate limit zone uint64_t Now; // Current timestamp in milliseconds uint64_t NowMonotonic; // Current monotonic timestamp in milliseconds } header; -typedef struct -{ +typedef struct { header *Header; // Header information entities *Entities; // Array of entities } ngx_zone_data_t; -static ngx_int_t ngx_decode_msg_pack(ngx_http_request_t *r, ngx_zone_data_t *msg_pack); +static ngx_int_t ngx_decode_msg_pack(ngx_http_request_t *r, + ngx_zone_data_t *msg_pack); static ngx_int_t ngx_http_limit_req_read_handler(ngx_http_request_t *r); static void ngx_http_limit_req_write_post_handler(ngx_http_request_t *r); -static ngx_int_t ngx_http_limit_req_write_handler (ngx_http_request_t *r); +static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r); static char *ngx_http_limit_req_rw_handler(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static void strip_zone_name_from_uri(ngx_str_t *uri, ngx_str_t *zone_name); static ngx_int_t dump_rate_limit_zones(ngx_http_request_t *r, ngx_buf_t *b); -static ngx_int_t dump_req_zone(ngx_pool_t *pool, ngx_buf_t *b, - ngx_str_t *zone_name, ngx_uint_t last_greater_equal); +static ngx_shm_zone_t *find_rate_limit_shm_zone_by_name(ngx_http_request_t *r, + ngx_str_t zone_name); static ngx_int_t dump_req_limits(ngx_pool_t *pool, ngx_shm_zone_t *shm_zone, ngx_buf_t *buf, ngx_uint_t last_greater_equal); static ngx_command_t ngx_http_limit_req_rw_commands[] = { @@ -91,22 +81,20 @@ ngx_module_t ngx_http_limit_req_rw_module = {NGX_MODULE_V1, NULL, NGX_MODULE_V1_PADDING}; -static ngx_int_t ngx_http_limit_req_handler(ngx_http_request_t *r) -{ - if (r->method == NGX_HTTP_GET) - { +static ngx_int_t ngx_http_limit_req_handler(ngx_http_request_t *r) { + if (r->method == NGX_HTTP_GET) { return ngx_http_limit_req_read_handler(r); } - if (r->method == NGX_HTTP_POST) - { + if (r->method == NGX_HTTP_POST) { r->request_body_in_single_buf = 1; - return ngx_http_read_client_request_body(r, ngx_http_limit_req_write_post_handler); + return ngx_http_read_client_request_body( + r, ngx_http_limit_req_write_post_handler); } return NGX_HTTP_SERVICE_UNAVAILABLE; } -static ngx_int_t ngx_decode_msg_pack(ngx_http_request_t *r, ngx_zone_data_t *ngx_zone_data) -{ +static ngx_int_t ngx_decode_msg_pack(ngx_http_request_t *r, + ngx_zone_data_t *ngx_zone_data) { ngx_chain_t *cl; size_t len = 0; u_char *data, *p; @@ -114,27 +102,24 @@ static ngx_int_t ngx_decode_msg_pack(ngx_http_request_t *r, ngx_zone_data_t *ngx msgpack_zone mempool; msgpack_object deserialized; - if (r->request_body == NULL || r->request_body->bufs == NULL) - { + if (r->request_body == NULL || r->request_body->bufs == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no request body found"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } - for (cl = r->request_body->bufs; cl; cl = cl->next) - { + for (cl = r->request_body->bufs; cl; cl = cl->next) { len += cl->buf->last - cl->buf->pos; } data = ngx_pnalloc(r->pool, len); - if (data == NULL) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for request body"); + if (data == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "failed to allocate memory for request body"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } p = data; - for (cl = r->request_body->bufs; cl; cl = cl->next) - { + for (cl = r->request_body->bufs; cl; cl = cl->next) { size = cl->buf->last - cl->buf->pos; ngx_memcpy(p, cl->buf->pos, size); p += size; @@ -144,9 +129,9 @@ static ngx_int_t ngx_decode_msg_pack(ngx_http_request_t *r, ngx_zone_data_t *ngx msgpack_unpack((char *)data, len, NULL, &mempool, &deserialized); ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: deserialized type: %d", deserialized.type); - if (deserialized.type != MSGPACK_OBJECT_ARRAY) - { + "ngx_http_limit_req_rw_module: deserialized type: %d", + deserialized.type); + if (deserialized.type != MSGPACK_OBJECT_ARRAY) { msgpack_zone_destroy(&mempool); return NGX_HTTP_INTERNAL_SERVER_ERROR; } @@ -155,24 +140,23 @@ static ngx_int_t ngx_decode_msg_pack(ngx_http_request_t *r, ngx_zone_data_t *ngx msgpack_object *items = deserialized.via.array.ptr; header *hdr = ngx_pnalloc(r->pool, sizeof(header)); - if (hdr == NULL) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for header"); + if (hdr == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "failed to allocate memory for header"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } - if (items->via.array.size >= 1) - { + if (items->via.array.size >= 1) { msgpack_object hdrKey = items[0].via.array.ptr[0]; msgpack_object hdrNow = items[0].via.array.ptr[1]; msgpack_object hdrNowMonotonic = items[0].via.array.ptr[2]; - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "Header Key type: %d", hdrKey.type); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Header Key type: %d", + hdrKey.type); hdr->Key.len = hdrKey.via.str.size; u_char *keyData = ngx_palloc(r->pool, hdrKey.via.str.size); - if (keyData == NULL) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for key"); + if (keyData == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "failed to allocate memory for key"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_memcpy(keyData, hdrKey.via.str.ptr, hdrKey.via.str.size); @@ -180,31 +164,30 @@ static ngx_int_t ngx_decode_msg_pack(ngx_http_request_t *r, ngx_zone_data_t *ngx hdr->Now = hdrNow.via.u64; hdr->NowMonotonic = hdrNowMonotonic.via.u64; - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "Header Key value: %*s", hdr->Key); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "Header Now value: %ul", hdr->Now); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Header Key value: %*s", + hdr->Key); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Header Now value: %ul", + hdr->Now); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Header NowMonotonic value: %ul", hdr->NowMonotonic); } ngx_zone_data->Header = hdr; entities *arr = ngx_pnalloc(r->pool, deserialized_size * sizeof(entities)); - if (data == NULL) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for array"); + if (data == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "failed to allocate memory for array"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } - for (uint32_t i = 1; i < items->via.array.size; i++) - { + for (uint32_t i = 1; i < items->via.array.size; i++) { msgpack_object iItemKey = items[i].via.array.ptr[0]; msgpack_object iItemLast = items[i].via.array.ptr[1]; msgpack_object iItemExcess = items[i].via.array.ptr[2]; arr[i - 1].Key.len = iItemKey.via.str.size; u_char *keyData = ngx_palloc(r->pool, iItemKey.via.str.size); - if (keyData == NULL) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for key"); + if (keyData == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "failed to allocate memory for key"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_memcpy(keyData, iItemKey.via.str.ptr, iItemKey.via.str.size); @@ -212,20 +195,19 @@ static ngx_int_t ngx_decode_msg_pack(ngx_http_request_t *r, ngx_zone_data_t *ngx arr[i - 1].Last = iItemLast.via.u64; arr[i - 1].Excess = iItemExcess.via.u64; - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "Key [%d] value: %*s", i, arr[i - 1].Key); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "Last [%d] value: %ul", i, arr[i - 1].Last); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "Excess [%d] value: %ul", i, arr[i - 1].Excess); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Key [%d] value: %*s", i, + arr[i - 1].Key); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Last [%d] value: %ul", i, + arr[i - 1].Last); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Excess [%d] value: %ul", + i, arr[i - 1].Excess); } ngx_zone_data->Entities = arr; msgpack_zone_destroy(&mempool); return NGX_OK; } -static void ngx_http_limit_req_write_post_handler(ngx_http_request_t *r) -{ +static void ngx_http_limit_req_write_post_handler(ngx_http_request_t *r) { ngx_int_t rc; rc = ngx_http_limit_req_write_handler(r); @@ -249,204 +231,185 @@ static void ngx_http_limit_req_write_post_handler(ngx_http_request_t *r) ngx_http_output_filter(r, &out); } -static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r) -{ +static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r) { ngx_int_t rc; ngx_zone_data_t *msg_pack = NULL; ngx_str_t zone_name; ngx_shm_zone_t *shm_zone; - volatile ngx_list_part_t *part; - ngx_uint_t i; + ngx_http_limit_req_ctx_t *ctx; if (r != r->main) { - return NGX_DECLINED; + return NGX_DECLINED; } msg_pack = ngx_pnalloc(r->pool, sizeof(ngx_zone_data_t)); - if (msg_pack == NULL) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "ngx_http_limit_req_rw_module: failed to allocate memory for msg_pack"); + if (msg_pack == NULL) { + ngx_log_error( + NGX_LOG_ERR, r->connection->log, 0, + "ngx_http_limit_req_rw_module: failed to allocate memory for msg_pack"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } rc = ngx_decode_msg_pack(r, msg_pack); - if (rc != NGX_OK) - { + if (rc != NGX_OK) { return rc; } strip_zone_name_from_uri(&r->uri, &zone_name); + shm_zone = find_rate_limit_shm_zone_by_name(r, zone_name); + if (shm_zone == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "ngx_http_limit_req_rw_module: rate limit zone %*s not found", + zone_name); + return NGX_HTTP_NOT_FOUND; + } - part = &ngx_cycle->shared_memory.part; - shm_zone = part->elts; - - for (i = 0; /* void */; i++) - { - if (i >= part->nelts) - { - if (part->next == NULL) - { - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: part->next is NULL, breaking out of loop"); - break; - } - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: part->next is not NULL, advancing"); - part = part->next; - shm_zone = part->elts; - i = 0; - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: new part->nelts %d", part->nelts); - } + ctx = shm_zone->data; + ngx_shmtx_lock(&ctx->shpool->mutex); - if (shm_zone == NULL) - { - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: shm_zone is NULL, continuing"); + struct in_addr addr; + inet_pton(AF_INET, "127.0.0.1", + &addr); // TODO: Replace with actual client IP retrieval logic + ngx_str_t key; + key.data = (u_char *)&addr; + key.len = sizeof(addr); // 4 + + uint32_t hash = ngx_crc32_short(key.data, key.len); + + ngx_rbtree_node_t *node = ctx->sh->rbtree.root; + ngx_rbtree_node_t *sentinel = ctx->sh->rbtree.sentinel; + ngx_http_limit_req_node_t *lr = NULL; + int found = 0; + while (node != sentinel) { + if (hash < node->key) { + node = node->left; continue; } - - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: comparing shm_zone tag"); - if (shm_zone[i].tag != &ngx_http_limit_req_module) - { - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: shm_zone tag is not limit_req_module, continuing"); + if (hash > node->key) { + node = node->right; continue; } - - ngx_http_limit_req_ctx_t *ctx = shm_zone[i].data; - ngx_shmtx_lock(&ctx->shpool->mutex); - - struct in_addr addr; - inet_pton(AF_INET, "127.0.0.1", &addr); // TODO: Replace with actual client IP retrieval logic - ngx_str_t key; - key.data = (u_char *)&addr; - key.len = sizeof(addr); // 4 - - uint32_t hash = ngx_crc32_short(key.data, key.len); - - ngx_rbtree_node_t *node = ctx->sh->rbtree.root; - ngx_rbtree_node_t *sentinel = ctx->sh->rbtree.sentinel; - ngx_http_limit_req_node_t *lr = NULL; - int found = 0; - while (node != sentinel) - { - if (hash < node->key) - { - node = node->left; - continue; - } - if (hash > node->key) - { - node = node->right; - continue; - } - lr = (ngx_http_limit_req_node_t *)&node->color; - if (lr->len == key.len && ngx_memcmp(lr->data, key.data, key.len) == 0) - { - lr->excess = 777; - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: found existing node, excess set to 777"); - found = 1; - break; - } - node = (ngx_memn2cmp(key.data, lr->data, key.len, lr->len) < 0) ? node->left : node->right; + lr = (ngx_http_limit_req_node_t *)&node->color; + if (lr->len == key.len && ngx_memcmp(lr->data, key.data, key.len) == 0) { + lr->excess = 777; + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: found existing node, " + "excess set to 777"); + found = 1; + break; } - if (!found) - { - size_t size = offsetof(ngx_rbtree_node_t, color) + offsetof(ngx_http_limit_req_node_t, data) + key.len; - node = ngx_slab_alloc_locked(ctx->shpool, size); - if (node == NULL) - { - ngx_shmtx_unlock(&ctx->shpool->mutex); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for rate limit node"); - r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; - } - node->key = hash; - lr = (ngx_http_limit_req_node_t *)&node->color; - - lr->len = (u_short)key.len; - lr->excess = 777; // TODO: replace with the value received from msgPack - lr->count = 0; - ngx_memcpy(lr->data, key.data, key.len); - - ngx_rbtree_insert(&ctx->sh->rbtree, node); - ngx_queue_insert_head(&ctx->sh->queue, &lr->queue); - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: new node created, excess set to 777"); + node = (ngx_memn2cmp(key.data, lr->data, key.len, lr->len) < 0) + ? node->left + : node->right; + } + if (!found) { + size_t size = offsetof(ngx_rbtree_node_t, color) + + offsetof(ngx_http_limit_req_node_t, data) + key.len; + node = ngx_slab_alloc_locked(ctx->shpool, size); + if (node == NULL) { + ngx_shmtx_unlock(&ctx->shpool->mutex); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "failed to allocate memory for rate limit node"); + r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; } - ngx_shmtx_unlock(&ctx->shpool->mutex); + node->key = hash; + lr = (ngx_http_limit_req_node_t *)&node->color; + + lr->len = (u_short)key.len; + lr->excess = 777; // TODO: replace with the value received from msgPack + lr->count = 0; + ngx_memcpy(lr->data, key.data, key.len); + + ngx_rbtree_insert(&ctx->sh->rbtree, node); + ngx_queue_insert_head(&ctx->sh->queue, &lr->queue); + ngx_log_error( + NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: new node created, excess set to 777"); + } + ngx_shmtx_unlock(&ctx->shpool->mutex); - ngx_str_t response = ngx_string("OK\n"); + ngx_str_t response = ngx_string("OK\n"); - r->headers_out.status = NGX_HTTP_OK; - r->headers_out.content_length_n = response.len; - ngx_str_set(&r->headers_out.content_type, "text/plain"); + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = response.len; + ngx_str_set(&r->headers_out.content_type, "text/plain"); - ngx_int_t rc = ngx_http_send_header(r); - if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) - { - r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; - } - ngx_buf_t *b = ngx_create_temp_buf(r->pool, response.len); - if (b == NULL) - { - r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; - } - ngx_memcpy(b->pos, response.data, response.len); - b->last = b->pos + response.len; - b->last_buf = 1; + rc = ngx_http_send_header(r); + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { + r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ngx_buf_t *b = ngx_create_temp_buf(r->pool, response.len); + if (b == NULL) { + r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ngx_memcpy(b->pos, response.data, response.len); + b->last = b->pos + response.len; + b->last_buf = 1; - ngx_chain_t out; - out.buf = b; - out.next = NULL; + ngx_chain_t out; + out.buf = b; + out.next = NULL; - ngx_http_output_filter(r, &out); - } + ngx_http_output_filter(r, &out); return NGX_OK; } -static ngx_int_t ngx_http_limit_req_read_handler(ngx_http_request_t *r) -{ +static ngx_int_t ngx_http_limit_req_read_handler(ngx_http_request_t *r) { ngx_str_t content_type, zone_name, last_greater_equal_arg; ngx_int_t last_greater_equal; ngx_int_t rc; ngx_buf_t *b; ngx_chain_t out; ngx_http_core_loc_conf_t *clcf; + ngx_shm_zone_t *shm_zone; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); b = ngx_create_temp_buf(r->pool, 1024 * 1024); - if (b == NULL) - { + if (b == NULL) { + ngx_log_error( + NGX_LOG_ERR, r->connection->log, 0, + "ngx_http_limit_req_rw_module: failed to create temporary buffer"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } - if (clcf == NULL) - { + if (clcf == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_http_limit_req_rw_module: clcf is NULL"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } - ngx_log_debug1(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: Processing request for URI: %*s", &r->uri); - ngx_log_debug1(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: clcf->name: %*s", &clcf->name); + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: request URI: %*s", r->uri); + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: clcf->name: %*s", clcf->name); if (clcf->name.len == r->uri.len) { - ngx_log_debug(NGX_LOG_DEBUG, r->connection->log, 0, + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: dumping rate limit zones"); rc = dump_rate_limit_zones(r, b); } else { strip_zone_name_from_uri(&r->uri, &zone_name); - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "zone name: %*s", zone_name); + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "zone name: %*s", + zone_name); last_greater_equal_arg.len = 0; last_greater_equal = 0; - if (r->args.len) - { - if (ngx_http_arg(r, (u_char *)"last_greater_equal", 18, &last_greater_equal_arg) == NGX_OK) - { - last_greater_equal = ngx_atoi(last_greater_equal_arg.data, last_greater_equal_arg.len); - if (last_greater_equal == NGX_ERROR || last_greater_equal < 0) - { + if (r->args.len) { + if (ngx_http_arg(r, (u_char *)"last_greater_equal", 18, + &last_greater_equal_arg) == NGX_OK) { + last_greater_equal = + ngx_atoi(last_greater_equal_arg.data, last_greater_equal_arg.len); + if (last_greater_equal == NGX_ERROR || last_greater_equal < 0) { return NGX_HTTP_BAD_REQUEST; } } } - rc = dump_req_zone(r->pool, b, &zone_name, (ngx_uint_t)last_greater_equal); + shm_zone = find_rate_limit_shm_zone_by_name(r, zone_name); + if (shm_zone == NULL) { + ngx_log_error( + NGX_LOG_ERR, r->connection->log, 0, + "ngx_http_limit_req_rw_module: rate limit zone %*s not found", + zone_name); + return NGX_HTTP_NOT_FOUND; + } + rc = dump_req_limits(r->pool, shm_zone, b, last_greater_equal); } if (rc != NGX_OK) { @@ -465,8 +428,7 @@ static ngx_int_t ngx_http_limit_req_read_handler(ngx_http_request_t *r) out.next = NULL; rc = ngx_http_send_header(r); - if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) - { + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return rc; } @@ -474,8 +436,7 @@ static ngx_int_t ngx_http_limit_req_read_handler(ngx_http_request_t *r) } static char *ngx_http_limit_req_rw_handler(ngx_conf_t *cf, ngx_command_t *cmd, - void *conf) -{ + void *conf) { ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); @@ -484,13 +445,11 @@ static char *ngx_http_limit_req_rw_handler(ngx_conf_t *cf, ngx_command_t *cmd, return NGX_CONF_OK; } -static void strip_zone_name_from_uri(ngx_str_t *uri, ngx_str_t *zone_name) -{ +static void strip_zone_name_from_uri(ngx_str_t *uri, ngx_str_t *zone_name) { zone_name->data = (u_char *)ngx_strlchr(uri->data, uri->data + uri->len, '/'); zone_name->len = 0; - if (zone_name->data) - { + if (zone_name->data) { zone_name->data = (u_char *)(ngx_strlchr(zone_name->data + 1, uri->data + uri->len, '/') + 1); @@ -499,26 +458,20 @@ static void strip_zone_name_from_uri(ngx_str_t *uri, ngx_str_t *zone_name) } static inline int msgpack_ngx_buf_write(void *data, const char *buf, - size_t len) -{ + size_t len) { ngx_buf_t *b = (ngx_buf_t *)data; b->last = ngx_cpymem(b->last, buf, len); return 0; } -static ngx_int_t dump_rate_limit_zones(ngx_http_request_t *r, ngx_buf_t *buf) -{ +static ngx_int_t dump_rate_limit_zones(ngx_http_request_t *r, ngx_buf_t *buf) { ngx_array_t *zones; ngx_str_t **zone_name; ngx_uint_t i; ngx_shm_zone_t *shm_zone; volatile ngx_list_part_t *part; - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: dump_rate_limit_zones called"); - - if (ngx_cycle == NULL) - { + if (ngx_cycle == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_http_limit_req_rw_module: ngx_cycle is NULL"); return NGX_HTTP_INTERNAL_SERVER_ERROR; @@ -527,8 +480,7 @@ static ngx_int_t dump_rate_limit_zones(ngx_http_request_t *r, ngx_buf_t *buf) msgpack_packer pk; msgpack_packer_init(&pk, buf, msgpack_ngx_buf_write); zones = ngx_array_create(r->pool, 0, sizeof(ngx_str_t *)); - if (zones == NULL) - { + if (zones == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_http_limit_req_rw_module: failed to create zones array"); return NGX_HTTP_INTERNAL_SERVER_ERROR; @@ -539,47 +491,46 @@ static ngx_int_t dump_rate_limit_zones(ngx_http_request_t *r, ngx_buf_t *buf) "ngx_http_limit_req_rw_module: part->nelts %d", part->nelts); shm_zone = part->elts; - for (i = 0; /* void */; i++) - { + for (i = 0; /* void */; i++) { - if (i >= part->nelts) - { - if (part->next == NULL) - { + if (i >= part->nelts) { + if (part->next == NULL) { ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: part->next is NULL, breaking out of loop"); + "ngx_http_limit_req_rw_module: part->next is NULL, " + "breaking out of loop"); break; } - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: part->next is not NULL, advancing"); + ngx_log_error( + NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: part->next is not NULL, advancing"); part = part->next; shm_zone = part->elts; i = 0; ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: new part->nelts %d", part->nelts); + "ngx_http_limit_req_rw_module: new part->nelts %d", + part->nelts); } - if (shm_zone == NULL) - { + if (shm_zone == NULL) { ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: shm_zone is NULL, continuing"); + "ngx_http_limit_req_rw_module: shm_zone is NULL, skipping"); continue; } ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: comparing shm_zone tag"); - if (shm_zone[i].tag != &ngx_http_limit_req_module) - { + if (shm_zone[i].tag != &ngx_http_limit_req_module) { ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: shm_zone tag is not limit_req_module, continuing"); + "ngx_http_limit_req_rw_module: shm_zone tag is not " + "limit_req_module, skipping"); continue; } - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: pushing new zone struct to array"); + ngx_log_error( + NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: pushing new zone struct to array"); zone_name = ngx_array_push(zones); - if (zone_name == NULL) - { + if (zone_name == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_http_limit_req_rw_module: failed to push zone name"); return NGX_HTTP_INTERNAL_SERVER_ERROR; @@ -595,14 +546,11 @@ static ngx_int_t dump_rate_limit_zones(ngx_http_request_t *r, ngx_buf_t *buf) ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "ngx_http_limit_req_rw_module: packing array of zones"); msgpack_pack_array(&pk, zones->nelts); - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: packing zone name"); zone_name = zones->elts; - for (i = 0; i < zones->nelts; i++) - { + for (i = 0; i < zones->nelts; i++) { ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: packing zone name %.*s", - (int)zone_name[i]->len, zone_name[i]->data); + "ngx_http_limit_req_rw_module: packing zone %*s", + *zone_name[i]); msgpack_pack_str(&pk, zone_name[i]->len); msgpack_pack_str_body(&pk, zone_name[i]->data, zone_name[i]->len); } @@ -610,28 +558,25 @@ static ngx_int_t dump_rate_limit_zones(ngx_http_request_t *r, ngx_buf_t *buf) return NGX_OK; } -static ngx_int_t dump_req_zone(ngx_pool_t *pool, ngx_buf_t *b, - ngx_str_t *zone_name, ngx_uint_t last_greater_equal) -{ +static ngx_shm_zone_t *find_rate_limit_shm_zone_by_name(ngx_http_request_t *r, + ngx_str_t zone_name) { ngx_uint_t i; ngx_shm_zone_t *shm_zone; volatile ngx_list_part_t *part; - if (ngx_cycle == NULL) - { - return NGX_HTTP_INTERNAL_SERVER_ERROR; + if (ngx_cycle == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "ngx_http_limit_req_rw_module: ngx_cycle is NULL"); + return NULL; } part = &ngx_cycle->shared_memory.part; shm_zone = part->elts; - for (i = 0; /* void */; i++) - { + for (i = 0; /* void */; i++) { - if (i >= part->nelts) - { - if (part->next == NULL) - { + if (i >= part->nelts) { + if (part->next == NULL) { break; } part = part->next; @@ -639,32 +584,32 @@ static ngx_int_t dump_req_zone(ngx_pool_t *pool, ngx_buf_t *b, i = 0; } - if (shm_zone == NULL) - { + if (shm_zone == NULL) { continue; } - if (shm_zone[i].tag != &ngx_http_limit_req_module) - { + if (shm_zone[i].tag != &ngx_http_limit_req_module) { continue; } - if (shm_zone[i].shm.name.len != zone_name->len) - { + if (shm_zone[i].shm.name.len != zone_name.len) { continue; } - if (ngx_strncmp(zone_name->data, shm_zone[i].shm.name.data, zone_name->len) == 0) - { - return dump_req_limits(pool, &shm_zone[i], b, last_greater_equal); + if (ngx_strncmp(zone_name.data, shm_zone[i].shm.name.data, zone_name.len) == + 0) { + return shm_zone; } } - return NGX_HTTP_NOT_FOUND; + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "ngx_http_limit_req_rw_module: rate limit zone %*s not found", + zone_name); + return NULL; } static ngx_int_t dump_req_limits(ngx_pool_t *pool, ngx_shm_zone_t *shm_zone, - ngx_buf_t *buf, ngx_uint_t last_greater_equal) -{ + ngx_buf_t *buf, + ngx_uint_t last_greater_equal) { ngx_http_limit_req_ctx_t *ctx; ngx_queue_t *head, *q, *last; ngx_http_limit_req_node_t *lr; @@ -687,14 +632,9 @@ static ngx_int_t dump_req_limits(ngx_pool_t *pool, ngx_shm_zone_t *shm_zone, msgpack_pack_uint64(&pk, now); msgpack_pack_uint64(&pk, now_monotonic); - ngx_log_debug4( - NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, "shm.name %p -> %.*s - rate: %lu", - ctx, (int)shm_zone->shm.name.len, shm_zone->shm.name.data, ctx->rate); - ngx_shmtx_lock(&ctx->shpool->mutex); - if (ngx_queue_empty(&ctx->sh->queue)) - { + if (ngx_queue_empty(&ctx->sh->queue)) { ngx_shmtx_unlock(&ctx->shpool->mutex); return NGX_OK; } @@ -703,11 +643,9 @@ static ngx_int_t dump_req_limits(ngx_pool_t *pool, ngx_shm_zone_t *shm_zone, last = ngx_queue_last(head); q = head; - for (i = 0; q != last && i < MAX_NUMBER_OF_RATE_LIMIT_ELEMENTS; i++) - { + for (i = 0; q != last && i < MAX_NUMBER_OF_RATE_LIMIT_ELEMENTS; i++) { lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue); - if (last_greater_equal != 0 && lr->last < last_greater_equal) - { + if (last_greater_equal != 0 && lr->last < last_greater_equal) { break; } msgpack_pack_array(&pk, 3); From 7fefb18ba15b1498b3c7c1641c46c45b7aea7450 Mon Sep 17 00:00:00 2001 From: Ezequiel Lopes dos Reis Junior Date: Fri, 27 Jun 2025 10:16:04 -0300 Subject: [PATCH 15/18] refactor: improve code readability and consistency in ngx_http_limit_req_rw_module --- ngx_http_limit_req_rw_module.c | 190 ++++++++++++++++++--------------- 1 file changed, 102 insertions(+), 88 deletions(-) diff --git a/ngx_http_limit_req_rw_module.c b/ngx_http_limit_req_rw_module.c index d850baa..f06fcc1 100644 --- a/ngx_http_limit_req_rw_module.c +++ b/ngx_http_limit_req_rw_module.c @@ -28,8 +28,9 @@ typedef struct { } header; typedef struct { - header *Header; // Header information - entities *Entities; // Array of entities + header *Header; // Header information + entities *Entities; // Array of entities + uint32_t EntitiesSize; // Size of the entities array } ngx_zone_data_t; static ngx_int_t ngx_decode_msg_pack(ngx_http_request_t *r, @@ -174,6 +175,7 @@ static ngx_int_t ngx_decode_msg_pack(ngx_http_request_t *r, ngx_zone_data->Header = hdr; entities *arr = ngx_pnalloc(r->pool, deserialized_size * sizeof(entities)); + ngx_zone_data->EntitiesSize = items->via.array.size; if (data == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for array"); @@ -254,103 +256,115 @@ static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r) { return rc; } - strip_zone_name_from_uri(&r->uri, &zone_name); - shm_zone = find_rate_limit_shm_zone_by_name(r, zone_name); - if (shm_zone == NULL) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "ngx_http_limit_req_rw_module: rate limit zone %*s not found", - zone_name); - return NGX_HTTP_NOT_FOUND; - } + for (uint32_t i = 0; i < msg_pack->EntitiesSize; i++) { + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: Entity Key: %*s, Last: %ul, " + "Excess: %ul", + msg_pack->Entities[i].Key, msg_pack->Entities[i].Last, + msg_pack->Entities[i].Excess); - ctx = shm_zone->data; - ngx_shmtx_lock(&ctx->shpool->mutex); + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: Header Key: %*s", + msg_pack->Header->Key); - struct in_addr addr; - inet_pton(AF_INET, "127.0.0.1", - &addr); // TODO: Replace with actual client IP retrieval logic - ngx_str_t key; - key.data = (u_char *)&addr; - key.len = sizeof(addr); // 4 - - uint32_t hash = ngx_crc32_short(key.data, key.len); - - ngx_rbtree_node_t *node = ctx->sh->rbtree.root; - ngx_rbtree_node_t *sentinel = ctx->sh->rbtree.sentinel; - ngx_http_limit_req_node_t *lr = NULL; - int found = 0; - while (node != sentinel) { - if (hash < node->key) { - node = node->left; - continue; - } - if (hash > node->key) { - node = node->right; - continue; + strip_zone_name_from_uri(&r->uri, &zone_name); + shm_zone = find_rate_limit_shm_zone_by_name(r, zone_name); + if (shm_zone == NULL) { + ngx_log_error( + NGX_LOG_ERR, r->connection->log, 0, + "ngx_http_limit_req_rw_module: rate limit zone %*s not found", + zone_name); + return NGX_HTTP_NOT_FOUND; } - lr = (ngx_http_limit_req_node_t *)&node->color; - if (lr->len == key.len && ngx_memcmp(lr->data, key.data, key.len) == 0) { - lr->excess = 777; - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: found existing node, " - "excess set to 777"); - found = 1; - break; + + ctx = shm_zone->data; + ngx_shmtx_lock(&ctx->shpool->mutex); + + struct in_addr addr; + const char *str = (const char *)msg_pack->Entities[i].Key.data; + inet_pton(AF_INET, str, + &addr); // TODO: Replace with actual client IP retrieval logic + ngx_str_t key; + key.data = (u_char *)&addr; + key.len = sizeof(addr); // 4 + + uint32_t hash = ngx_crc32_short(key.data, key.len); + + ngx_rbtree_node_t *node = ctx->sh->rbtree.root; + ngx_rbtree_node_t *sentinel = ctx->sh->rbtree.sentinel; + ngx_http_limit_req_node_t *lr = NULL; + int found = 0; + while (node != sentinel) { + if (hash < node->key) { + node = node->left; + continue; + } + if (hash > node->key) { + node = node->right; + continue; + } + lr = (ngx_http_limit_req_node_t *)&node->color; + if (lr->len == key.len && ngx_memcmp(lr->data, key.data, key.len) == 0) { + lr->excess = msg_pack->Entities[i].Excess; + found = 1; + break; + } + node = (ngx_memn2cmp(key.data, lr->data, key.len, lr->len) < 0) + ? node->left + : node->right; } - node = (ngx_memn2cmp(key.data, lr->data, key.len, lr->len) < 0) - ? node->left - : node->right; - } - if (!found) { - size_t size = offsetof(ngx_rbtree_node_t, color) + - offsetof(ngx_http_limit_req_node_t, data) + key.len; - node = ngx_slab_alloc_locked(ctx->shpool, size); - if (node == NULL) { - ngx_shmtx_unlock(&ctx->shpool->mutex); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "failed to allocate memory for rate limit node"); - r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; + if (!found) { + size_t size = offsetof(ngx_rbtree_node_t, color) + + offsetof(ngx_http_limit_req_node_t, data) + key.len; + node = ngx_slab_alloc_locked(ctx->shpool, size); + if (node == NULL) { + ngx_shmtx_unlock(&ctx->shpool->mutex); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "failed to allocate memory for rate limit node"); + r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; + } + node->key = hash; + lr = (ngx_http_limit_req_node_t *)&node->color; + + lr->len = (u_short)key.len; + lr->excess = + msg_pack->Entities[i] + .Excess; // TODO: replace with the value received from msgPack + lr->count = 0; + ngx_memcpy(lr->data, key.data, key.len); + + ngx_rbtree_insert(&ctx->sh->rbtree, node); + ngx_queue_insert_head(&ctx->sh->queue, &lr->queue); + ngx_log_error( + NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: new node created, excess set to 777"); } - node->key = hash; - lr = (ngx_http_limit_req_node_t *)&node->color; + ngx_shmtx_unlock(&ctx->shpool->mutex); - lr->len = (u_short)key.len; - lr->excess = 777; // TODO: replace with the value received from msgPack - lr->count = 0; - ngx_memcpy(lr->data, key.data, key.len); + ngx_str_t response = ngx_string("OK\n"); - ngx_rbtree_insert(&ctx->sh->rbtree, node); - ngx_queue_insert_head(&ctx->sh->queue, &lr->queue); - ngx_log_error( - NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: new node created, excess set to 777"); - } - ngx_shmtx_unlock(&ctx->shpool->mutex); + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = response.len; + ngx_str_set(&r->headers_out.content_type, "text/plain"); - ngx_str_t response = ngx_string("OK\n"); + rc = ngx_http_send_header(r); + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { + r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ngx_buf_t *b = ngx_create_temp_buf(r->pool, response.len); + if (b == NULL) { + r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ngx_memcpy(b->pos, response.data, response.len); + b->last = b->pos + response.len; + b->last_buf = 1; - r->headers_out.status = NGX_HTTP_OK; - r->headers_out.content_length_n = response.len; - ngx_str_set(&r->headers_out.content_type, "text/plain"); + ngx_chain_t out; + out.buf = b; + out.next = NULL; - rc = ngx_http_send_header(r); - if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { - r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; - } - ngx_buf_t *b = ngx_create_temp_buf(r->pool, response.len); - if (b == NULL) { - r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; + ngx_http_output_filter(r, &out); } - ngx_memcpy(b->pos, response.data, response.len); - b->last = b->pos + response.len; - b->last_buf = 1; - - ngx_chain_t out; - out.buf = b; - out.next = NULL; - - ngx_http_output_filter(r, &out); - return NGX_OK; } From 90d429288a9927490e06900ba62100e2e212ae0f Mon Sep 17 00:00:00 2001 From: ravilock Date: Fri, 27 Jun 2025 18:02:15 -0300 Subject: [PATCH 16/18] fix: use binary address for entry keys on send cli --- log_zone/send.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/log_zone/send.go b/log_zone/send.go index faa3fab..7ed9d26 100644 --- a/log_zone/send.go +++ b/log_zone/send.go @@ -19,8 +19,10 @@ func send(zone string) { Now: time.Now().Unix(), NowMonotonic: time.Now().UnixNano() / int64(time.Millisecond), }, []RateLimitEntry{ - {Key("127.0.0.0"), 7, 99}, - {Key("127.6.4.00"), 2, 98}, + {Key([]byte{127, 0, 0, 0}), 7, 99}, + {Key([]byte{127, 6, 4, 00}), 2, 98}, + {Key([]byte{127, 0, 0, 99}), 30, 999}, + {Key([]byte{10, 0, 0, 1}), 444, 21}, }) if err != nil { logrus.Fatalf("Error sending request: %v", err) From d32ccf75caade4daf784f7a01d336439221a7c87 Mon Sep 17 00:00:00 2001 From: ravilock Date: Fri, 27 Jun 2025 18:23:05 -0300 Subject: [PATCH 17/18] refactor(limit-req): optimize entity update logic Refactored the rate limit entity update logic to: - Move zone lookup and locking outside the entity loop for efficiency. - Use direct key from message pack instead of converting to in_addr. - Replace memcmp with ngx_memn2cmp for key comparison in rbtree. - Fix entity array allocation and assignment. - Improve error handling and logging. - Remove redundant debug logs and streamline memory management. This improves performance, correctness, and maintainability of the rate limit state update handler. --- log_zone/send.go | 3 +- ngx_http_limit_req_rw_module.c | 176 ++++++++++++++------------------- 2 files changed, 77 insertions(+), 102 deletions(-) diff --git a/log_zone/send.go b/log_zone/send.go index 7ed9d26..effe051 100644 --- a/log_zone/send.go +++ b/log_zone/send.go @@ -20,8 +20,9 @@ func send(zone string) { NowMonotonic: time.Now().UnixNano() / int64(time.Millisecond), }, []RateLimitEntry{ {Key([]byte{127, 0, 0, 0}), 7, 99}, + {Key([]byte{127, 0, 0, 1}), 7, 12}, {Key([]byte{127, 6, 4, 00}), 2, 98}, - {Key([]byte{127, 0, 0, 99}), 30, 999}, + {Key([]byte{127, 0, 0, 99}), 30, 300}, {Key([]byte{10, 0, 0, 1}), 444, 21}, }) if err != nil { diff --git a/ngx_http_limit_req_rw_module.c b/ngx_http_limit_req_rw_module.c index f06fcc1..ce31fa3 100644 --- a/ngx_http_limit_req_rw_module.c +++ b/ngx_http_limit_req_rw_module.c @@ -129,14 +129,14 @@ static ngx_int_t ngx_decode_msg_pack(ngx_http_request_t *r, msgpack_zone_init(&mempool, 2048); msgpack_unpack((char *)data, len, NULL, &mempool, &deserialized); - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: deserialized type: %d", - deserialized.type); + ngx_log_error( + NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: deserialized type: %d - size: %d", + deserialized.type, deserialized.via.array.size); if (deserialized.type != MSGPACK_OBJECT_ARRAY) { msgpack_zone_destroy(&mempool); return NGX_HTTP_INTERNAL_SERVER_ERROR; } - deserialized_size = deserialized.via.array.size; msgpack_object *items = deserialized.via.array.ptr; @@ -151,8 +151,6 @@ static ngx_int_t ngx_decode_msg_pack(ngx_http_request_t *r, msgpack_object hdrKey = items[0].via.array.ptr[0]; msgpack_object hdrNow = items[0].via.array.ptr[1]; msgpack_object hdrNowMonotonic = items[0].via.array.ptr[2]; - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Header Key type: %d", - hdrKey.type); hdr->Key.len = hdrKey.via.str.size; u_char *keyData = ngx_palloc(r->pool, hdrKey.via.str.size); if (keyData == NULL) { @@ -164,28 +162,25 @@ static ngx_int_t ngx_decode_msg_pack(ngx_http_request_t *r, hdr->Key.data = keyData; hdr->Now = hdrNow.via.u64; hdr->NowMonotonic = hdrNowMonotonic.via.u64; - - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Header Key value: %*s", - hdr->Key); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Header Now value: %ul", - hdr->Now); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "Header NowMonotonic value: %ul", hdr->NowMonotonic); } ngx_zone_data->Header = hdr; entities *arr = ngx_pnalloc(r->pool, deserialized_size * sizeof(entities)); - ngx_zone_data->EntitiesSize = items->via.array.size; - if (data == NULL) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "failed to allocate memory for array"); - return NGX_HTTP_INTERNAL_SERVER_ERROR; - } - for (uint32_t i = 1; i < items->via.array.size; i++) { + ngx_zone_data->EntitiesSize = deserialized_size - 1; + ngx_zone_data->Entities = arr; + + for (uint32_t i = 1; i < deserialized_size; i++) { + if (items[i].via.array.size != 3) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "invalid number of items in array at index %d", i); + msgpack_zone_destroy(&mempool); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + msgpack_object iItemKey = items[i].via.array.ptr[0]; msgpack_object iItemLast = items[i].via.array.ptr[1]; msgpack_object iItemExcess = items[i].via.array.ptr[2]; - arr[i - 1].Key.len = iItemKey.via.str.size; + u_char *keyData = ngx_palloc(r->pool, iItemKey.via.str.size); if (keyData == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, @@ -193,18 +188,12 @@ static ngx_int_t ngx_decode_msg_pack(ngx_http_request_t *r, return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_memcpy(keyData, iItemKey.via.str.ptr, iItemKey.via.str.size); + + arr[i - 1].Key.len = iItemKey.via.str.size; arr[i - 1].Key.data = keyData; arr[i - 1].Last = iItemLast.via.u64; arr[i - 1].Excess = iItemExcess.via.u64; - - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Key [%d] value: %*s", i, - arr[i - 1].Key); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Last [%d] value: %ul", i, - arr[i - 1].Last); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Excess [%d] value: %ul", - i, arr[i - 1].Excess); } - ngx_zone_data->Entities = arr; msgpack_zone_destroy(&mempool); return NGX_OK; } @@ -234,11 +223,16 @@ static void ngx_http_limit_req_write_post_handler(ngx_http_request_t *r) { } static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r) { - ngx_int_t rc; + ngx_int_t rc, found; ngx_zone_data_t *msg_pack = NULL; ngx_str_t zone_name; ngx_shm_zone_t *shm_zone; ngx_http_limit_req_ctx_t *ctx; + ngx_str_t key; + size_t size; + uint32_t hash; + ngx_rbtree_node_t *node, *sentinel; + ngx_http_limit_req_node_t *lr; if (r != r->main) { return NGX_DECLINED; @@ -251,49 +245,36 @@ static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r) { "ngx_http_limit_req_rw_module: failed to allocate memory for msg_pack"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } + rc = ngx_decode_msg_pack(r, msg_pack); if (rc != NGX_OK) { return rc; } - for (uint32_t i = 0; i < msg_pack->EntitiesSize; i++) { - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: Entity Key: %*s, Last: %ul, " - "Excess: %ul", - msg_pack->Entities[i].Key, msg_pack->Entities[i].Last, - msg_pack->Entities[i].Excess); - - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: Header Key: %*s", - msg_pack->Header->Key); - - strip_zone_name_from_uri(&r->uri, &zone_name); - shm_zone = find_rate_limit_shm_zone_by_name(r, zone_name); - if (shm_zone == NULL) { - ngx_log_error( - NGX_LOG_ERR, r->connection->log, 0, - "ngx_http_limit_req_rw_module: rate limit zone %*s not found", - zone_name); - return NGX_HTTP_NOT_FOUND; - } + strip_zone_name_from_uri(&r->uri, &zone_name); + shm_zone = find_rate_limit_shm_zone_by_name(r, zone_name); + if (shm_zone == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "ngx_http_limit_req_rw_module: rate limit zone %*s not found", + zone_name); + return NGX_HTTP_NOT_FOUND; + } + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: Header Key: %*s", + msg_pack->Header->Key); + for (uint32_t i = 0; i < msg_pack->EntitiesSize; i++) { ctx = shm_zone->data; ngx_shmtx_lock(&ctx->shpool->mutex); - struct in_addr addr; - const char *str = (const char *)msg_pack->Entities[i].Key.data; - inet_pton(AF_INET, str, - &addr); // TODO: Replace with actual client IP retrieval logic - ngx_str_t key; - key.data = (u_char *)&addr; - key.len = sizeof(addr); // 4 + key = msg_pack->Entities[i].Key; + + hash = ngx_crc32_short(key.data, key.len); - uint32_t hash = ngx_crc32_short(key.data, key.len); + node = ctx->sh->rbtree.root; + sentinel = ctx->sh->rbtree.sentinel; - ngx_rbtree_node_t *node = ctx->sh->rbtree.root; - ngx_rbtree_node_t *sentinel = ctx->sh->rbtree.sentinel; - ngx_http_limit_req_node_t *lr = NULL; - int found = 0; + found = 0; while (node != sentinel) { if (hash < node->key) { node = node->left; @@ -303,67 +284,60 @@ static ngx_int_t ngx_http_limit_req_write_handler(ngx_http_request_t *r) { node = node->right; continue; } + + /* hash == node->key */ + lr = (ngx_http_limit_req_node_t *)&node->color; - if (lr->len == key.len && ngx_memcmp(lr->data, key.data, key.len) == 0) { - lr->excess = msg_pack->Entities[i].Excess; + + rc = ngx_memn2cmp(key.data, lr->data, key.len, lr->len); + + if (rc == 0) { found = 1; break; } - node = (ngx_memn2cmp(key.data, lr->data, key.len, lr->len) < 0) - ? node->left - : node->right; + + node = (rc < 0) ? node->left : node->right; } - if (!found) { - size_t size = offsetof(ngx_rbtree_node_t, color) + - offsetof(ngx_http_limit_req_node_t, data) + key.len; + + if (found) { + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: existing node found %ul", + lr->excess); + lr->last = msg_pack->Entities[i].Last; + lr->excess = msg_pack->Entities[i].Excess; + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: existing node updated %ul", + lr->excess); + } else { + size = offsetof(ngx_rbtree_node_t, color) + + offsetof(ngx_http_limit_req_node_t, data) + key.len; node = ngx_slab_alloc_locked(ctx->shpool, size); if (node == NULL) { ngx_shmtx_unlock(&ctx->shpool->mutex); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to allocate memory for rate limit node"); - r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; + return NGX_HTTP_INTERNAL_SERVER_ERROR; } node->key = hash; + lr = (ngx_http_limit_req_node_t *)&node->color; lr->len = (u_short)key.len; - lr->excess = - msg_pack->Entities[i] - .Excess; // TODO: replace with the value received from msgPack + lr->excess = msg_pack->Entities[i].Excess; + lr->last = msg_pack->Entities[i].Last; lr->count = 0; + ngx_memcpy(lr->data, key.data, key.len); ngx_rbtree_insert(&ctx->sh->rbtree, node); - ngx_queue_insert_head(&ctx->sh->queue, &lr->queue); - ngx_log_error( - NGX_LOG_DEBUG, r->connection->log, 0, - "ngx_http_limit_req_rw_module: new node created, excess set to 777"); - } - ngx_shmtx_unlock(&ctx->shpool->mutex); - - ngx_str_t response = ngx_string("OK\n"); - r->headers_out.status = NGX_HTTP_OK; - r->headers_out.content_length_n = response.len; - ngx_str_set(&r->headers_out.content_type, "text/plain"); + ngx_queue_insert_head(&ctx->sh->queue, &lr->queue); - rc = ngx_http_send_header(r); - if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { - r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; - } - ngx_buf_t *b = ngx_create_temp_buf(r->pool, response.len); - if (b == NULL) { - r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "ngx_http_limit_req_rw_module: new node excess set to %ul", + lr->excess); } - ngx_memcpy(b->pos, response.data, response.len); - b->last = b->pos + response.len; - b->last_buf = 1; - - ngx_chain_t out; - out.buf = b; - out.next = NULL; - - ngx_http_output_filter(r, &out); + ngx_shmtx_unlock(&ctx->shpool->mutex); } return NGX_OK; } From 9a403479bcef52b6bf35a963de6bac6189667cd3 Mon Sep 17 00:00:00 2001 From: ravilock Date: Fri, 27 Jun 2025 18:30:40 -0300 Subject: [PATCH 18/18] chore: remove copyright --- ngx_http_limit_req_module.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ngx_http_limit_req_module.h b/ngx_http_limit_req_module.h index fbaf191..99187e2 100644 --- a/ngx_http_limit_req_module.h +++ b/ngx_http_limit_req_module.h @@ -1,7 +1,6 @@ /* - * Copyright (C) Igor Sysoev - * Copyright (C) Nginx, Inc. - */ +TODO: copyright +*/ #include #include