src/http/modules/ngx_http_limit_conn_module.c - nginx-1.7.10

Global variables defined

Data types defined

Functions defined

Source code


  1. /*
  2. * Copyright (C) Igor Sysoev
  3. * Copyright (C) Nginx, Inc.
  4. */


  5. #include <ngx_config.h>
  6. #include <ngx_core.h>
  7. #include <ngx_http.h>


  8. typedef struct {
  9.     u_char                     color;
  10.     u_char                     len;
  11.     u_short                    conn;
  12.     u_char                     data[1];
  13. } ngx_http_limit_conn_node_t;


  14. typedef struct {
  15.     ngx_shm_zone_t            *shm_zone;
  16.     ngx_rbtree_node_t         *node;
  17. } ngx_http_limit_conn_cleanup_t;


  18. typedef struct {
  19.     ngx_rbtree_t              *rbtree;
  20.     ngx_http_complex_value_t   key;
  21. } ngx_http_limit_conn_ctx_t;


  22. typedef struct {
  23.     ngx_shm_zone_t            *shm_zone;
  24.     ngx_uint_t                 conn;
  25. } ngx_http_limit_conn_limit_t;


  26. typedef struct {
  27.     ngx_array_t                limits;
  28.     ngx_uint_t                 log_level;
  29.     ngx_uint_t                 status_code;
  30. } ngx_http_limit_conn_conf_t;


  31. static ngx_rbtree_node_t *ngx_http_limit_conn_lookup(ngx_rbtree_t *rbtree,
  32.     ngx_str_t *key, uint32_t hash);
  33. static void ngx_http_limit_conn_cleanup(void *data);
  34. static ngx_inline void ngx_http_limit_conn_cleanup_all(ngx_pool_t *pool);

  35. static void *ngx_http_limit_conn_create_conf(ngx_conf_t *cf);
  36. static char *ngx_http_limit_conn_merge_conf(ngx_conf_t *cf, void *parent,
  37.     void *child);
  38. static char *ngx_http_limit_conn_zone(ngx_conf_t *cf, ngx_command_t *cmd,
  39.     void *conf);
  40. static char *ngx_http_limit_conn(ngx_conf_t *cf, ngx_command_t *cmd,
  41.     void *conf);
  42. static ngx_int_t ngx_http_limit_conn_init(ngx_conf_t *cf);


  43. static ngx_conf_enum_t  ngx_http_limit_conn_log_levels[] = {
  44.     { ngx_string("info"), NGX_LOG_INFO },
  45.     { ngx_string("notice"), NGX_LOG_NOTICE },
  46.     { ngx_string("warn"), NGX_LOG_WARN },
  47.     { ngx_string("error"), NGX_LOG_ERR },
  48.     { ngx_null_string, 0 }
  49. };


  50. static ngx_conf_num_bounds_t  ngx_http_limit_conn_status_bounds = {
  51.     ngx_conf_check_num_bounds, 400, 599
  52. };


  53. static ngx_command_t  ngx_http_limit_conn_commands[] = {

  54.     { ngx_string("limit_conn_zone"),
  55.       NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE2,
  56.       ngx_http_limit_conn_zone,
  57.       0,
  58.       0,
  59.       NULL },

  60.     { ngx_string("limit_conn"),
  61.       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
  62.       ngx_http_limit_conn,
  63.       NGX_HTTP_LOC_CONF_OFFSET,
  64.       0,
  65.       NULL },

  66.     { ngx_string("limit_conn_log_level"),
  67.       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
  68.       ngx_conf_set_enum_slot,
  69.       NGX_HTTP_LOC_CONF_OFFSET,
  70.       offsetof(ngx_http_limit_conn_conf_t, log_level),
  71.       &ngx_http_limit_conn_log_levels },

  72.     { ngx_string("limit_conn_status"),
  73.       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
  74.       ngx_conf_set_num_slot,
  75.       NGX_HTTP_LOC_CONF_OFFSET,
  76.       offsetof(ngx_http_limit_conn_conf_t, status_code),
  77.       &ngx_http_limit_conn_status_bounds },

  78.       ngx_null_command
  79. };


  80. static ngx_http_module_t  ngx_http_limit_conn_module_ctx = {
  81.     NULL,                                  /* preconfiguration */
  82.     ngx_http_limit_conn_init,              /* postconfiguration */

  83.     NULL,                                  /* create main configuration */
  84.     NULL,                                  /* init main configuration */

  85.     NULL,                                  /* create server configuration */
  86.     NULL,                                  /* merge server configuration */

  87.     ngx_http_limit_conn_create_conf,       /* create location configuration */
  88.     ngx_http_limit_conn_merge_conf         /* merge location configuration */
  89. };


  90. ngx_module_t  ngx_http_limit_conn_module = {
  91.     NGX_MODULE_V1,
  92.     &ngx_http_limit_conn_module_ctx,       /* module context */
  93.     ngx_http_limit_conn_commands,          /* module directives */
  94.     NGX_HTTP_MODULE,                       /* module type */
  95.     NULL,                                  /* init master */
  96.     NULL,                                  /* init module */
  97.     NULL,                                  /* init process */
  98.     NULL,                                  /* init thread */
  99.     NULL,                                  /* exit thread */
  100.     NULL,                                  /* exit process */
  101.     NULL,                                  /* exit master */
  102.     NGX_MODULE_V1_PADDING
  103. };


  104. static ngx_int_t
  105. ngx_http_limit_conn_handler(ngx_http_request_t *r)
  106. {
  107.     size_t                          n;
  108.     uint32_t                        hash;
  109.     ngx_str_t                       key;
  110.     ngx_uint_t                      i;
  111.     ngx_slab_pool_t                *shpool;
  112.     ngx_rbtree_node_t              *node;
  113.     ngx_pool_cleanup_t             *cln;
  114.     ngx_http_limit_conn_ctx_t      *ctx;
  115.     ngx_http_limit_conn_node_t     *lc;
  116.     ngx_http_limit_conn_conf_t     *lccf;
  117.     ngx_http_limit_conn_limit_t    *limits;
  118.     ngx_http_limit_conn_cleanup_t  *lccln;

  119.     if (r->main->limit_conn_set) {
  120.         return NGX_DECLINED;
  121.     }

  122.     lccf = ngx_http_get_module_loc_conf(r, ngx_http_limit_conn_module);
  123.     limits = lccf->limits.elts;

  124.     for (i = 0; i < lccf->limits.nelts; i++) {
  125.         ctx = limits[i].shm_zone->data;

  126.         if (ngx_http_complex_value(r, &ctx->key, &key) != NGX_OK) {
  127.             return NGX_HTTP_INTERNAL_SERVER_ERROR;
  128.         }

  129.         if (key.len == 0) {
  130.             continue;
  131.         }

  132.         if (key.len > 255) {
  133.             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
  134.                           "the value of the \"%V\" key "
  135.                           "is more than 255 bytes: \"%V\"",
  136.                           &ctx->key.value, &key);
  137.             continue;
  138.         }

  139.         r->main->limit_conn_set = 1;

  140.         hash = ngx_crc32_short(key.data, key.len);

  141.         shpool = (ngx_slab_pool_t *) limits[i].shm_zone->shm.addr;

  142.         ngx_shmtx_lock(&shpool->mutex);

  143.         node = ngx_http_limit_conn_lookup(ctx->rbtree, &key, hash);

  144.         if (node == NULL) {

  145.             n = offsetof(ngx_rbtree_node_t, color)
  146.                 + offsetof(ngx_http_limit_conn_node_t, data)
  147.                 + key.len;

  148.             node = ngx_slab_alloc_locked(shpool, n);

  149.             if (node == NULL) {
  150.                 ngx_shmtx_unlock(&shpool->mutex);
  151.                 ngx_http_limit_conn_cleanup_all(r->pool);
  152.                 return lccf->status_code;
  153.             }

  154.             lc = (ngx_http_limit_conn_node_t *) &node->color;

  155.             node->key = hash;
  156.             lc->len = (u_char) key.len;
  157.             lc->conn = 1;
  158.             ngx_memcpy(lc->data, key.data, key.len);

  159.             ngx_rbtree_insert(ctx->rbtree, node);

  160.         } else {

  161.             lc = (ngx_http_limit_conn_node_t *) &node->color;

  162.             if ((ngx_uint_t) lc->conn >= limits[i].conn) {

  163.                 ngx_shmtx_unlock(&shpool->mutex);

  164.                 ngx_log_error(lccf->log_level, r->connection->log, 0,
  165.                               "limiting connections by zone \"%V\"",
  166.                               &limits[i].shm_zone->shm.name);

  167.                 ngx_http_limit_conn_cleanup_all(r->pool);
  168.                 return lccf->status_code;
  169.             }

  170.             lc->conn++;
  171.         }

  172.         ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  173.                        "limit conn: %08XD %d", node->key, lc->conn);

  174.         ngx_shmtx_unlock(&shpool->mutex);

  175.         cln = ngx_pool_cleanup_add(r->pool,
  176.                                    sizeof(ngx_http_limit_conn_cleanup_t));
  177.         if (cln == NULL) {
  178.             return NGX_HTTP_INTERNAL_SERVER_ERROR;
  179.         }

  180.         cln->handler = ngx_http_limit_conn_cleanup;
  181.         lccln = cln->data;

  182.         lccln->shm_zone = limits[i].shm_zone;
  183.         lccln->node = node;
  184.     }

  185.     return NGX_DECLINED;
  186. }


  187. static void
  188. ngx_http_limit_conn_rbtree_insert_value(ngx_rbtree_node_t *temp,
  189.     ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
  190. {
  191.     ngx_rbtree_node_t           **p;
  192.     ngx_http_limit_conn_node_t   *lcn, *lcnt;

  193.     for ( ;; ) {

  194.         if (node->key < temp->key) {

  195.             p = &temp->left;

  196.         } else if (node->key > temp->key) {

  197.             p = &temp->right;

  198.         } else { /* node->key == temp->key */

  199.             lcn = (ngx_http_limit_conn_node_t *) &node->color;
  200.             lcnt = (ngx_http_limit_conn_node_t *) &temp->color;

  201.             p = (ngx_memn2cmp(lcn->data, lcnt->data, lcn->len, lcnt->len) < 0)
  202.                 ? &temp->left : &temp->right;
  203.         }

  204.         if (*p == sentinel) {
  205.             break;
  206.         }

  207.         temp = *p;
  208.     }

  209.     *p = node;
  210.     node->parent = temp;
  211.     node->left = sentinel;
  212.     node->right = sentinel;
  213.     ngx_rbt_red(node);
  214. }


  215. static ngx_rbtree_node_t *
  216. ngx_http_limit_conn_lookup(ngx_rbtree_t *rbtree, ngx_str_t *key, uint32_t hash)
  217. {
  218.     ngx_int_t                    rc;
  219.     ngx_rbtree_node_t           *node, *sentinel;
  220.     ngx_http_limit_conn_node_t  *lcn;

  221.     node = rbtree->root;
  222.     sentinel = rbtree->sentinel;

  223.     while (node != sentinel) {

  224.         if (hash < node->key) {
  225.             node = node->left;
  226.             continue;
  227.         }

  228.         if (hash > node->key) {
  229.             node = node->right;
  230.             continue;
  231.         }

  232.         /* hash == node->key */

  233.         lcn = (ngx_http_limit_conn_node_t *) &node->color;

  234.         rc = ngx_memn2cmp(key->data, lcn->data, key->len, (size_t) lcn->len);

  235.         if (rc == 0) {
  236.             return node;
  237.         }

  238.         node = (rc < 0) ? node->left : node->right;
  239.     }

  240.     return NULL;
  241. }


  242. static void
  243. ngx_http_limit_conn_cleanup(void *data)
  244. {
  245.     ngx_http_limit_conn_cleanup_t  *lccln = data;

  246.     ngx_slab_pool_t             *shpool;
  247.     ngx_rbtree_node_t           *node;
  248.     ngx_http_limit_conn_ctx_t   *ctx;
  249.     ngx_http_limit_conn_node_t  *lc;

  250.     ctx = lccln->shm_zone->data;
  251.     shpool = (ngx_slab_pool_t *) lccln->shm_zone->shm.addr;
  252.     node = lccln->node;
  253.     lc = (ngx_http_limit_conn_node_t *) &node->color;

  254.     ngx_shmtx_lock(&shpool->mutex);

  255.     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, lccln->shm_zone->shm.log, 0,
  256.                    "limit conn cleanup: %08XD %d", node->key, lc->conn);

  257.     lc->conn--;

  258.     if (lc->conn == 0) {
  259.         ngx_rbtree_delete(ctx->rbtree, node);
  260.         ngx_slab_free_locked(shpool, node);
  261.     }

  262.     ngx_shmtx_unlock(&shpool->mutex);
  263. }


  264. static ngx_inline void
  265. ngx_http_limit_conn_cleanup_all(ngx_pool_t *pool)
  266. {
  267.     ngx_pool_cleanup_t  *cln;

  268.     cln = pool->cleanup;

  269.     while (cln && cln->handler == ngx_http_limit_conn_cleanup) {
  270.         ngx_http_limit_conn_cleanup(cln->data);
  271.         cln = cln->next;
  272.     }

  273.     pool->cleanup = cln;
  274. }


  275. static ngx_int_t
  276. ngx_http_limit_conn_init_zone(ngx_shm_zone_t *shm_zone, void *data)
  277. {
  278.     ngx_http_limit_conn_ctx_t  *octx = data;

  279.     size_t                      len;
  280.     ngx_slab_pool_t            *shpool;
  281.     ngx_rbtree_node_t          *sentinel;
  282.     ngx_http_limit_conn_ctx_t  *ctx;

  283.     ctx = shm_zone->data;

  284.     if (octx) {
  285.         if (ctx->key.value.len != octx->key.value.len
  286.             || ngx_strncmp(ctx->key.value.data, octx->key.value.data,
  287.                            ctx->key.value.len)
  288.                != 0)
  289.         {
  290.             ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
  291.                           "limit_conn_zone \"%V\" uses the \"%V\" key "
  292.                           "while previously it used the \"%V\" key",
  293.                           &shm_zone->shm.name, &ctx->key.value,
  294.                           &octx->key.value);
  295.             return NGX_ERROR;
  296.         }

  297.         ctx->rbtree = octx->rbtree;

  298.         return NGX_OK;
  299.     }

  300.     shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;

  301.     if (shm_zone->shm.exists) {
  302.         ctx->rbtree = shpool->data;

  303.         return NGX_OK;
  304.     }

  305.     ctx->rbtree = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_t));
  306.     if (ctx->rbtree == NULL) {
  307.         return NGX_ERROR;
  308.     }

  309.     shpool->data = ctx->rbtree;

  310.     sentinel = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_node_t));
  311.     if (sentinel == NULL) {
  312.         return NGX_ERROR;
  313.     }

  314.     ngx_rbtree_init(ctx->rbtree, sentinel,
  315.                     ngx_http_limit_conn_rbtree_insert_value);

  316.     len = sizeof(" in limit_conn_zone \"\"") + shm_zone->shm.name.len;

  317.     shpool->log_ctx = ngx_slab_alloc(shpool, len);
  318.     if (shpool->log_ctx == NULL) {
  319.         return NGX_ERROR;
  320.     }

  321.     ngx_sprintf(shpool->log_ctx, " in limit_conn_zone \"%V\"%Z",
  322.                 &shm_zone->shm.name);

  323.     return NGX_OK;
  324. }


  325. static void *
  326. ngx_http_limit_conn_create_conf(ngx_conf_t *cf)
  327. {
  328.     ngx_http_limit_conn_conf_t  *conf;

  329.     conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_conn_conf_t));
  330.     if (conf == NULL) {
  331.         return NULL;
  332.     }

  333.     /*
  334.      * set by ngx_pcalloc():
  335.      *
  336.      *     conf->limits.elts = NULL;
  337.      */

  338.     conf->log_level = NGX_CONF_UNSET_UINT;
  339.     conf->status_code = NGX_CONF_UNSET_UINT;

  340.     return conf;
  341. }


  342. static char *
  343. ngx_http_limit_conn_merge_conf(ngx_conf_t *cf, void *parent, void *child)
  344. {
  345.     ngx_http_limit_conn_conf_t *prev = parent;
  346.     ngx_http_limit_conn_conf_t *conf = child;

  347.     if (conf->limits.elts == NULL) {
  348.         conf->limits = prev->limits;
  349.     }

  350.     ngx_conf_merge_uint_value(conf->log_level, prev->log_level, NGX_LOG_ERR);
  351.     ngx_conf_merge_uint_value(conf->status_code, prev->status_code,
  352.                               NGX_HTTP_SERVICE_UNAVAILABLE);

  353.     return NGX_CONF_OK;
  354. }


  355. static char *
  356. ngx_http_limit_conn_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
  357. {
  358.     u_char                            *p;
  359.     ssize_t                            size;
  360.     ngx_str_t                         *value, name, s;
  361.     ngx_uint_t                         i;
  362.     ngx_shm_zone_t                    *shm_zone;
  363.     ngx_http_limit_conn_ctx_t         *ctx;
  364.     ngx_http_compile_complex_value_t   ccv;

  365.     value = cf->args->elts;

  366.     ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_conn_ctx_t));
  367.     if (ctx == NULL) {
  368.         return NGX_CONF_ERROR;
  369.     }

  370.     ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

  371.     ccv.cf = cf;
  372.     ccv.value = &value[1];
  373.     ccv.complex_value = &ctx->key;

  374.     if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
  375.         return NGX_CONF_ERROR;
  376.     }

  377.     size = 0;
  378.     name.len = 0;

  379.     for (i = 2; i < cf->args->nelts; i++) {

  380.         if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {

  381.             name.data = value[i].data + 5;

  382.             p = (u_char *) ngx_strchr(name.data, ':');

  383.             if (p == NULL) {
  384.                 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  385.                                    "invalid zone size \"%V\"", &value[i]);
  386.                 return NGX_CONF_ERROR;
  387.             }

  388.             name.len = p - name.data;

  389.             s.data = p + 1;
  390.             s.len = value[i].data + value[i].len - s.data;

  391.             size = ngx_parse_size(&s);

  392.             if (size == NGX_ERROR) {
  393.                 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  394.                                    "invalid zone size \"%V\"", &value[i]);
  395.                 return NGX_CONF_ERROR;
  396.             }

  397.             if (size < (ssize_t) (8 * ngx_pagesize)) {
  398.                 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  399.                                    "zone \"%V\" is too small", &value[i]);
  400.                 return NGX_CONF_ERROR;
  401.             }

  402.             continue;
  403.         }

  404.         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  405.                            "invalid parameter \"%V\"", &value[i]);
  406.         return NGX_CONF_ERROR;
  407.     }

  408.     if (name.len == 0) {
  409.         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  410.                            "\"%V\" must have \"zone\" parameter",
  411.                            &cmd->name);
  412.         return NGX_CONF_ERROR;
  413.     }

  414.     shm_zone = ngx_shared_memory_add(cf, &name, size,
  415.                                      &ngx_http_limit_conn_module);
  416.     if (shm_zone == NULL) {
  417.         return NGX_CONF_ERROR;
  418.     }

  419.     if (shm_zone->data) {
  420.         ctx = shm_zone->data;

  421.         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  422.                            "%V \"%V\" is already bound to key \"%V\"",
  423.                            &cmd->name, &name, &ctx->key.value);
  424.         return NGX_CONF_ERROR;
  425.     }

  426.     shm_zone->init = ngx_http_limit_conn_init_zone;
  427.     shm_zone->data = ctx;

  428.     return NGX_CONF_OK;
  429. }


  430. static char *
  431. ngx_http_limit_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
  432. {
  433.     ngx_shm_zone_t               *shm_zone;
  434.     ngx_http_limit_conn_conf_t   *lccf = conf;
  435.     ngx_http_limit_conn_limit_t  *limit, *limits;

  436.     ngx_str_t  *value;
  437.     ngx_int_t   n;
  438.     ngx_uint_t  i;

  439.     value = cf->args->elts;

  440.     shm_zone = ngx_shared_memory_add(cf, &value[1], 0,
  441.                                      &ngx_http_limit_conn_module);
  442.     if (shm_zone == NULL) {
  443.         return NGX_CONF_ERROR;
  444.     }

  445.     limits = lccf->limits.elts;

  446.     if (limits == NULL) {
  447.         if (ngx_array_init(&lccf->limits, cf->pool, 1,
  448.                            sizeof(ngx_http_limit_conn_limit_t))
  449.             != NGX_OK)
  450.         {
  451.             return NGX_CONF_ERROR;
  452.         }
  453.     }

  454.     for (i = 0; i < lccf->limits.nelts; i++) {
  455.         if (shm_zone == limits[i].shm_zone) {
  456.             return "is duplicate";
  457.         }
  458.     }

  459.     n = ngx_atoi(value[2].data, value[2].len);
  460.     if (n <= 0) {
  461.         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  462.                            "invalid number of connections \"%V\"", &value[2]);
  463.         return NGX_CONF_ERROR;
  464.     }

  465.     if (n > 65535) {
  466.         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  467.                            "connection limit must be less 65536");
  468.         return NGX_CONF_ERROR;
  469.     }

  470.     limit = ngx_array_push(&lccf->limits);
  471.     if (limit == NULL) {
  472.         return NGX_CONF_ERROR;
  473.     }

  474.     limit->conn = n;
  475.     limit->shm_zone = shm_zone;

  476.     return NGX_CONF_OK;
  477. }


  478. static ngx_int_t
  479. ngx_http_limit_conn_init(ngx_conf_t *cf)
  480. {
  481.     ngx_http_handler_pt        *h;
  482.     ngx_http_core_main_conf_t  *cmcf;

  483.     cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

  484.     h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
  485.     if (h == NULL) {
  486.         return NGX_ERROR;
  487.     }

  488.     *h = ngx_http_limit_conn_handler;

  489.     return NGX_OK;
  490. }