Hello, Please find below a patch to enable dynamic rate limiting for limit_req module.
/* ----------------------------EXAMPLE---------------------------------*/ geo $traffic_tier { default free; 127.0.1.0/24 basic; 127.0.2.0/24 premium; } map $traffic_tier $rate { free 1r/m; basic 2r/m; premium 1r/s; } limit_req_zone $binary_remote_addr zone=one:10m rate=$rate; server { limit_req zone=one; listen 80; server_name localhost; return 200; } curl --interface 127.0.X.X localhost /* ----------------------------NGINX-TESTS---------------------------------*/ debian@debian:~/projects/nginx-merc/nginx-tests$ prove limit_req* limit_req2.t ......... ok limit_req_delay.t .... ok limit_req_dry_run.t .. ok limit_req.t .......... ok All tests successful. Files=4, Tests=40, 4 wallclock secs ( 0.04 usr 0.00 sys + 0.28 cusr 0.11 csys = 0.43 CPU) Result: PASS /* ----------------------------CHANGES OF BEHAVIOUR---------------------------------*/ It is backwards compatible with the syntax of existing configurations, either a rate=$variable can be used or the existing syntax of rate=xy/s. - 'rate=' can be assigned empty value, which results in an unlimited(maximum) rate limits value. - 'rate=' set to an invalid value also results in an unlimited(maximum) rate limit value. - The value of rate is now limited to prevent integer overflow in certain operations. - The maximum time between consecutive requests that is determined is now limited to 60s (60000ms) to prevent integer overflow/underflow. /* ----------------------------USE-CASES---------------------------------*/ Allow rate limits for a given user to be determined by mapping trusted request values to a rate, such as: - Source IP CIDR. - Client Certificate identifiers. - JWT claims. This could also be performed dynamically at runtime with key_val zone to alter rate limits on the fly without a reload. /* ----------------------------PATCHBOMB---------------------------------*/ # HG changeset patch # User jordanc.car...@outlook.com # Date 1672437935 0 # Fri Dec 30 22:05:35 2022 +0000 # Branch dynamic-rate-limiting # Node ID b2bd50efa81e5aeeb9b8f84ee0af34463add07fa # Parent 07b0bee87f32be91a33210bc06973e07c4c1dac9 Changed 'rate=' to complex value and added limits to the rate value to prevent integer overflow/underflow diff -r 07b0bee87f32 -r b2bd50efa81e src/http/modules/ngx_http_limit_req_module.c --- a/src/http/modules/ngx_http_limit_req_module.c Wed Dec 21 14:53:27 2022 +0300 +++ b/src/http/modules/ngx_http_limit_req_module.c Fri Dec 30 22:05:35 2022 +0000 @@ -26,6 +26,7 @@ /* integer value, 1 corresponds to 0.001 r/s */ ngx_uint_t excess; ngx_uint_t count; + ngx_uint_t rate; u_char data[1]; } ngx_http_limit_req_node_t; @@ -41,7 +42,7 @@ 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 rate; ngx_http_complex_value_t key; ngx_http_limit_req_node_t *node; } ngx_http_limit_req_ctx_t; @@ -66,9 +67,9 @@ static void ngx_http_limit_req_delay(ngx_http_request_t *r); static ngx_int_t ngx_http_limit_req_lookup(ngx_http_limit_req_limit_t *limit, - ngx_uint_t hash, ngx_str_t *key, ngx_uint_t *ep, ngx_uint_t account); + ngx_uint_t hash, ngx_str_t *key, ngx_uint_t *ep, ngx_uint_t account, ngx_uint_t rate); static ngx_msec_t ngx_http_limit_req_account(ngx_http_limit_req_limit_t *limits, - ngx_uint_t n, ngx_uint_t *ep, ngx_http_limit_req_limit_t **limit); + ngx_uint_t n, ngx_uint_t *ep, ngx_http_limit_req_limit_t **limit, ngx_uint_t rate); static void ngx_http_limit_req_unlock(ngx_http_limit_req_limit_t *limits, ngx_uint_t n); static void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, @@ -195,10 +196,13 @@ ngx_http_limit_req_handler(ngx_http_request_t *r) { uint32_t hash; - ngx_str_t key; + ngx_str_t key, rate_s; ngx_int_t rc; ngx_uint_t n, excess; + ngx_uint_t scale; + ngx_uint_t rate; ngx_msec_t delay; + u_char *p; ngx_http_limit_req_ctx_t *ctx; ngx_http_limit_req_conf_t *lrcf; ngx_http_limit_req_limit_t *limit, *limits; @@ -243,10 +247,34 @@ hash = ngx_crc32_short(key.data, key.len); + if (ngx_http_complex_value(r, &ctx->rate, &rate_s) != NGX_OK) { + ngx_http_limit_req_unlock(limits, n); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + scale = 1; + rate = NGX_ERROR; + + if (rate_s.len > 8) { + + rate = (ngx_uint_t) ngx_atoi(rate_s.data + 5, rate_s.len - 8); + + p = rate_s.data + rate_s.len - 3; + if (ngx_strncmp(p, "r/m", 3) == 0) { + scale = 60; + } else if (ngx_strncmp(p, "r/s", 3) != 0){ + rate = NGX_ERROR; + } + } + + rate = (rate != 0 && rate < NGX_MAX_INT_T_VALUE / 60000000 - 1001) ? + rate * 1000 / scale : + NGX_MAX_INT_T_VALUE / 60000000 - 1001; + ngx_shmtx_lock(&ctx->shpool->mutex); rc = ngx_http_limit_req_lookup(limit, hash, &key, &excess, - (n == lrcf->limits.nelts - 1)); + (n == lrcf->limits.nelts - 1), rate); ngx_shmtx_unlock(&ctx->shpool->mutex); @@ -291,7 +319,7 @@ excess = 0; } - delay = ngx_http_limit_req_account(limits, n, &excess, &limit); + delay = ngx_http_limit_req_account(limits, n, &excess, &limit, rate); if (!delay) { r->main->limit_req_status = NGX_HTTP_LIMIT_REQ_PASSED; @@ -403,7 +431,7 @@ static ngx_int_t ngx_http_limit_req_lookup(ngx_http_limit_req_limit_t *limit, ngx_uint_t hash, - ngx_str_t *key, ngx_uint_t *ep, ngx_uint_t account) + ngx_str_t *key, ngx_uint_t *ep, ngx_uint_t account, ngx_uint_t rate) { size_t size; ngx_int_t rc, excess; @@ -412,7 +440,6 @@ ngx_rbtree_node_t *node, *sentinel; ngx_http_limit_req_ctx_t *ctx; ngx_http_limit_req_node_t *lr; - now = ngx_current_msec; ctx = limit->shm_zone->data; @@ -446,12 +473,14 @@ if (ms < -60000) { ms = 1; - } else if (ms < 0) { ms = 0; + } else if (ms > 60000) { + ms = 60000; } - excess = lr->excess - ctx->rate * ms / 1000 + 1000; + lr->rate = rate; + excess = lr->excess - lr->rate * ms / 1000 + 1000; if (excess < 0) { excess = 0; @@ -510,6 +539,7 @@ lr->len = (u_short) key->len; lr->excess = 0; + lr->rate = rate; ngx_memcpy(lr->data, key->data, key->len); @@ -534,7 +564,7 @@ static ngx_msec_t ngx_http_limit_req_account(ngx_http_limit_req_limit_t *limits, ngx_uint_t n, - ngx_uint_t *ep, ngx_http_limit_req_limit_t **limit) + ngx_uint_t *ep, ngx_http_limit_req_limit_t **limit, ngx_uint_t rate) { ngx_int_t excess; ngx_msec_t now, delay, max_delay; @@ -543,13 +573,13 @@ ngx_http_limit_req_node_t *lr; excess = *ep; + max_delay = 0; if ((ngx_uint_t) excess <= (*limit)->delay) { max_delay = 0; } else { - ctx = (*limit)->shm_zone->data; - max_delay = (excess - (*limit)->delay) * 1000 / ctx->rate; + max_delay = (excess - (*limit)->delay) * 1000 / rate; } while (n--) { @@ -570,9 +600,11 @@ } else if (ms < 0) { ms = 0; + } else if (ms > 60000) { + ms = 60000; } - excess = lr->excess - ctx->rate * ms / 1000 + 1000; + excess = lr->excess - lr->rate * ms / 1000 + 1000; if (excess < 0) { excess = 0; @@ -593,7 +625,7 @@ continue; } - delay = (excess - limits[n].delay) * 1000 / ctx->rate; + delay = (excess - limits[n].delay) * 1000 / lr->rate; if (delay > max_delay) { max_delay = delay; @@ -674,9 +706,11 @@ if (ms < 60000) { return; + } else { + ms = 60000; } - excess = lr->excess - ctx->rate * ms / 1000; + excess = lr->excess - lr->rate * ms / 1000; if (excess > 0) { return; @@ -833,14 +867,12 @@ ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { u_char *p; - size_t len; ssize_t size; ngx_str_t *value, name, s; - ngx_int_t rate, scale; ngx_uint_t i; ngx_shm_zone_t *shm_zone; ngx_http_limit_req_ctx_t *ctx; - ngx_http_compile_complex_value_t ccv; + ngx_http_compile_complex_value_t key, rate; value = cf->args->elts; @@ -849,19 +881,17 @@ return NGX_CONF_ERROR; } - ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + ngx_memzero(&key, sizeof(ngx_http_compile_complex_value_t)); - ccv.cf = cf; - ccv.value = &value[1]; - ccv.complex_value = &ctx->key; + key.cf = cf; + key.value = &value[1]; + key.complex_value = &ctx->key; - if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + if (ngx_http_compile_complex_value(&key) != NGX_OK) { return NGX_CONF_ERROR; } size = 0; - rate = 1; - scale = 1; name.len = 0; for (i = 2; i < cf->args->nelts; i++) { @@ -902,25 +932,14 @@ if (ngx_strncmp(value[i].data, "rate=", 5) == 0) { - len = value[i].len; - p = value[i].data + len - 3; - - if (ngx_strncmp(p, "r/s", 3) == 0) { - scale = 1; - len -= 3; + ngx_memzero(&rate, sizeof(ngx_http_compile_complex_value_t)); + rate.cf = cf; + rate.value = &value[i]; + rate.complex_value = &ctx->rate; - } else if (ngx_strncmp(p, "r/m", 3) == 0) { - scale = 60; - len -= 3; - } - - rate = ngx_atoi(value[i].data + 5, len - 5); - if (rate <= 0) { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "invalid rate \"%V\"", &value[i]); + if (ngx_http_compile_complex_value(&rate) != NGX_OK) { return NGX_CONF_ERROR; } - continue; } @@ -936,8 +955,6 @@ return NGX_CONF_ERROR; } - ctx->rate = rate * 1000 / scale; - shm_zone = ngx_shared_memory_add(cf, &name, size, &ngx_http_limit_req_module); if (shm_zone == NULL) {
_______________________________________________ nginx-devel mailing list nginx-devel@nginx.org https://mailman.nginx.org/mailman/listinfo/nginx-devel