mod_reqtimeout.c revision 3b5fbf686c2c0e2e69716a073173a5c50539410c
342N/A/* Licensed to the Apache Software Foundation (ASF) under one or more
3201N/A * contributor license agreements. See the NOTICE file distributed with
342N/A * this work for additional information regarding copyright ownership.
342N/A * The ASF licenses this file to You under the Apache License, Version 2.0
342N/A * (the "License"); you may not use this file except in compliance with
342N/A * the License. You may obtain a copy of the License at
342N/A *
342N/A * http://www.apache.org/licenses/LICENSE-2.0
342N/A *
342N/A * Unless required by applicable law or agreed to in writing, software
342N/A * distributed under the License is distributed on an "AS IS" BASIS,
342N/A * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
342N/A * See the License for the specific language governing permissions and
342N/A * limitations under the License.
342N/A */
342N/A
342N/A#include "httpd.h"
342N/A#include "http_config.h"
1472N/A#include "http_request.h"
1472N/A#include "http_connection.h"
1472N/A#include "http_protocol.h"
342N/A#include "http_log.h"
342N/A#include "http_core.h"
342N/A#include "util_filter.h"
1879N/A#define APR_WANT_STRFUNC
1879N/A#include "apr_strings.h"
1879N/A#include "apr_support.h"
1879N/A
342N/Amodule AP_MODULE_DECLARE_DATA reqtimeout_module;
342N/A
342N/Atypedef struct
342N/A{
350N/A int header_timeout; /* timeout for reading the req hdrs in secs */
342N/A int header_max_timeout; /* max timeout for req hdrs in secs */
751N/A int header_min_rate; /* min rate for reading req hdrs in bytes/s */
342N/A apr_time_t header_rate_factor;
342N/A int body_timeout; /* timeout for reading the req body in secs */
884N/A int body_max_timeout; /* max timeout for req body in secs */
342N/A int body_min_rate; /* timeout for reading the req body in secs */
342N/A apr_time_t body_rate_factor;
342N/A} reqtimeout_srv_cfg;
342N/A
342N/A/* this struct is used both as conn_config and as filter context */
1044N/Atypedef struct
1044N/A{
1044N/A apr_time_t timeout_at;
1044N/A apr_time_t max_timeout_at;
1044N/A int min_rate;
1044N/A int new_timeout;
1044N/A int new_max_timeout;
1044N/A int in_keep_alive;
342N/A char *type;
342N/A apr_socket_t *socket;
342N/A apr_time_t rate_factor;
342N/A apr_bucket_brigade *tmpbb;
1282N/A} reqtimeout_con_cfg;
342N/A
342N/Astatic const char *const reqtimeout_filter_name = "reqtimeout";
751N/A
342N/Astatic void extend_timeout(reqtimeout_con_cfg *ccfg, apr_bucket_brigade *bb)
342N/A{
884N/A apr_off_t len;
884N/A apr_time_t new_timeout_at;
884N/A
884N/A if (apr_brigade_length(bb, 0, &len) != APR_SUCCESS || len <= 0)
884N/A return;
884N/A
1282N/A new_timeout_at = ccfg->timeout_at + len * ccfg->rate_factor;
342N/A if (ccfg->max_timeout_at > 0 && new_timeout_at > ccfg->max_timeout_at) {
342N/A ccfg->timeout_at = ccfg->max_timeout_at;
2059N/A }
2059N/A else {
2059N/A ccfg->timeout_at = new_timeout_at;
2059N/A }
2059N/A}
2059N/A
2059N/Astatic apr_status_t check_time_left(reqtimeout_con_cfg *ccfg,
2059N/A apr_time_t *time_left_p)
2346N/A{
2283N/A *time_left_p = ccfg->timeout_at - apr_time_now();
2346N/A if (*time_left_p <= 0)
342N/A return APR_TIMEUP;
1282N/A
342N/A if (*time_left_p < apr_time_from_sec(1)) {
342N/A *time_left_p = apr_time_from_sec(1);
1282N/A }
342N/A return APR_SUCCESS;
342N/A}
2034N/A
2034N/Astatic apr_status_t have_lf_or_eos(apr_bucket_brigade *bb)
2034N/A{
2034N/A apr_bucket *b = APR_BRIGADE_LAST(bb);
2034N/A
2034N/A for ( ; b != APR_BRIGADE_SENTINEL(bb) ; b = APR_BUCKET_PREV(b) ) {
2034N/A const char *str;
342N/A apr_size_t len;
342N/A apr_status_t rv;
342N/A
342N/A if (APR_BUCKET_IS_EOS(b))
342N/A return APR_SUCCESS;
342N/A
342N/A if (APR_BUCKET_IS_METADATA(b))
342N/A continue;
342N/A
342N/A rv = apr_bucket_read(b, &str, &len, APR_BLOCK_READ);
342N/A if (rv != APR_SUCCESS)
1282N/A return rv;
342N/A
342N/A if (len == 0)
342N/A continue;
342N/A
342N/A if (str[len-1] == APR_ASCII_LF)
342N/A return APR_SUCCESS;
342N/A }
342N/A return APR_INCOMPLETE;
616N/A}
616N/A
616N/A
342N/A#define MIN(x,y) ((x) < (y) ? (x) : (y))
342N/Astatic apr_status_t reqtimeout_filter(ap_filter_t *f,
342N/A apr_bucket_brigade *bb,
342N/A ap_input_mode_t mode,
342N/A apr_read_type_e block,
342N/A apr_off_t readbytes)
342N/A{
2755N/A apr_time_t time_left;
2755N/A apr_time_t now;
2755N/A apr_status_t rv;
342N/A apr_interval_time_t saved_sock_timeout = -1;
342N/A reqtimeout_con_cfg *ccfg = f->ctx;
342N/A
342N/A if (ccfg->in_keep_alive) {
883N/A /* For this read, the normal keep-alive timeout must be used */
883N/A ccfg->in_keep_alive = 0;
883N/A return ap_get_brigade(f->next, bb, mode, block, readbytes);
1282N/A }
883N/A
1111N/A now = apr_time_now();
1111N/A if (ccfg->new_timeout > 0) {
1111N/A /* set new timeout */
1282N/A ccfg->timeout_at = now + apr_time_from_sec(ccfg->new_timeout);
1111N/A ccfg->new_timeout = 0;
1111N/A if (ccfg->new_max_timeout > 0) {
1111N/A ccfg->max_timeout_at = now + apr_time_from_sec(ccfg->new_max_timeout);
1111N/A ccfg->new_max_timeout = 0;
1111N/A }
1282N/A }
1111N/A else if (ccfg->timeout_at == 0) {
1111N/A /* no timeout set */
1111N/A return ap_get_brigade(f->next, bb, mode, block, readbytes);
883N/A }
1282N/A
1111N/A if (!ccfg->socket) {
1111N/A core_net_rec *net_rec;
1111N/A ap_filter_t *core_in = f->next;
1282N/A
1111N/A while (core_in && core_in->frec != ap_core_input_filter_handle)
1111N/A core_in = core_in->next;
1111N/A
1111N/A if (!core_in) {
1282N/A ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, f->c,
1111N/A "mod_reqtimeout: Can't get socket "
1111N/A "handle from core_input_filter");
1111N/A ap_remove_input_filter(f);
1282N/A return ap_get_brigade(f->next, bb, mode, block, readbytes);
1111N/A }
1111N/A net_rec = core_in->ctx;
342N/A ccfg->socket = net_rec->client_socket;
342N/A }
342N/A
342N/A rv = check_time_left(ccfg, &time_left);
751N/A if (rv != APR_SUCCESS)
342N/A goto out;
342N/A
2278N/A if (block == APR_NONBLOCK_READ || mode == AP_MODE_INIT
2278N/A || mode == AP_MODE_EATCRLF) {
2278N/A rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
2278N/A if (ccfg->min_rate > 0 && rv == APR_SUCCESS) {
342N/A extend_timeout(ccfg, bb);
342N/A }
342N/A return rv;
1261N/A }
1261N/A
1261N/A rv = apr_socket_timeout_get(ccfg->socket, &saved_sock_timeout);
1261N/A AP_DEBUG_ASSERT(rv == APR_SUCCESS);
1261N/A
1261N/A rv = apr_socket_timeout_set(ccfg->socket, MIN(time_left, saved_sock_timeout));
1261N/A AP_DEBUG_ASSERT(rv == APR_SUCCESS);
1261N/A
1261N/A if (mode == AP_MODE_GETLINE) {
1261N/A /*
1261N/A * For a blocking AP_MODE_GETLINE read, apr_brigade_split_line()
1261N/A * would loop until a whole line has been read. As this would make it
1261N/A * impossible to enforce a total timeout, we only do non-blocking
1261N/A * reads.
342N/A */
342N/A apr_size_t remaining = HUGE_STRING_LEN;
342N/A do {
342N/A apr_off_t bblen;
342N/A
342N/A rv = ap_get_brigade(f->next, bb, AP_MODE_GETLINE, APR_NONBLOCK_READ, remaining);
342N/A if (APR_STATUS_IS_EAGAIN(rv)) {
342N/A rv = APR_SUCCESS;
342N/A }
342N/A else if (rv != APR_SUCCESS) {
342N/A break;
342N/A }
342N/A
342N/A if (!APR_BRIGADE_EMPTY(bb)) {
342N/A if (ccfg->min_rate > 0) {
342N/A extend_timeout(ccfg, bb);
342N/A }
342N/A
342N/A rv = have_lf_or_eos(bb);
342N/A if (rv != APR_INCOMPLETE) {
342N/A break;
342N/A }
342N/A
342N/A rv = apr_brigade_length(bb, 1, &bblen);
2753N/A if (rv != APR_SUCCESS) {
342N/A break;
342N/A }
342N/A remaining -= bblen;
1282N/A if (remaining <= 0) {
342N/A break;
342N/A }
342N/A
342N/A /* Haven't got a whole line yet, save what we have ... */
342N/A if (!ccfg->tmpbb) {
342N/A ccfg->tmpbb = apr_brigade_create(f->c->pool, f->c->bucket_alloc);
342N/A }
342N/A APR_BRIGADE_CONCAT(ccfg->tmpbb, bb);
342N/A }
1087N/A
1087N/A /* ... and wait for more */
1087N/A rv = apr_wait_for_io_or_timeout(NULL, ccfg->socket, 1);
1087N/A if (rv != APR_SUCCESS)
942N/A break;
942N/A
942N/A rv = check_time_left(ccfg, &time_left);
1357N/A if (rv != APR_SUCCESS)
638N/A break;
638N/A
638N/A rv = apr_socket_timeout_set(ccfg->socket,
1357N/A MIN(time_left, saved_sock_timeout));
638N/A AP_DEBUG_ASSERT(rv == APR_SUCCESS);
794N/A
794N/A } while (1);
1282N/A
794N/A if (ccfg->tmpbb)
890N/A APR_BRIGADE_PREPEND(bb, ccfg->tmpbb);
890N/A
890N/A }
890N/A else {
940N/A /* mode != AP_MODE_GETLINE */
940N/A rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
940N/A if (ccfg->min_rate > 0 && rv == APR_SUCCESS) {
1194N/A extend_timeout(ccfg, bb);
1194N/A }
1261N/A }
1261N/A
1261N/A apr_socket_timeout_set(ccfg->socket, saved_sock_timeout);
1261N/A
2037N/Aout:
2037N/A if (rv == APR_TIMEUP) {
2037N/A ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, f->c,
2037N/A "Request %s read timeout", ccfg->type);
2037N/A /*
2037N/A * If we allow lingering close, the client may keep this
2037N/A * process/thread busy for another 30s (MAX_SECS_TO_LINGER).
2037N/A * Therefore we have to abort the connection. The downside is
2037N/A * that the client will most likely not receive the error
2037N/A * message.
2037N/A */
2037N/A f->c->aborted = 1;
2037N/A }
2382N/A return rv;
2382N/A}
2382N/A
2382N/Astatic int reqtimeout_init(conn_rec *c)
2382N/A{
2069N/A reqtimeout_con_cfg *ccfg;
2069N/A reqtimeout_srv_cfg *cfg;
2413N/A
2413N/A cfg = ap_get_module_config(c->base_server->module_config,
2413N/A &reqtimeout_module);
2413N/A AP_DEBUG_ASSERT(cfg != NULL);
3009N/A if (cfg->header_timeout <= 0 && cfg->body_timeout <= 0) {
3009N/A /* not configured for this vhost */
3009N/A return DECLINED;
3009N/A }
3009N/A
3009N/A ccfg = apr_pcalloc(c->pool, sizeof(reqtimeout_con_cfg));
3066N/A ccfg->new_timeout = cfg->header_timeout;
3009N/A ccfg->new_max_timeout = cfg->header_max_timeout;
3201N/A ccfg->type = "header";
3201N/A ccfg->min_rate = cfg->header_min_rate;
3635N/A ccfg->rate_factor = cfg->header_rate_factor;
3201N/A ap_set_module_config(c->conn_config, &reqtimeout_module, ccfg);
3201N/A
3201N/A ap_add_input_filter("reqtimeout", ccfg, NULL, c);
3635N/A /* we are not handling the connection, we just do initialization */
3635N/A return DECLINED;
3635N/A}
3201N/A
3635N/Astatic int reqtimeout_after_headers(request_rec *r)
3635N/A{
3201N/A reqtimeout_srv_cfg *cfg;
3201N/A reqtimeout_con_cfg *ccfg =
3201N/A ap_get_module_config(r->connection->conn_config, &reqtimeout_module);
3678N/A
3678N/A if (ccfg == NULL) {
3678N/A /* not configured for this connection */
3678N/A return OK;
342N/A }
350N/A
1879N/A cfg = ap_get_module_config(r->connection->base_server->module_config,
1879N/A &reqtimeout_module);
AP_DEBUG_ASSERT(cfg != NULL);
ccfg->timeout_at = 0;
ccfg->max_timeout_at = 0;
ccfg->new_timeout = cfg->body_timeout;
ccfg->new_max_timeout = cfg->body_max_timeout;
ccfg->min_rate = cfg->body_min_rate;
ccfg->rate_factor = cfg->body_rate_factor;
ccfg->type = "body";
return OK;
}
static int reqtimeout_after_body(request_rec *r)
{
reqtimeout_srv_cfg *cfg;
reqtimeout_con_cfg *ccfg =
ap_get_module_config(r->connection->conn_config, &reqtimeout_module);
if (ccfg == NULL) {
/* not configured for this connection */
return OK;
}
cfg = ap_get_module_config(r->connection->base_server->module_config,
&reqtimeout_module);
AP_DEBUG_ASSERT(cfg != NULL);
ccfg->timeout_at = 0;
ccfg->max_timeout_at = 0;
ccfg->in_keep_alive = 1;
ccfg->new_timeout = cfg->header_timeout;
ccfg->new_max_timeout = cfg->header_max_timeout;
ccfg->min_rate = cfg->header_min_rate;
ccfg->rate_factor = cfg->header_rate_factor;
ccfg->type = "header";
return OK;
}
static void *reqtimeout_create_srv_config(apr_pool_t *p, server_rec *s)
{
reqtimeout_srv_cfg *cfg = apr_pcalloc(p, sizeof(reqtimeout_srv_cfg));
cfg->header_timeout = -1;
cfg->header_max_timeout = -1;
cfg->header_min_rate = -1;
cfg->body_timeout = -1;
cfg->body_max_timeout = -1;
cfg->body_min_rate = -1;
return cfg;
}
#define MERGE_INT(cfg, b, a, val) cfg->val = (a->val == -1) ? b->val : a->val;
static void *reqtimeout_merge_srv_config(apr_pool_t *p, void *base_, void *add_)
{
reqtimeout_srv_cfg *base = base_;
reqtimeout_srv_cfg *add = add_;
reqtimeout_srv_cfg *cfg = apr_pcalloc(p, sizeof(reqtimeout_srv_cfg));
MERGE_INT(cfg, base, add, header_timeout);
MERGE_INT(cfg, base, add, header_max_timeout);
MERGE_INT(cfg, base, add, header_min_rate);
MERGE_INT(cfg, base, add, body_timeout);
MERGE_INT(cfg, base, add, body_max_timeout);
MERGE_INT(cfg, base, add, body_min_rate);
cfg->header_rate_factor = (cfg->header_min_rate == -1) ? base->header_rate_factor :
add->header_rate_factor;
cfg->body_rate_factor = (cfg->body_min_rate == -1) ? base->body_rate_factor :
add->body_rate_factor;
return cfg;
}
static const char *parse_int(apr_pool_t *p, const char *arg, int *val) {
char *endptr;
*val = strtol(arg, &endptr, 10);
if (arg == endptr) {
return apr_psprintf(p, "Value '%s' not numerical", endptr);
}
if (*endptr != '\0') {
return apr_psprintf(p, "Cannot parse '%s'", endptr);
}
if (*val < 0) {
return "Value must be non-negative";
}
return NULL;
}
static const char *set_reqtimeout_param(reqtimeout_srv_cfg *conf,
apr_pool_t *p,
const char *key,
const char *val)
{
const char *ret = NULL;
char *rate_str = NULL, *initial_str, *max_str = NULL;
int rate = 0, initial = 0, max = 0;
enum { PARAM_HEADER, PARAM_BODY } type;
if (!strcasecmp(key, "header")) {
type = PARAM_HEADER;
}
else if (!strcasecmp(key, "body")) {
type = PARAM_BODY;
}
else {
return "Unknown RequestReadTimeout parameter";
}
if ((rate_str = ap_strcasestr(val, ",minrate="))) {
initial_str = apr_pstrndup(p, val, rate_str - val);
rate_str += strlen(",minrate=");
ret = parse_int(p, rate_str, &rate);
if (ret)
return ret;
if (rate == 0)
return "Minimum data rate must be larger than 0";
if ((max_str = strchr(initial_str, '-'))) {
*max_str++ = '\0';
ret = parse_int(p, max_str, &max);
if (ret)
return ret;
}
ret = parse_int(p, initial_str, &initial);
}
else {
if (ap_strchr_c(val, '-'))
return "Must set MinRate option if using timeout range";
ret = parse_int(p, val, &initial);
}
if (ret)
return ret;
if (max && initial >= max) {
return "Maximum timeout must be larger than initial timeout";
}
if (type == PARAM_HEADER) {
conf->header_timeout = initial;
conf->header_max_timeout = max;
conf->header_min_rate = rate;
if (rate)
conf->header_rate_factor = apr_time_from_sec(1) / rate;
}
else {
conf->body_timeout = initial;
conf->body_max_timeout = max;
conf->body_min_rate = rate;
if (rate)
conf->body_rate_factor = apr_time_from_sec(1) / rate;
}
return ret;
}
static const char *set_reqtimeouts(cmd_parms *cmd, void *mconfig,
const char *arg)
{
reqtimeout_srv_cfg *conf =
ap_get_module_config(cmd->server->module_config,
&reqtimeout_module);
while (*arg) {
char *word, *val;
const char *err;
word = ap_getword_conf(cmd->pool, &arg);
val = strchr(word, '=');
if (!val) {
return "Invalid RequestReadTimeout parameter. Parameter must be "
"in the form 'key=value'";
}
else
*val++ = '\0';
err = set_reqtimeout_param(conf, cmd->pool, word, val);
if (err)
return apr_psprintf(cmd->temp_pool, "RequestReadTimeout: %s=%s: %s",
word, val, err);
}
return NULL;
}
static void reqtimeout_hooks(apr_pool_t *pool)
{
/*
* mod_ssl is AP_FTYPE_CONNECTION + 5 and mod_reqtimeout needs to
* be called before mod_ssl. Otherwise repeated reads during the ssl
* handshake can prevent the timeout from triggering.
*/
ap_register_input_filter(reqtimeout_filter_name, reqtimeout_filter, NULL,
AP_FTYPE_CONNECTION + 8);
/*
* mod_reqtimeout needs to be called before ap_process_http_request (which
* is run at APR_HOOK_REALLY_LAST) but after all other protocol modules.
* This ensures that it only influences normal http connections and not
* e.g. mod_ftp. Also, if mod_reqtimeout used the pre_connection hook, it
* would be inserted on mod_proxy's backend connections.
*/
ap_hook_process_connection(reqtimeout_init, NULL, NULL, APR_HOOK_LAST);
ap_hook_post_read_request(reqtimeout_after_headers, NULL, NULL,
APR_HOOK_MIDDLE);
ap_hook_log_transaction(reqtimeout_after_body, NULL, NULL,
APR_HOOK_MIDDLE);
}
static const command_rec reqtimeout_cmds[] = {
AP_INIT_RAW_ARGS("RequestReadTimeout", set_reqtimeouts, NULL, RSRC_CONF,
"Set various timeout parameters for reading request "
"headers and body"),
{NULL}
};
module AP_MODULE_DECLARE_DATA reqtimeout_module = {
STANDARD20_MODULE_STUFF,
NULL, /* create per-dir config structures */
NULL, /* merge per-dir config structures */
reqtimeout_create_srv_config, /* create per-server config structures */
reqtimeout_merge_srv_config, /* merge per-server config structures */
reqtimeout_cmds, /* table of config file commands */
reqtimeout_hooks
};