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)
{
    size_t                      size;
    ngx_int_t                   rc, excess;
    ngx_msec_t                  now;
    ngx_msec_int_t              ms;
    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;

    node = ctx->sh->rbtree.root;
    sentinel = ctx->sh->rbtree.sentinel;

    while (node != sentinel) {

        if (hash < node->key) {
            node = node->left;
            continue;
        }

        if (hash > node->key) {
            node = node->right;
            continue;
        }

        /* hash == node->key */

        lr = (ngx_http_limit_req_node_t *) &node->color;

        rc = ngx_memn2cmp(key->data, lr->data, key->len, (size_t) lr->len);

        if (rc == 0) {
            ngx_queue_remove(&lr->queue);
            ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);

            ms = (ngx_msec_int_t) (now - lr->last);

            excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000;

            if (excess < 0) {
                excess = 0;
            }

            *ep = excess;

            if ((ngx_uint_t) excess > limit->burst) {
                return NGX_BUSY;
            }

            if (account) {
                lr->excess = excess;
                lr->last = now;
                return NGX_OK;
            }

            lr->count++;

            ctx->node = lr;

            return NGX_AGAIN;
        }

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

    *ep = 0;

    size = offsetof(ngx_rbtree_node_t, color)
           + offsetof(ngx_http_limit_req_node_t, data)
           + key->len;

    ngx_http_limit_req_expire(ctx, 1);

    node = ngx_slab_alloc_locked(ctx->shpool, size);

    if (node == NULL) {
        ngx_http_limit_req_expire(ctx, 0);

        node = ngx_slab_alloc_locked(ctx->shpool, size);
        if (node == NULL) {
            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
                          "could not allocate node%s", ctx->shpool->log_ctx);
            return NGX_ERROR;
        }
    }

    node->key = hash;

    lr = (ngx_http_limit_req_node_t *) &node->color;

    lr->len = (u_short) key->len;
    lr->excess = 0;

    ngx_memcpy(lr->data, key->data, key->len);

    ngx_rbtree_insert(&ctx->sh->rbtree, node);

    ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);

    if (account) {
        lr->last = now;
        lr->count = 0;
        return NGX_OK;
    }

    lr->last = 0;
    lr->count = 1;

    ctx->node = lr;

    return NGX_AGAIN;
}
static ngx_int_t
ngx_http_limit_req_handler(ngx_http_request_t *r)
{
    size_t                      len, n;
    uint32_t                    hash;
    ngx_int_t                   rc;
    ngx_uint_t                  excess;
    ngx_time_t                 *tp;
    ngx_rbtree_node_t          *node;
    ngx_http_variable_value_t  *vv;
    ngx_http_limit_req_ctx_t   *ctx;
    ngx_http_limit_req_node_t  *lr;
    ngx_http_limit_req_conf_t  *lrcf;

    if (r->main->limit_req_set) {
        return NGX_DECLINED;
    }

    lrcf = ngx_http_get_module_loc_conf(r, ngx_http_limit_req_module);

    if (lrcf->shm_zone == NULL) {
        return NGX_DECLINED;
    }

    ctx = lrcf->shm_zone->data;

    vv = ngx_http_get_indexed_variable(r, ctx->index);

    if (vv == NULL || vv->not_found) {
        return NGX_DECLINED;
    }

    len = vv->len;

    if (len == 0) {
        return NGX_DECLINED;
    }

    if (len > 65535) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "the value of the \"%V\" variable "
                      "is more than 65535 bytes: \"%v\"",
                      &ctx->var, vv);
        return NGX_DECLINED;
    }

    r->main->limit_req_set = 1;

    hash = ngx_crc32_short(vv->data, len);

    ngx_shmtx_lock(&ctx->shpool->mutex);

    ngx_http_limit_req_expire(ctx, 1);

    rc = ngx_http_limit_req_lookup(lrcf, hash, vv->data, len, &lr);

    if (lr) {
        ngx_queue_remove(&lr->queue);

        ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);

        excess = lr->excess;

    } else {
        excess = 0;
    }

    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                  "limit_req: %i %ui.%03ui", rc, excess / 1000, excess % 1000);

    if (rc == NGX_BUSY) {
        ngx_shmtx_unlock(&ctx->shpool->mutex);

        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "limiting requests, excess: %ui.%03ui by zone \"%V\"",
                      excess / 1000, excess % 1000, &lrcf->shm_zone->shm.name);

        return NGX_HTTP_SERVICE_UNAVAILABLE;
    }

    if (rc == NGX_AGAIN) {
        ngx_shmtx_unlock(&ctx->shpool->mutex);

        if (lrcf->nodelay) {
            return NGX_DECLINED;
        }

        ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
                      "delaying request, excess: %ui.%03ui, by zone \"%V\"",
                      excess / 1000, excess % 1000, &lrcf->shm_zone->shm.name);

        if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        r->read_event_handler = ngx_http_test_reading;
        r->write_event_handler = ngx_http_limit_req_delay;
        ngx_add_timer(r->connection->write, (ngx_msec_t) excess);

        return NGX_AGAIN;
    }

    if (rc == NGX_OK) {
        goto done;
    }

    /* rc == NGX_DECLINED */

    n = offsetof(ngx_rbtree_node_t, color)
        + offsetof(ngx_http_limit_req_node_t, data)
        + len;

    node = ngx_slab_alloc_locked(ctx->shpool, n);
    if (node == NULL) {

        ngx_http_limit_req_expire(ctx, 0);

        node = ngx_slab_alloc_locked(ctx->shpool, n);
        if (node == NULL) {
            ngx_shmtx_unlock(&ctx->shpool->mutex);
            return NGX_HTTP_SERVICE_UNAVAILABLE;
        }
    }

    lr = (ngx_http_limit_req_node_t *) &node->color;

    node->key = hash;
    lr->len = (u_char) len;

    tp = ngx_timeofday();
    lr->last = (ngx_msec_t) (tp->sec * 1000 + tp->msec);

    lr->excess = 0;
    ngx_memcpy(lr->data, vv->data, len);

    ngx_rbtree_insert(&ctx->sh->rbtree, node);

    ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);

done:

    ngx_shmtx_unlock(&ctx->shpool->mutex);

    return NGX_DECLINED;
}
Esempio n. 3
0
static ngx_int_t
ngx_http_limit_req_handler(ngx_http_request_t *r)
{
    size_t                         n, total_len;
    uint32_t                       hash;
    ngx_int_t                      rc;
    ngx_msec_t                     delay_time;
    ngx_uint_t                     excess, delay_excess, delay_postion,
                                   nodelay, i;
    ngx_time_t                    *tp;
    ngx_rbtree_node_t             *node;
    ngx_http_limit_req_t          *limit_req;
    ngx_http_limit_req_ctx_t      *ctx;
    ngx_http_limit_req_node_t     *lr;
    ngx_http_limit_req_conf_t     *lrcf;

    delay_excess = 0;
    delay_postion = 0;
    nodelay = 0;
    ctx = NULL;
    rc = 0;

    if (r->main->limit_req_set) {
        return NGX_DECLINED;
    }

    lrcf = ngx_http_get_module_loc_conf(r, ngx_http_limit_req_module);
    if (lrcf->rules == NULL) {
        return NGX_DECLINED;
    }

    if (!lrcf->enable) {
        return NGX_DECLINED;
    }

    /* filter whitelist */
    if (ngx_http_limit_req_ip_filter(r, lrcf) == NGX_OK) {
        return NGX_DECLINED;
    }

    /* to match limit_req rule*/
    limit_req = lrcf->rules->elts;
    for (i = 0; i < lrcf->rules->nelts; i++) {
        ctx = limit_req[i].shm_zone->data;

        ngx_crc32_init(hash);
        total_len = 0;

        total_len = ngx_http_limit_req_copy_variables(r, &hash, ctx, NULL);
        if (total_len == 0) {
            continue;
        }

        ngx_crc32_final(hash);

        r->main->limit_req_set = 1;

        ngx_shmtx_lock(&ctx->shpool->mutex);

        ngx_http_limit_req_expire(r, ctx, 1);
        rc = ngx_http_limit_req_lookup(r, &limit_req[i], hash, &excess);
        ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "limit_req module: %i %ui.%03ui "
                       "hash is %ui total_len is %i",
                       rc, excess / 1000, excess % 1000, hash, total_len);

        /* first limit_req */
        if (rc == NGX_DECLINED) {

            n = offsetof(ngx_rbtree_node_t, color)
                + offsetof(ngx_http_limit_req_node_t, data)
                + total_len;

            node = ngx_slab_alloc_locked(ctx->shpool, n);
            if (node == NULL) {
                ngx_http_limit_req_expire(r, ctx, 0);
                node = ngx_slab_alloc_locked(ctx->shpool, n);
                if (node == NULL) {
                    ngx_shmtx_unlock(&ctx->shpool->mutex);
                    return NGX_HTTP_SERVICE_UNAVAILABLE;
                }
            }

            lr = (ngx_http_limit_req_node_t *) &node->color;

            node->key = hash;
            lr->len = (u_char) total_len;

            tp = ngx_timeofday();
            lr->last = (ngx_msec_t) (tp->sec * 1000 + tp->msec);

            lr->excess = 0;
            ngx_http_limit_req_copy_variables(r, &hash, ctx, lr);

            ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);
            ngx_rbtree_insert(&ctx->sh->rbtree, node);

            ngx_shmtx_unlock(&ctx->shpool->mutex);

            continue;
        }

        ngx_shmtx_unlock(&ctx->shpool->mutex);

        if (rc == NGX_OK) {
            continue;
        }

        /* need limit request */
        if (rc == NGX_BUSY) {
            ngx_log_error(lrcf->limit_log_level, r->connection->log, 0,
                          "limit_req limiting requests, "
                          "excess: %ui.%03ui by zone \"%V\"",
                          excess / 1000, excess % 1000,
                         &limit_req[i].shm_zone->shm.name);

            if (limit_req[i].forbid_action.len == 0) {

                return NGX_HTTP_SERVICE_UNAVAILABLE;
            } else if (limit_req[i].forbid_action.data[0] == '@') {

                ngx_log_error(lrcf->limit_log_level, r->connection->log, 0,
                              "limiting requests, forbid_action is %V",
                              &limit_req[i].forbid_action);
                (void) ngx_http_named_location(r, &limit_req[i].forbid_action);

            } else {

                ngx_log_error(lrcf->limit_log_level, r->connection->log, 0,
                              "limiting requests, forbid_action is %V",
                              &limit_req[i].forbid_action);
                (void) ngx_http_internal_redirect(r,
                                                  &limit_req[i].forbid_action,
                                                  &r->args);
            }

            ngx_http_finalize_request(r, NGX_DONE);
            return NGX_DONE;

        }

        if (rc == NGX_AGAIN) {
            if (delay_excess < excess) {
                delay_excess = excess;
                nodelay = limit_req[i].nodelay;
                delay_postion = i;
            }
        }
    }

    if (rc == 0) {
        return NGX_DECLINED;
    }

    /* rc = NGX_AGAIN */
    if (delay_excess != 0) {

        if (nodelay) {
            return NGX_DECLINED;
        }

        delay_time = (ngx_msec_t) delay_excess * 1000 / ctx->rate;
        ngx_log_error(lrcf->delay_log_level, r->connection->log, 0,
                      "delaying request,"
                      "excess: %ui.%03ui, by zone \"%V\", delay \"%ui\" s",
                      delay_excess / 1000, delay_excess % 1000,
                      &limit_req[delay_postion].shm_zone->shm.name, delay_time);

        if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        r->read_event_handler = ngx_http_test_reading;
        r->write_event_handler = ngx_http_limit_req_delay;
        ngx_add_timer(r->connection->write, delay_time);

        return NGX_AGAIN;
    }

    /* rc == NGX_OK or rc == NGX_DECLINED */

    return NGX_DECLINED;
}
static ngx_int_t
ngx_http_limit_req_lookup(ngx_http_limit_req_limit_t *limit, ngx_uint_t hash,
    u_char *data, size_t len, ngx_uint_t *ep, ngx_uint_t account)
{
    size_t                      size;
    ngx_int_t                   rc, excess;
    ngx_time_t                 *tp;
    ngx_msec_t                  now;
    ngx_msec_int_t              ms;
    ngx_rbtree_node_t          *node, *sentinel;
    ngx_http_limit_req_ctx_t   *ctx;
    ngx_http_limit_req_node_t  *lr;

    tp = ngx_timeofday();
    now = (ngx_msec_t) (tp->sec * 1000 + tp->msec);

    ctx = limit->shm_zone->data;

    node = ctx->sh->rbtree.root;
    sentinel = ctx->sh->rbtree.sentinel;

    while (node != sentinel) {

        if (hash < node->key) {
            node = node->left;
            continue;
        }

        if (hash > node->key) {
            node = node->right;
            continue;
        }

        /* hash == node->key */

        lr = (ngx_http_limit_req_node_t *) &node->color;

        rc = ngx_memn2cmp(data, lr->data, len, (size_t) lr->len);

        if (rc == 0) {
            ngx_queue_remove(&lr->queue);
            ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);

            ms = (ngx_msec_int_t) (now - lr->last);

            excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000;

            if (excess < 0) {
                excess = 0;
            }

            *ep = excess;

            if ((ngx_uint_t) excess > limit->burst) {
                return NGX_BUSY;
            }

            if (account) {
                lr->excess = excess;
                lr->last = now;
                return NGX_OK;
            }

            lr->count++;

            ctx->node = lr;

            return NGX_AGAIN;
        }

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

    *ep = 0;

    size = offsetof(ngx_rbtree_node_t, color)
           + offsetof(ngx_http_limit_req_node_t, data)
           + len;

    ngx_http_limit_req_expire(ctx, 1);

    node = ngx_slab_alloc_locked(ctx->shpool, size);

    if (node == NULL) {
        ngx_http_limit_req_expire(ctx, 0);

        node = ngx_slab_alloc_locked(ctx->shpool, size);
        if (node == NULL) {
            return NGX_ERROR;
        }
    }

    node->key = hash;

    lr = (ngx_http_limit_req_node_t *) &node->color;

    lr->len = (u_char) len;
    lr->excess = 0;

    ngx_memcpy(lr->data, data, len);

    ngx_rbtree_insert(&ctx->sh->rbtree, node);

    ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);

    if (account) {
        lr->last = now;
        lr->count = 0;
        return NGX_OK;
    }

    lr->last = 0;
    lr->count = 1;

    ctx->node = lr;

    return NGX_AGAIN;
}
Esempio n. 5
0
static ngx_int_t
ngx_http_limit_req_lookup(ngx_http_request_t *r,
    ngx_http_limit_req_limit_t *limit, ngx_uint_t hash,
    size_t len, ngx_uint_t *ep, ngx_uint_t account)
{
    size_t                           size;
    ngx_int_t                        rc, excess;
    ngx_time_t                      *tp;
    ngx_msec_t                       now;
    ngx_msec_int_t                   ms;
    ngx_rbtree_node_t               *node, *sentinel;
    ngx_http_limit_req_ctx_t        *ctx;
    ngx_http_limit_req_node_t       *lr;
    u_char                          *lr_data, *lr_last;
    ngx_http_variable_value_t       *vv;
    ngx_http_limit_req_variable_t   *lrv;
    size_t                           lr_vv_len;
    ngx_uint_t                       i;
    ngx_http_limit_req_variable_t   *cond;
    ngx_http_variable_value_t       *cond_vv;

    tp = ngx_timeofday();
    now = (ngx_msec_t) (tp->sec * 1000 + tp->msec);

    ctx = limit->shm_zone->data;
    cond = &limit->condition;

    node = ctx->sh->rbtree.root;
    sentinel = ctx->sh->rbtree.sentinel;
    rc = -1;

    lrv = ctx->limit_vars->elts;

    while (node != sentinel) {

        if (hash < node->key) {
            node = node->left;
            continue;
        }

        if (hash > node->key) {
            node = node->right;
            continue;
        }

        /* hash == node->key */

        lr = (ngx_http_limit_req_node_t *) &node->color;

        lr_data = lr->data;
        lr_last = lr_data + lr->len;

        for (i = 0; i < ctx->limit_vars->nelts; i++) {
            vv = ngx_http_get_indexed_variable(r, lrv[i].index);

            ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "limit vv is %i %v node is %s",
                           lrv[i].index, vv, lr_data);

            lr_vv_len = ngx_min(lr_last - lr_data, vv->len);

            if ((rc = ngx_memcmp(vv->data, lr_data, lr_vv_len)) != 0) {
                break;
            }

            if (lr_vv_len != vv->len) {
                rc = 1;
                break;
            }

            /* lr_vv_len == vv->len */
            lr_data += lr_vv_len;
        }

        if (rc == 0 && lr_last > lr_data) {
            rc = -1;
        }

        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "limit lookup is : %i, size is %ui",
                       rc, ctx->limit_vars->nelts);

        if (rc == 0) {
            if (cond->index != -1) {
                /* need to check condition */
                cond_vv = ngx_http_get_indexed_variable(r, cond->index);
                if (cond_vv == NULL || cond_vv->not_found) {
                    goto out;
                }

                if (cond_vv->len == 0) {
                    goto out;
                }

                if (ngx_memcmp(cond_vv->data, "1", 1) != 0) {
                    goto out;
                }
            }

            ngx_queue_remove(&lr->queue);
            ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);

            ms = (ngx_msec_int_t) (now - lr->last);

            excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000;

            if (excess < 0) {
                excess = 0;
            }

            *ep = excess;

            if ((ngx_uint_t) excess > limit->burst) {
                return NGX_BUSY;
            }

            if (account) {
                lr->excess = excess;
                lr->last = now;
                return NGX_OK;
            }

            lr->count++;

            ctx->node = lr;

out:
            return NGX_AGAIN;
        }

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

    *ep = 0;

    size = offsetof(ngx_rbtree_node_t, color)
           + offsetof(ngx_http_limit_req_node_t, data)
           + len;

    ngx_http_limit_req_expire(ctx, 1);

    node = ngx_slab_alloc_locked(ctx->shpool, size);

    if (node == NULL) {
        ngx_http_limit_req_expire(ctx, 0);

        node = ngx_slab_alloc_locked(ctx->shpool, size);
        if (node == NULL) {
            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
                          "could not allocate node%s", ctx->shpool->log_ctx);
            return NGX_ERROR;
        }
    }

    node->key = hash;

    lr = (ngx_http_limit_req_node_t *) &node->color;

    lr->len = (u_char) len;
    lr->excess = 0;

    ngx_http_limit_req_copy_variables(r, (uint32_t *)&hash, ctx, lr);

    ngx_rbtree_insert(&ctx->sh->rbtree, node);

    ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);

    if (account) {
        lr->last = now;
        lr->count = 0;
        return NGX_OK;
    }

    lr->last = 0;
    lr->count = 1;

    ctx->node = lr;

    return NGX_AGAIN;
}