Groonga 3.0.9 Source Code Document
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
ngx_http_limit_conn_module.c
Go to the documentation of this file.
1 
2 /*
3  * Copyright (C) Igor Sysoev
4  * Copyright (C) Nginx, Inc.
5  */
6 
7 
8 #include <ngx_config.h>
9 #include <ngx_core.h>
10 #include <ngx_http.h>
11 
12 
13 typedef struct {
14  u_char color;
15  u_char len;
16  u_short conn;
17  u_char data[1];
19 
20 
21 typedef struct {
25 
26 
27 typedef struct {
32 
33 
34 typedef struct {
38 
39 
40 typedef struct {
45 
46 
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);
51 
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);
62 
63 
64 static ngx_conf_deprecated_t ngx_conf_deprecated_limit_zone = {
65  ngx_conf_deprecated, "limit_zone", "limit_conn_zone"
66 };
67 
68 
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 }
75 };
76 
77 
78 static ngx_conf_num_bounds_t ngx_http_limit_conn_status_bounds = {
80 };
81 
82 
83 static ngx_command_t ngx_http_limit_conn_commands[] = {
84 
85  { ngx_string("limit_conn_zone"),
87  ngx_http_limit_conn_zone,
88  0,
89  0,
90  NULL },
91 
92  { ngx_string("limit_zone"),
94  ngx_http_limit_zone,
95  0,
96  0,
97  NULL },
98 
99  { ngx_string("limit_conn"),
101  ngx_http_limit_conn,
103  0,
104  NULL },
105 
106  { ngx_string("limit_conn_log_level"),
110  offsetof(ngx_http_limit_conn_conf_t, log_level),
111  &ngx_http_limit_conn_log_levels },
112 
113  { ngx_string("limit_conn_status"),
117  offsetof(ngx_http_limit_conn_conf_t, status_code),
118  &ngx_http_limit_conn_status_bounds },
119 
121 };
122 
123 
124 static ngx_http_module_t ngx_http_limit_conn_module_ctx = {
125  NULL, /* preconfiguration */
126  ngx_http_limit_conn_init, /* postconfiguration */
127 
128  NULL, /* create main configuration */
129  NULL, /* init main configuration */
130 
131  NULL, /* create server configuration */
132  NULL, /* merge server configuration */
133 
134  ngx_http_limit_conn_create_conf, /* create location configuration */
135  ngx_http_limit_conn_merge_conf /* merge location configuration */
136 };
137 
138 
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 */
152 };
153 
154 
155 static ngx_int_t
156 ngx_http_limit_conn_handler(ngx_http_request_t *r)
157 {
158  size_t len, n;
159  uint32_t hash;
160  ngx_uint_t i;
161  ngx_slab_pool_t *shpool;
163  ngx_pool_cleanup_t *cln;
170 
171  if (r->main->limit_conn_set) {
172  return NGX_DECLINED;
173  }
174 
175  lccf = ngx_http_get_module_loc_conf(r, ngx_http_limit_conn_module);
176  limits = lccf->limits.elts;
177 
178  for (i = 0; i < lccf->limits.nelts; i++) {
179  ctx = limits[i].shm_zone->data;
180 
181  vv = ngx_http_get_indexed_variable(r, ctx->index);
182 
183  if (vv == NULL || vv->not_found) {
184  continue;
185  }
186 
187  len = vv->len;
188 
189  if (len == 0) {
190  continue;
191  }
192 
193  if (len > 255) {
195  "the value of the \"%V\" variable "
196  "is more than 255 bytes: \"%v\"",
197  &ctx->var, vv);
198  continue;
199  }
200 
201  r->main->limit_conn_set = 1;
202 
203  hash = ngx_crc32_short(vv->data, len);
204 
205  shpool = (ngx_slab_pool_t *) limits[i].shm_zone->shm.addr;
206 
207  ngx_shmtx_lock(&shpool->mutex);
208 
209  node = ngx_http_limit_conn_lookup(ctx->rbtree, vv, hash);
210 
211  if (node == NULL) {
212 
213  n = offsetof(ngx_rbtree_node_t, color)
214  + offsetof(ngx_http_limit_conn_node_t, data)
215  + len;
216 
217  node = ngx_slab_alloc_locked(shpool, n);
218 
219  if (node == NULL) {
220  ngx_shmtx_unlock(&shpool->mutex);
221  ngx_http_limit_conn_cleanup_all(r->pool);
222  return lccf->status_code;
223  }
224 
225  lc = (ngx_http_limit_conn_node_t *) &node->color;
226 
227  node->key = hash;
228  lc->len = (u_char) len;
229  lc->conn = 1;
230  ngx_memcpy(lc->data, vv->data, len);
231 
232  ngx_rbtree_insert(ctx->rbtree, node);
233 
234  } else {
235 
236  lc = (ngx_http_limit_conn_node_t *) &node->color;
237 
238  if ((ngx_uint_t) lc->conn >= limits[i].conn) {
239 
240  ngx_shmtx_unlock(&shpool->mutex);
241 
242  ngx_log_error(lccf->log_level, r->connection->log, 0,
243  "limiting connections by zone \"%V\"",
244  &limits[i].shm_zone->shm.name);
245 
246  ngx_http_limit_conn_cleanup_all(r->pool);
247  return lccf->status_code;
248  }
249 
250  lc->conn++;
251  }
252 
254  "limit conn: %08XD %d", node->key, lc->conn);
255 
256  ngx_shmtx_unlock(&shpool->mutex);
257 
258  cln = ngx_pool_cleanup_add(r->pool,
260  if (cln == NULL) {
262  }
263 
264  cln->handler = ngx_http_limit_conn_cleanup;
265  lccln = cln->data;
266 
267  lccln->shm_zone = limits[i].shm_zone;
268  lccln->node = node;
269  }
270 
271  return NGX_DECLINED;
272 }
273 
274 
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)
278 {
279  ngx_rbtree_node_t **p;
280  ngx_http_limit_conn_node_t *lcn, *lcnt;
281 
282  for ( ;; ) {
283 
284  if (node->key < temp->key) {
285 
286  p = &temp->left;
287 
288  } else if (node->key > temp->key) {
289 
290  p = &temp->right;
291 
292  } else { /* node->key == temp->key */
293 
294  lcn = (ngx_http_limit_conn_node_t *) &node->color;
295  lcnt = (ngx_http_limit_conn_node_t *) &temp->color;
296 
297  p = (ngx_memn2cmp(lcn->data, lcnt->data, lcn->len, lcnt->len) < 0)
298  ? &temp->left : &temp->right;
299  }
300 
301  if (*p == sentinel) {
302  break;
303  }
304 
305  temp = *p;
306  }
307 
308  *p = node;
309  node->parent = temp;
310  node->left = sentinel;
311  node->right = sentinel;
312  ngx_rbt_red(node);
313 }
314 
315 
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)
319 {
320  ngx_int_t rc;
321  ngx_rbtree_node_t *node, *sentinel;
323 
324  node = rbtree->root;
325  sentinel = rbtree->sentinel;
326 
327  while (node != sentinel) {
328 
329  if (hash < node->key) {
330  node = node->left;
331  continue;
332  }
333 
334  if (hash > node->key) {
335  node = node->right;
336  continue;
337  }
338 
339  /* hash == node->key */
340 
341  lcn = (ngx_http_limit_conn_node_t *) &node->color;
342 
343  rc = ngx_memn2cmp(vv->data, lcn->data,
344  (size_t) vv->len, (size_t) lcn->len);
345  if (rc == 0) {
346  return node;
347  }
348 
349  node = (rc < 0) ? node->left : node->right;
350  }
351 
352  return NULL;
353 }
354 
355 
356 static void
357 ngx_http_limit_conn_cleanup(void *data)
358 {
359  ngx_http_limit_conn_cleanup_t *lccln = data;
360 
361  ngx_slab_pool_t *shpool;
365 
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;
370 
371  ngx_shmtx_lock(&shpool->mutex);
372 
374  "limit conn cleanup: %08XD %d", node->key, lc->conn);
375 
376  lc->conn--;
377 
378  if (lc->conn == 0) {
379  ngx_rbtree_delete(ctx->rbtree, node);
380  ngx_slab_free_locked(shpool, node);
381  }
382 
383  ngx_shmtx_unlock(&shpool->mutex);
384 }
385 
386 
387 static ngx_inline void
388 ngx_http_limit_conn_cleanup_all(ngx_pool_t *pool)
389 {
390  ngx_pool_cleanup_t *cln;
391 
392  cln = pool->cleanup;
393 
394  while (cln && cln->handler == ngx_http_limit_conn_cleanup) {
395  ngx_http_limit_conn_cleanup(cln->data);
396  cln = cln->next;
397  }
398 
399  pool->cleanup = cln;
400 }
401 
402 
403 static ngx_int_t
404 ngx_http_limit_conn_init_zone(ngx_shm_zone_t *shm_zone, void *data)
405 {
406  ngx_http_limit_conn_ctx_t *octx = data;
407 
408  size_t len;
409  ngx_slab_pool_t *shpool;
410  ngx_rbtree_node_t *sentinel;
412 
413  ctx = shm_zone->data;
414 
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;
422  }
423 
424  ctx->rbtree = octx->rbtree;
425 
426  return NGX_OK;
427  }
428 
429  shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
430 
431  if (shm_zone->shm.exists) {
432  ctx->rbtree = shpool->data;
433 
434  return NGX_OK;
435  }
436 
437  ctx->rbtree = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_t));
438  if (ctx->rbtree == NULL) {
439  return NGX_ERROR;
440  }
441 
442  shpool->data = ctx->rbtree;
443 
444  sentinel = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_node_t));
445  if (sentinel == NULL) {
446  return NGX_ERROR;
447  }
448 
449  ngx_rbtree_init(ctx->rbtree, sentinel,
450  ngx_http_limit_conn_rbtree_insert_value);
451 
452  len = sizeof(" in limit_conn_zone \"\"") + shm_zone->shm.name.len;
453 
454  shpool->log_ctx = ngx_slab_alloc(shpool, len);
455  if (shpool->log_ctx == NULL) {
456  return NGX_ERROR;
457  }
458 
459  ngx_sprintf(shpool->log_ctx, " in limit_conn_zone \"%V\"%Z",
460  &shm_zone->shm.name);
461 
462  return NGX_OK;
463 }
464 
465 
466 static void *
467 ngx_http_limit_conn_create_conf(ngx_conf_t *cf)
468 {
470 
471  conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_conn_conf_t));
472  if (conf == NULL) {
473  return NULL;
474  }
475 
476  /*
477  * set by ngx_pcalloc():
478  *
479  * conf->limits.elts = NULL;
480  */
481 
484 
485  return conf;
486 }
487 
488 
489 static char *
490 ngx_http_limit_conn_merge_conf(ngx_conf_t *cf, void *parent, void *child)
491 {
492  ngx_http_limit_conn_conf_t *prev = parent;
493  ngx_http_limit_conn_conf_t *conf = child;
494 
495  if (conf->limits.elts == NULL) {
496  conf->limits = prev->limits;
497  }
498 
502 
503  return NGX_CONF_OK;
504 }
505 
506 
507 static char *
508 ngx_http_limit_conn_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
509 {
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;
516 
517  value = cf->args->elts;
518 
519  ctx = NULL;
520  size = 0;
521  name.len = 0;
522 
523  for (i = 1; i < cf->args->nelts; i++) {
524 
525  if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
526 
527  name.data = value[i].data + 5;
528 
529  p = (u_char *) ngx_strchr(name.data, ':');
530 
531  if (p == NULL) {
533  "invalid zone size \"%V\"", &value[i]);
534  return NGX_CONF_ERROR;
535  }
536 
537  name.len = p - name.data;
538 
539  s.data = p + 1;
540  s.len = value[i].data + value[i].len - s.data;
541 
542  size = ngx_parse_size(&s);
543 
544  if (size == NGX_ERROR) {
546  "invalid zone size \"%V\"", &value[i]);
547  return NGX_CONF_ERROR;
548  }
549 
550  if (size < (ssize_t) (8 * ngx_pagesize)) {
552  "zone \"%V\" is too small", &value[i]);
553  return NGX_CONF_ERROR;
554  }
555 
556  continue;
557  }
558 
559  if (value[i].data[0] == '$') {
560 
561  value[i].len--;
562  value[i].data++;
563 
564  ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_conn_ctx_t));
565  if (ctx == NULL) {
566  return NGX_CONF_ERROR;
567  }
568 
569  ctx->index = ngx_http_get_variable_index(cf, &value[i]);
570  if (ctx->index == NGX_ERROR) {
571  return NGX_CONF_ERROR;
572  }
573 
574  ctx->var = value[i];
575 
576  continue;
577  }
578 
580  "invalid parameter \"%V\"", &value[i]);
581  return NGX_CONF_ERROR;
582  }
583 
584  if (name.len == 0) {
586  "\"%V\" must have \"zone\" parameter",
587  &cmd->name);
588  return NGX_CONF_ERROR;
589  }
590 
591  if (ctx == NULL) {
593  "no variable is defined for %V \"%V\"",
594  &cmd->name, &name);
595  return NGX_CONF_ERROR;
596  }
597 
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;
602  }
603 
604  if (shm_zone->data) {
605  ctx = shm_zone->data;
606 
608  "%V \"%V\" is already bound to variable \"%V\"",
609  &cmd->name, &name, &ctx->var);
610  return NGX_CONF_ERROR;
611  }
612 
613  shm_zone->init = ngx_http_limit_conn_init_zone;
614  shm_zone->data = ctx;
615 
616  return NGX_CONF_OK;
617 }
618 
619 
620 static char *
621 ngx_http_limit_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
622 {
623  ssize_t n;
624  ngx_str_t *value;
625  ngx_shm_zone_t *shm_zone;
627 
628  ngx_conf_deprecated(cf, &ngx_conf_deprecated_limit_zone, NULL);
629 
630  value = cf->args->elts;
631 
632  if (value[2].data[0] != '$') {
634  "invalid variable name \"%V\"", &value[2]);
635  return NGX_CONF_ERROR;
636  }
637 
638  value[2].len--;
639  value[2].data++;
640 
641  ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_conn_ctx_t));
642  if (ctx == NULL) {
643  return NGX_CONF_ERROR;
644  }
645 
646  ctx->index = ngx_http_get_variable_index(cf, &value[2]);
647  if (ctx->index == NGX_ERROR) {
648  return NGX_CONF_ERROR;
649  }
650 
651  ctx->var = value[2];
652 
653  n = ngx_parse_size(&value[3]);
654 
655  if (n == NGX_ERROR) {
657  "invalid size of limit_zone \"%V\"", &value[3]);
658  return NGX_CONF_ERROR;
659  }
660 
661  if (n < (ngx_int_t) (8 * ngx_pagesize)) {
663  "limit_zone \"%V\" is too small", &value[1]);
664  return NGX_CONF_ERROR;
665  }
666 
667 
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;
672  }
673 
674  if (shm_zone->data) {
675  ctx = shm_zone->data;
676 
678  "limit_zone \"%V\" is already bound to variable \"%V\"",
679  &value[1], &ctx->var);
680  return NGX_CONF_ERROR;
681  }
682 
683  shm_zone->init = ngx_http_limit_conn_init_zone;
684  shm_zone->data = ctx;
685 
686  return NGX_CONF_OK;
687 }
688 
689 
690 static char *
691 ngx_http_limit_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
692 {
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;
696 
697  ngx_str_t *value;
698  ngx_int_t n;
699  ngx_uint_t i;
700 
701  value = cf->args->elts;
702 
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;
707  }
708 
709  limits = lccf->limits.elts;
710 
711  if (limits == NULL) {
712  if (ngx_array_init(&lccf->limits, cf->pool, 1,
714  != NGX_OK)
715  {
716  return NGX_CONF_ERROR;
717  }
718  }
719 
720  for (i = 0; i < lccf->limits.nelts; i++) {
721  if (shm_zone == limits[i].shm_zone) {
722  return "is duplicate";
723  }
724  }
725 
726  n = ngx_atoi(value[2].data, value[2].len);
727  if (n <= 0) {
729  "invalid number of connections \"%V\"", &value[2]);
730  return NGX_CONF_ERROR;
731  }
732 
733  if (n > 65535) {
735  "connection limit must be less 65536");
736  return NGX_CONF_ERROR;
737  }
738 
739  limit = ngx_array_push(&lccf->limits);
740  if (limit == NULL) {
741  return NGX_CONF_ERROR;
742  }
743 
744  limit->conn = n;
745  limit->shm_zone = shm_zone;
746 
747  return NGX_CONF_OK;
748 }
749 
750 
751 static ngx_int_t
752 ngx_http_limit_conn_init(ngx_conf_t *cf)
753 {
756 
758 
760  if (h == NULL) {
761  return NGX_ERROR;
762  }
763 
764  *h = ngx_http_limit_conn_handler;
765 
766  return NGX_OK;
767 }