Update and clean Tomato RAF files
[tomato.git] / release / src / router / nginx / src / http / modules / ngx_http_limit_conn_module.c
blob7f0eea7ab0050ccea03b079b8c9e7dc186df2b58
2 /*
3 * Copyright (C) Igor Sysoev
4 * Copyright (C) Nginx, Inc.
5 */
8 #include <ngx_config.h>
9 #include <ngx_core.h>
10 #include <ngx_http.h>
13 typedef struct {
14 u_char color;
15 u_char len;
16 u_short conn;
17 u_char data[1];
18 } ngx_http_limit_conn_node_t;
21 typedef struct {
22 ngx_shm_zone_t *shm_zone;
23 ngx_rbtree_node_t *node;
24 } ngx_http_limit_conn_cleanup_t;
27 typedef struct {
28 ngx_rbtree_t *rbtree;
29 ngx_int_t index;
30 ngx_str_t var;
31 } ngx_http_limit_conn_ctx_t;
34 typedef struct {
35 ngx_shm_zone_t *shm_zone;
36 ngx_uint_t conn;
37 } ngx_http_limit_conn_limit_t;
40 typedef struct {
41 ngx_array_t limits;
42 ngx_uint_t log_level;
43 ngx_uint_t status_code;
44 } ngx_http_limit_conn_conf_t;
47 static ngx_rbtree_node_t *ngx_http_limit_conn_lookup(ngx_rbtree_t *rbtree,
48 ngx_http_variable_value_t *vv, uint32_t hash);
49 static void ngx_http_limit_conn_cleanup(void *data);
50 static ngx_inline void ngx_http_limit_conn_cleanup_all(ngx_pool_t *pool);
52 static void *ngx_http_limit_conn_create_conf(ngx_conf_t *cf);
53 static char *ngx_http_limit_conn_merge_conf(ngx_conf_t *cf, void *parent,
54 void *child);
55 static char *ngx_http_limit_conn_zone(ngx_conf_t *cf, ngx_command_t *cmd,
56 void *conf);
57 static char *ngx_http_limit_zone(ngx_conf_t *cf, ngx_command_t *cmd,
58 void *conf);
59 static char *ngx_http_limit_conn(ngx_conf_t *cf, ngx_command_t *cmd,
60 void *conf);
61 static ngx_int_t ngx_http_limit_conn_init(ngx_conf_t *cf);
64 static ngx_conf_deprecated_t ngx_conf_deprecated_limit_zone = {
65 ngx_conf_deprecated, "limit_zone", "limit_conn_zone"
69 static ngx_conf_enum_t ngx_http_limit_conn_log_levels[] = {
70 { ngx_string("info"), NGX_LOG_INFO },
71 { ngx_string("notice"), NGX_LOG_NOTICE },
72 { ngx_string("warn"), NGX_LOG_WARN },
73 { ngx_string("error"), NGX_LOG_ERR },
74 { ngx_null_string, 0 }
78 static ngx_conf_num_bounds_t ngx_http_limit_conn_status_bounds = {
79 ngx_conf_check_num_bounds, 400, 599
83 static ngx_command_t ngx_http_limit_conn_commands[] = {
85 { ngx_string("limit_conn_zone"),
86 NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE2,
87 ngx_http_limit_conn_zone,
90 NULL },
92 { ngx_string("limit_zone"),
93 NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE3,
94 ngx_http_limit_zone,
97 NULL },
99 { ngx_string("limit_conn"),
100 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
101 ngx_http_limit_conn,
102 NGX_HTTP_LOC_CONF_OFFSET,
104 NULL },
106 { ngx_string("limit_conn_log_level"),
107 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
108 ngx_conf_set_enum_slot,
109 NGX_HTTP_LOC_CONF_OFFSET,
110 offsetof(ngx_http_limit_conn_conf_t, log_level),
111 &ngx_http_limit_conn_log_levels },
113 { ngx_string("limit_conn_status"),
114 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
115 ngx_conf_set_num_slot,
116 NGX_HTTP_LOC_CONF_OFFSET,
117 offsetof(ngx_http_limit_conn_conf_t, status_code),
118 &ngx_http_limit_conn_status_bounds },
120 ngx_null_command
124 static ngx_http_module_t ngx_http_limit_conn_module_ctx = {
125 NULL, /* preconfiguration */
126 ngx_http_limit_conn_init, /* postconfiguration */
128 NULL, /* create main configuration */
129 NULL, /* init main configuration */
131 NULL, /* create server configuration */
132 NULL, /* merge server configuration */
134 ngx_http_limit_conn_create_conf, /* create location configuration */
135 ngx_http_limit_conn_merge_conf /* merge location configuration */
139 ngx_module_t ngx_http_limit_conn_module = {
140 NGX_MODULE_V1,
141 &ngx_http_limit_conn_module_ctx, /* module context */
142 ngx_http_limit_conn_commands, /* module directives */
143 NGX_HTTP_MODULE, /* module type */
144 NULL, /* init master */
145 NULL, /* init module */
146 NULL, /* init process */
147 NULL, /* init thread */
148 NULL, /* exit thread */
149 NULL, /* exit process */
150 NULL, /* exit master */
151 NGX_MODULE_V1_PADDING
155 static ngx_int_t
156 ngx_http_limit_conn_handler(ngx_http_request_t *r)
158 size_t len, n;
159 uint32_t hash;
160 ngx_uint_t i;
161 ngx_slab_pool_t *shpool;
162 ngx_rbtree_node_t *node;
163 ngx_pool_cleanup_t *cln;
164 ngx_http_variable_value_t *vv;
165 ngx_http_limit_conn_ctx_t *ctx;
166 ngx_http_limit_conn_node_t *lc;
167 ngx_http_limit_conn_conf_t *lccf;
168 ngx_http_limit_conn_limit_t *limits;
169 ngx_http_limit_conn_cleanup_t *lccln;
171 if (r->main->limit_conn_set) {
172 return NGX_DECLINED;
175 lccf = ngx_http_get_module_loc_conf(r, ngx_http_limit_conn_module);
176 limits = lccf->limits.elts;
178 for (i = 0; i < lccf->limits.nelts; i++) {
179 ctx = limits[i].shm_zone->data;
181 vv = ngx_http_get_indexed_variable(r, ctx->index);
183 if (vv == NULL || vv->not_found) {
184 continue;
187 len = vv->len;
189 if (len == 0) {
190 continue;
193 if (len > 255) {
194 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
195 "the value of the \"%V\" variable "
196 "is more than 255 bytes: \"%v\"",
197 &ctx->var, vv);
198 continue;
201 r->main->limit_conn_set = 1;
203 hash = ngx_crc32_short(vv->data, len);
205 shpool = (ngx_slab_pool_t *) limits[i].shm_zone->shm.addr;
207 ngx_shmtx_lock(&shpool->mutex);
209 node = ngx_http_limit_conn_lookup(ctx->rbtree, vv, hash);
211 if (node == NULL) {
213 n = offsetof(ngx_rbtree_node_t, color)
214 + offsetof(ngx_http_limit_conn_node_t, data)
215 + len;
217 node = ngx_slab_alloc_locked(shpool, n);
219 if (node == NULL) {
220 ngx_shmtx_unlock(&shpool->mutex);
221 ngx_http_limit_conn_cleanup_all(r->pool);
222 return lccf->status_code;
225 lc = (ngx_http_limit_conn_node_t *) &node->color;
227 node->key = hash;
228 lc->len = (u_char) len;
229 lc->conn = 1;
230 ngx_memcpy(lc->data, vv->data, len);
232 ngx_rbtree_insert(ctx->rbtree, node);
234 } else {
236 lc = (ngx_http_limit_conn_node_t *) &node->color;
238 if ((ngx_uint_t) lc->conn >= limits[i].conn) {
240 ngx_shmtx_unlock(&shpool->mutex);
242 ngx_log_error(lccf->log_level, r->connection->log, 0,
243 "limiting connections by zone \"%V\"",
244 &limits[i].shm_zone->shm.name);
246 ngx_http_limit_conn_cleanup_all(r->pool);
247 return lccf->status_code;
250 lc->conn++;
253 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
254 "limit conn: %08XD %d", node->key, lc->conn);
256 ngx_shmtx_unlock(&shpool->mutex);
258 cln = ngx_pool_cleanup_add(r->pool,
259 sizeof(ngx_http_limit_conn_cleanup_t));
260 if (cln == NULL) {
261 return NGX_HTTP_INTERNAL_SERVER_ERROR;
264 cln->handler = ngx_http_limit_conn_cleanup;
265 lccln = cln->data;
267 lccln->shm_zone = limits[i].shm_zone;
268 lccln->node = node;
271 return NGX_DECLINED;
275 static void
276 ngx_http_limit_conn_rbtree_insert_value(ngx_rbtree_node_t *temp,
277 ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
279 ngx_rbtree_node_t **p;
280 ngx_http_limit_conn_node_t *lcn, *lcnt;
282 for ( ;; ) {
284 if (node->key < temp->key) {
286 p = &temp->left;
288 } else if (node->key > temp->key) {
290 p = &temp->right;
292 } else { /* node->key == temp->key */
294 lcn = (ngx_http_limit_conn_node_t *) &node->color;
295 lcnt = (ngx_http_limit_conn_node_t *) &temp->color;
297 p = (ngx_memn2cmp(lcn->data, lcnt->data, lcn->len, lcnt->len) < 0)
298 ? &temp->left : &temp->right;
301 if (*p == sentinel) {
302 break;
305 temp = *p;
308 *p = node;
309 node->parent = temp;
310 node->left = sentinel;
311 node->right = sentinel;
312 ngx_rbt_red(node);
316 static ngx_rbtree_node_t *
317 ngx_http_limit_conn_lookup(ngx_rbtree_t *rbtree, ngx_http_variable_value_t *vv,
318 uint32_t hash)
320 ngx_int_t rc;
321 ngx_rbtree_node_t *node, *sentinel;
322 ngx_http_limit_conn_node_t *lcn;
324 node = rbtree->root;
325 sentinel = rbtree->sentinel;
327 while (node != sentinel) {
329 if (hash < node->key) {
330 node = node->left;
331 continue;
334 if (hash > node->key) {
335 node = node->right;
336 continue;
339 /* hash == node->key */
341 lcn = (ngx_http_limit_conn_node_t *) &node->color;
343 rc = ngx_memn2cmp(vv->data, lcn->data,
344 (size_t) vv->len, (size_t) lcn->len);
345 if (rc == 0) {
346 return node;
349 node = (rc < 0) ? node->left : node->right;
352 return NULL;
356 static void
357 ngx_http_limit_conn_cleanup(void *data)
359 ngx_http_limit_conn_cleanup_t *lccln = data;
361 ngx_slab_pool_t *shpool;
362 ngx_rbtree_node_t *node;
363 ngx_http_limit_conn_ctx_t *ctx;
364 ngx_http_limit_conn_node_t *lc;
366 ctx = lccln->shm_zone->data;
367 shpool = (ngx_slab_pool_t *) lccln->shm_zone->shm.addr;
368 node = lccln->node;
369 lc = (ngx_http_limit_conn_node_t *) &node->color;
371 ngx_shmtx_lock(&shpool->mutex);
373 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, lccln->shm_zone->shm.log, 0,
374 "limit conn cleanup: %08XD %d", node->key, lc->conn);
376 lc->conn--;
378 if (lc->conn == 0) {
379 ngx_rbtree_delete(ctx->rbtree, node);
380 ngx_slab_free_locked(shpool, node);
383 ngx_shmtx_unlock(&shpool->mutex);
387 static ngx_inline void
388 ngx_http_limit_conn_cleanup_all(ngx_pool_t *pool)
390 ngx_pool_cleanup_t *cln;
392 cln = pool->cleanup;
394 while (cln && cln->handler == ngx_http_limit_conn_cleanup) {
395 ngx_http_limit_conn_cleanup(cln->data);
396 cln = cln->next;
399 pool->cleanup = cln;
403 static ngx_int_t
404 ngx_http_limit_conn_init_zone(ngx_shm_zone_t *shm_zone, void *data)
406 ngx_http_limit_conn_ctx_t *octx = data;
408 size_t len;
409 ngx_slab_pool_t *shpool;
410 ngx_rbtree_node_t *sentinel;
411 ngx_http_limit_conn_ctx_t *ctx;
413 ctx = shm_zone->data;
415 if (octx) {
416 if (ngx_strcmp(ctx->var.data, octx->var.data) != 0) {
417 ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
418 "limit_conn_zone \"%V\" uses the \"%V\" variable "
419 "while previously it used the \"%V\" variable",
420 &shm_zone->shm.name, &ctx->var, &octx->var);
421 return NGX_ERROR;
424 ctx->rbtree = octx->rbtree;
426 return NGX_OK;
429 shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
431 if (shm_zone->shm.exists) {
432 ctx->rbtree = shpool->data;
434 return NGX_OK;
437 ctx->rbtree = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_t));
438 if (ctx->rbtree == NULL) {
439 return NGX_ERROR;
442 shpool->data = ctx->rbtree;
444 sentinel = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_node_t));
445 if (sentinel == NULL) {
446 return NGX_ERROR;
449 ngx_rbtree_init(ctx->rbtree, sentinel,
450 ngx_http_limit_conn_rbtree_insert_value);
452 len = sizeof(" in limit_conn_zone \"\"") + shm_zone->shm.name.len;
454 shpool->log_ctx = ngx_slab_alloc(shpool, len);
455 if (shpool->log_ctx == NULL) {
456 return NGX_ERROR;
459 ngx_sprintf(shpool->log_ctx, " in limit_conn_zone \"%V\"%Z",
460 &shm_zone->shm.name);
462 return NGX_OK;
466 static void *
467 ngx_http_limit_conn_create_conf(ngx_conf_t *cf)
469 ngx_http_limit_conn_conf_t *conf;
471 conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_conn_conf_t));
472 if (conf == NULL) {
473 return NULL;
477 * set by ngx_pcalloc():
479 * conf->limits.elts = NULL;
482 conf->log_level = NGX_CONF_UNSET_UINT;
483 conf->status_code = NGX_CONF_UNSET_UINT;
485 return conf;
489 static char *
490 ngx_http_limit_conn_merge_conf(ngx_conf_t *cf, void *parent, void *child)
492 ngx_http_limit_conn_conf_t *prev = parent;
493 ngx_http_limit_conn_conf_t *conf = child;
495 if (conf->limits.elts == NULL) {
496 conf->limits = prev->limits;
499 ngx_conf_merge_uint_value(conf->log_level, prev->log_level, NGX_LOG_ERR);
500 ngx_conf_merge_uint_value(conf->status_code, prev->status_code,
501 NGX_HTTP_SERVICE_UNAVAILABLE);
503 return NGX_CONF_OK;
507 static char *
508 ngx_http_limit_conn_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
510 u_char *p;
511 ssize_t size;
512 ngx_str_t *value, name, s;
513 ngx_uint_t i;
514 ngx_shm_zone_t *shm_zone;
515 ngx_http_limit_conn_ctx_t *ctx;
517 value = cf->args->elts;
519 ctx = NULL;
520 size = 0;
521 name.len = 0;
523 for (i = 1; i < cf->args->nelts; i++) {
525 if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
527 name.data = value[i].data + 5;
529 p = (u_char *) ngx_strchr(name.data, ':');
531 if (p == NULL) {
532 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
533 "invalid zone size \"%V\"", &value[i]);
534 return NGX_CONF_ERROR;
537 name.len = p - name.data;
539 s.data = p + 1;
540 s.len = value[i].data + value[i].len - s.data;
542 size = ngx_parse_size(&s);
544 if (size == NGX_ERROR) {
545 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
546 "invalid zone size \"%V\"", &value[i]);
547 return NGX_CONF_ERROR;
550 if (size < (ssize_t) (8 * ngx_pagesize)) {
551 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
552 "zone \"%V\" is too small", &value[i]);
553 return NGX_CONF_ERROR;
556 continue;
559 if (value[i].data[0] == '$') {
561 value[i].len--;
562 value[i].data++;
564 ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_conn_ctx_t));
565 if (ctx == NULL) {
566 return NGX_CONF_ERROR;
569 ctx->index = ngx_http_get_variable_index(cf, &value[i]);
570 if (ctx->index == NGX_ERROR) {
571 return NGX_CONF_ERROR;
574 ctx->var = value[i];
576 continue;
579 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
580 "invalid parameter \"%V\"", &value[i]);
581 return NGX_CONF_ERROR;
584 if (name.len == 0) {
585 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
586 "\"%V\" must have \"zone\" parameter",
587 &cmd->name);
588 return NGX_CONF_ERROR;
591 if (ctx == NULL) {
592 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
593 "no variable is defined for %V \"%V\"",
594 &cmd->name, &name);
595 return NGX_CONF_ERROR;
598 shm_zone = ngx_shared_memory_add(cf, &name, size,
599 &ngx_http_limit_conn_module);
600 if (shm_zone == NULL) {
601 return NGX_CONF_ERROR;
604 if (shm_zone->data) {
605 ctx = shm_zone->data;
607 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
608 "%V \"%V\" is already bound to variable \"%V\"",
609 &cmd->name, &name, &ctx->var);
610 return NGX_CONF_ERROR;
613 shm_zone->init = ngx_http_limit_conn_init_zone;
614 shm_zone->data = ctx;
616 return NGX_CONF_OK;
620 static char *
621 ngx_http_limit_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
623 ssize_t n;
624 ngx_str_t *value;
625 ngx_shm_zone_t *shm_zone;
626 ngx_http_limit_conn_ctx_t *ctx;
628 ngx_conf_deprecated(cf, &ngx_conf_deprecated_limit_zone, NULL);
630 value = cf->args->elts;
632 if (value[2].data[0] != '$') {
633 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
634 "invalid variable name \"%V\"", &value[2]);
635 return NGX_CONF_ERROR;
638 value[2].len--;
639 value[2].data++;
641 ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_conn_ctx_t));
642 if (ctx == NULL) {
643 return NGX_CONF_ERROR;
646 ctx->index = ngx_http_get_variable_index(cf, &value[2]);
647 if (ctx->index == NGX_ERROR) {
648 return NGX_CONF_ERROR;
651 ctx->var = value[2];
653 n = ngx_parse_size(&value[3]);
655 if (n == NGX_ERROR) {
656 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
657 "invalid size of limit_zone \"%V\"", &value[3]);
658 return NGX_CONF_ERROR;
661 if (n < (ngx_int_t) (8 * ngx_pagesize)) {
662 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
663 "limit_zone \"%V\" is too small", &value[1]);
664 return NGX_CONF_ERROR;
668 shm_zone = ngx_shared_memory_add(cf, &value[1], n,
669 &ngx_http_limit_conn_module);
670 if (shm_zone == NULL) {
671 return NGX_CONF_ERROR;
674 if (shm_zone->data) {
675 ctx = shm_zone->data;
677 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
678 "limit_zone \"%V\" is already bound to variable \"%V\"",
679 &value[1], &ctx->var);
680 return NGX_CONF_ERROR;
683 shm_zone->init = ngx_http_limit_conn_init_zone;
684 shm_zone->data = ctx;
686 return NGX_CONF_OK;
690 static char *
691 ngx_http_limit_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
693 ngx_shm_zone_t *shm_zone;
694 ngx_http_limit_conn_conf_t *lccf = conf;
695 ngx_http_limit_conn_limit_t *limit, *limits;
697 ngx_str_t *value;
698 ngx_int_t n;
699 ngx_uint_t i;
701 value = cf->args->elts;
703 shm_zone = ngx_shared_memory_add(cf, &value[1], 0,
704 &ngx_http_limit_conn_module);
705 if (shm_zone == NULL) {
706 return NGX_CONF_ERROR;
709 limits = lccf->limits.elts;
711 if (limits == NULL) {
712 if (ngx_array_init(&lccf->limits, cf->pool, 1,
713 sizeof(ngx_http_limit_conn_limit_t))
714 != NGX_OK)
716 return NGX_CONF_ERROR;
720 for (i = 0; i < lccf->limits.nelts; i++) {
721 if (shm_zone == limits[i].shm_zone) {
722 return "is duplicate";
726 n = ngx_atoi(value[2].data, value[2].len);
727 if (n <= 0) {
728 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
729 "invalid number of connections \"%V\"", &value[2]);
730 return NGX_CONF_ERROR;
733 if (n > 65535) {
734 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
735 "connection limit must be less 65536");
736 return NGX_CONF_ERROR;
739 limit = ngx_array_push(&lccf->limits);
740 if (limit == NULL) {
741 return NGX_CONF_ERROR;
744 limit->conn = n;
745 limit->shm_zone = shm_zone;
747 return NGX_CONF_OK;
751 static ngx_int_t
752 ngx_http_limit_conn_init(ngx_conf_t *cf)
754 ngx_http_handler_pt *h;
755 ngx_http_core_main_conf_t *cmcf;
757 cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
759 h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
760 if (h == NULL) {
761 return NGX_ERROR;
764 *h = ngx_http_limit_conn_handler;
766 return NGX_OK;