blob: a2e9bd0197710ae321edc4e9a51a598601162a39 [file] [log] [blame]
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "httpd.h"
#include "http_config.h"
#include "http_log.h"
#include "util_filter.h"
#include "mod_ratelimit.h"
#define RATE_LIMIT_FILTER_NAME "RATE_LIMIT"
#define RATE_INTERVAL_MS (200)
typedef enum rl_state_e
{
RATE_ERROR,
RATE_LIMIT,
RATE_FULLSPEED
} rl_state_e;
typedef struct rl_ctx_t
{
int speed;
int chunk_size;
rl_state_e state;
apr_bucket_brigade *tmpbb;
apr_bucket_brigade *holdingbb;
} rl_ctx_t;
#if 0
static void brigade_dump(request_rec *r, apr_bucket_brigade *bb)
{
apr_bucket *e;
int i = 0;
for (e = APR_BRIGADE_FIRST(bb);
e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e), i++) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(03193)
"brigade: [%d] %s", i, e->type->name);
}
}
#endif
static apr_status_t
rate_limit_filter(ap_filter_t *f, apr_bucket_brigade *input_bb)
{
apr_status_t rv = APR_SUCCESS;
rl_ctx_t *ctx = f->ctx;
apr_bucket *fb;
int do_sleep = 0;
apr_bucket_alloc_t *ba = f->r->connection->bucket_alloc;
apr_bucket_brigade *bb = input_bb;
if (f->c->aborted) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01454) "rl: conn aborted");
apr_brigade_cleanup(bb);
return APR_ECONNABORTED;
}
if (ctx == NULL) {
const char *rl = NULL;
int ratelimit;
/* no subrequests. */
if (f->r->main != NULL) {
ap_remove_output_filter(f);
return ap_pass_brigade(f->next, bb);
}
rl = apr_table_get(f->r->subprocess_env, "rate-limit");
if (rl == NULL) {
ap_remove_output_filter(f);
return ap_pass_brigade(f->next, bb);
}
/* rl is in kilo bytes / second */
ratelimit = atoi(rl) * 1024;
if (ratelimit <= 0) {
/* remove ourselves */
ap_remove_output_filter(f);
return ap_pass_brigade(f->next, bb);
}
/* first run, init stuff */
ctx = apr_palloc(f->r->pool, sizeof(rl_ctx_t));
f->ctx = ctx;
ctx->state = RATE_LIMIT;
ctx->speed = ratelimit;
/* calculate how many bytes / interval we want to send */
/* speed is bytes / second, so, how many (speed / 1000 % interval) */
ctx->chunk_size = (ctx->speed / (1000 / RATE_INTERVAL_MS));
ctx->tmpbb = apr_brigade_create(f->r->pool, ba);
ctx->holdingbb = apr_brigade_create(f->r->pool, ba);
}
while (ctx->state != RATE_ERROR &&
(!APR_BRIGADE_EMPTY(bb) || !APR_BRIGADE_EMPTY(ctx->holdingbb))) {
apr_bucket *e;
if (!APR_BRIGADE_EMPTY(ctx->holdingbb)) {
APR_BRIGADE_CONCAT(bb, ctx->holdingbb);
}
while (ctx->state == RATE_FULLSPEED && !APR_BRIGADE_EMPTY(bb)) {
/* Find where we 'stop' going full speed. */
for (e = APR_BRIGADE_FIRST(bb);
e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) {
if (AP_RL_BUCKET_IS_END(e)) {
apr_bucket *f;
f = APR_RING_LAST(&bb->list);
APR_RING_UNSPLICE(e, f, link);
APR_RING_SPLICE_TAIL(&ctx->holdingbb->list, e, f,
apr_bucket, link);
ctx->state = RATE_LIMIT;
break;
}
}
if (f->c->aborted) {
apr_brigade_cleanup(bb);
ctx->state = RATE_ERROR;
break;
}
fb = apr_bucket_flush_create(ba);
APR_BRIGADE_INSERT_TAIL(bb, fb);
rv = ap_pass_brigade(f->next, bb);
if (rv != APR_SUCCESS) {
ctx->state = RATE_ERROR;
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, f->r, APLOGNO(01455)
"rl: full speed brigade pass failed.");
}
}
while (ctx->state == RATE_LIMIT && !APR_BRIGADE_EMPTY(bb)) {
for (e = APR_BRIGADE_FIRST(bb);
e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) {
if (AP_RL_BUCKET_IS_START(e)) {
apr_bucket *f;
f = APR_RING_LAST(&bb->list);
APR_RING_UNSPLICE(e, f, link);
APR_RING_SPLICE_TAIL(&ctx->holdingbb->list, e, f,
apr_bucket, link);
ctx->state = RATE_FULLSPEED;
break;
}
}
while (!APR_BRIGADE_EMPTY(bb)) {
apr_bucket *stop_point;
apr_off_t len = 0;
if (f->c->aborted) {
apr_brigade_cleanup(bb);
ctx->state = RATE_ERROR;
break;
}
if (do_sleep) {
apr_sleep(RATE_INTERVAL_MS * 1000);
}
else {
do_sleep = 1;
}
apr_brigade_length(bb, 1, &len);
rv = apr_brigade_partition(bb, ctx->chunk_size, &stop_point);
if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
ctx->state = RATE_ERROR;
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01456)
"rl: partition failed.");
break;
}
if (stop_point != APR_BRIGADE_SENTINEL(bb)) {
apr_bucket *f;
apr_bucket *e = APR_BUCKET_PREV(stop_point);
f = APR_RING_FIRST(&bb->list);
APR_RING_UNSPLICE(f, e, link);
APR_RING_SPLICE_HEAD(&ctx->tmpbb->list, f, e, apr_bucket,
link);
}
else {
APR_BRIGADE_CONCAT(ctx->tmpbb, bb);
}
fb = apr_bucket_flush_create(ba);
APR_BRIGADE_INSERT_TAIL(ctx->tmpbb, fb);
#if 0
brigade_dump(f->r, ctx->tmpbb);
brigade_dump(f->r, bb);
#endif
rv = ap_pass_brigade(f->next, ctx->tmpbb);
apr_brigade_cleanup(ctx->tmpbb);
if (rv != APR_SUCCESS) {
ctx->state = RATE_ERROR;
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, f->r, APLOGNO(01457)
"rl: brigade pass failed.");
break;
}
}
}
}
return rv;
}
static apr_status_t
rl_bucket_read(apr_bucket *b, const char **str,
apr_size_t *len, apr_read_type_e block)
{
*str = NULL;
*len = 0;
return APR_SUCCESS;
}
AP_RL_DECLARE(apr_bucket *)
ap_rl_end_create(apr_bucket_alloc_t *list)
{
apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
APR_BUCKET_INIT(b);
b->free = apr_bucket_free;
b->list = list;
b->length = 0;
b->start = 0;
b->data = NULL;
b->type = &ap_rl_bucket_type_end;
return b;
}
AP_RL_DECLARE(apr_bucket *)
ap_rl_start_create(apr_bucket_alloc_t *list)
{
apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
APR_BUCKET_INIT(b);
b->free = apr_bucket_free;
b->list = list;
b->length = 0;
b->start = 0;
b->data = NULL;
b->type = &ap_rl_bucket_type_start;
return b;
}
AP_RL_DECLARE_DATA const apr_bucket_type_t ap_rl_bucket_type_end = {
"RL_END", 5, APR_BUCKET_METADATA,
apr_bucket_destroy_noop,
rl_bucket_read,
apr_bucket_setaside_noop,
apr_bucket_split_notimpl,
apr_bucket_simple_copy
};
AP_RL_DECLARE_DATA const apr_bucket_type_t ap_rl_bucket_type_start = {
"RL_START", 5, APR_BUCKET_METADATA,
apr_bucket_destroy_noop,
rl_bucket_read,
apr_bucket_setaside_noop,
apr_bucket_split_notimpl,
apr_bucket_simple_copy
};
static void register_hooks(apr_pool_t *p)
{
/* run after mod_deflate etc etc, but not at connection level, ie, mod_ssl. */
ap_register_output_filter(RATE_LIMIT_FILTER_NAME, rate_limit_filter,
NULL, AP_FTYPE_PROTOCOL + 3);
}
AP_DECLARE_MODULE(ratelimit) = {
STANDARD20_MODULE_STUFF,
NULL, /* create per-directory config structure */
NULL, /* merge per-directory config structures */
NULL, /* create per-server config structure */
NULL, /* merge per-server config structures */
NULL, /* command apr_table_t */
register_hooks
};