3 * Copyright (C) Igor Sysoev
4 * Copyright (C) Nginx, Inc.
8 #include <ngx_config.h>
18 } ngx_http_limit_conn_node_t
;
22 ngx_shm_zone_t
*shm_zone
;
23 ngx_rbtree_node_t
*node
;
24 } ngx_http_limit_conn_cleanup_t
;
31 } ngx_http_limit_conn_ctx_t
;
35 ngx_shm_zone_t
*shm_zone
;
37 } ngx_http_limit_conn_limit_t
;
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
,
55 static char *ngx_http_limit_conn_zone(ngx_conf_t
*cf
, ngx_command_t
*cmd
,
57 static char *ngx_http_limit_zone(ngx_conf_t
*cf
, ngx_command_t
*cmd
,
59 static char *ngx_http_limit_conn(ngx_conf_t
*cf
, ngx_command_t
*cmd
,
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
,
92 { ngx_string("limit_zone"),
93 NGX_HTTP_MAIN_CONF
|NGX_CONF_TAKE3
,
99 { ngx_string("limit_conn"),
100 NGX_HTTP_MAIN_CONF
|NGX_HTTP_SRV_CONF
|NGX_HTTP_LOC_CONF
|NGX_CONF_TAKE2
,
102 NGX_HTTP_LOC_CONF_OFFSET
,
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
},
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
= {
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
156 ngx_http_limit_conn_handler(ngx_http_request_t
*r
)
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
) {
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
) {
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\"",
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
);
213 n
= offsetof(ngx_rbtree_node_t
, color
)
214 + offsetof(ngx_http_limit_conn_node_t
, data
)
217 node
= ngx_slab_alloc_locked(shpool
, n
);
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
;
228 lc
->len
= (u_char
) len
;
230 ngx_memcpy(lc
->data
, vv
->data
, len
);
232 ngx_rbtree_insert(ctx
->rbtree
, node
);
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
;
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
));
261 return NGX_HTTP_INTERNAL_SERVER_ERROR
;
264 cln
->handler
= ngx_http_limit_conn_cleanup
;
267 lccln
->shm_zone
= limits
[i
].shm_zone
;
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
;
284 if (node
->key
< temp
->key
) {
288 } else if (node
->key
> temp
->key
) {
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
) {
310 node
->left
= sentinel
;
311 node
->right
= sentinel
;
316 static ngx_rbtree_node_t
*
317 ngx_http_limit_conn_lookup(ngx_rbtree_t
*rbtree
, ngx_http_variable_value_t
*vv
,
321 ngx_rbtree_node_t
*node
, *sentinel
;
322 ngx_http_limit_conn_node_t
*lcn
;
325 sentinel
= rbtree
->sentinel
;
327 while (node
!= sentinel
) {
329 if (hash
< node
->key
) {
334 if (hash
> node
->key
) {
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
);
349 node
= (rc
< 0) ? node
->left
: node
->right
;
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
;
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
);
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
;
394 while (cln
&& cln
->handler
== ngx_http_limit_conn_cleanup
) {
395 ngx_http_limit_conn_cleanup(cln
->data
);
404 ngx_http_limit_conn_init_zone(ngx_shm_zone_t
*shm_zone
, void *data
)
406 ngx_http_limit_conn_ctx_t
*octx
= data
;
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
;
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
);
424 ctx
->rbtree
= octx
->rbtree
;
429 shpool
= (ngx_slab_pool_t
*) shm_zone
->shm
.addr
;
431 if (shm_zone
->shm
.exists
) {
432 ctx
->rbtree
= shpool
->data
;
437 ctx
->rbtree
= ngx_slab_alloc(shpool
, sizeof(ngx_rbtree_t
));
438 if (ctx
->rbtree
== NULL
) {
442 shpool
->data
= ctx
->rbtree
;
444 sentinel
= ngx_slab_alloc(shpool
, sizeof(ngx_rbtree_node_t
));
445 if (sentinel
== NULL
) {
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
) {
459 ngx_sprintf(shpool
->log_ctx
, " in limit_conn_zone \"%V\"%Z",
460 &shm_zone
->shm
.name
);
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
));
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
;
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
);
508 ngx_http_limit_conn_zone(ngx_conf_t
*cf
, ngx_command_t
*cmd
, void *conf
)
512 ngx_str_t
*value
, name
, s
;
514 ngx_shm_zone_t
*shm_zone
;
515 ngx_http_limit_conn_ctx_t
*ctx
;
517 value
= cf
->args
->elts
;
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
, ':');
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
;
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
;
559 if (value
[i
].data
[0] == '$') {
564 ctx
= ngx_pcalloc(cf
->pool
, sizeof(ngx_http_limit_conn_ctx_t
));
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
;
579 ngx_conf_log_error(NGX_LOG_EMERG
, cf
, 0,
580 "invalid parameter \"%V\"", &value
[i
]);
581 return NGX_CONF_ERROR
;
585 ngx_conf_log_error(NGX_LOG_EMERG
, cf
, 0,
586 "\"%V\" must have \"zone\" parameter",
588 return NGX_CONF_ERROR
;
592 ngx_conf_log_error(NGX_LOG_EMERG
, cf
, 0,
593 "no variable is defined for %V \"%V\"",
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
;
621 ngx_http_limit_zone(ngx_conf_t
*cf
, ngx_command_t
*cmd
, void *conf
)
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
;
641 ctx
= ngx_pcalloc(cf
->pool
, sizeof(ngx_http_limit_conn_ctx_t
));
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
;
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
;
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
;
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
))
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
);
728 ngx_conf_log_error(NGX_LOG_EMERG
, cf
, 0,
729 "invalid number of connections \"%V\"", &value
[2]);
730 return NGX_CONF_ERROR
;
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
);
741 return NGX_CONF_ERROR
;
745 limit
->shm_zone
= shm_zone
;
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
);
764 *h
= ngx_http_limit_conn_handler
;