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; }
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; }
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; }