mirror of
https://github.com/nginx/nginx.git
synced 2024-12-16 22:47:47 +08:00
f8a6de481c
A failure to detect duplicate "keepalive" directive resulted in stack exhaustion.
550 lines
14 KiB
C
550 lines
14 KiB
C
|
|
/*
|
|
* Copyright (C) Maxim Dounin
|
|
* Copyright (C) Nginx, Inc.
|
|
*/
|
|
|
|
|
|
#include <ngx_config.h>
|
|
#include <ngx_core.h>
|
|
#include <ngx_http.h>
|
|
|
|
|
|
typedef struct {
|
|
ngx_uint_t max_cached;
|
|
|
|
ngx_queue_t cache;
|
|
ngx_queue_t free;
|
|
|
|
ngx_http_upstream_init_pt original_init_upstream;
|
|
ngx_http_upstream_init_peer_pt original_init_peer;
|
|
|
|
} ngx_http_upstream_keepalive_srv_conf_t;
|
|
|
|
|
|
typedef struct {
|
|
ngx_http_upstream_keepalive_srv_conf_t *conf;
|
|
|
|
ngx_http_upstream_t *upstream;
|
|
|
|
void *data;
|
|
|
|
ngx_event_get_peer_pt original_get_peer;
|
|
ngx_event_free_peer_pt original_free_peer;
|
|
|
|
#if (NGX_HTTP_SSL)
|
|
ngx_event_set_peer_session_pt original_set_session;
|
|
ngx_event_save_peer_session_pt original_save_session;
|
|
#endif
|
|
|
|
ngx_uint_t failed; /* unsigned:1 */
|
|
|
|
} ngx_http_upstream_keepalive_peer_data_t;
|
|
|
|
|
|
typedef struct {
|
|
ngx_http_upstream_keepalive_srv_conf_t *conf;
|
|
|
|
ngx_queue_t queue;
|
|
ngx_connection_t *connection;
|
|
|
|
socklen_t socklen;
|
|
u_char sockaddr[NGX_SOCKADDRLEN];
|
|
|
|
} ngx_http_upstream_keepalive_cache_t;
|
|
|
|
|
|
static ngx_int_t ngx_http_upstream_init_keepalive_peer(ngx_http_request_t *r,
|
|
ngx_http_upstream_srv_conf_t *us);
|
|
static ngx_int_t ngx_http_upstream_get_keepalive_peer(ngx_peer_connection_t *pc,
|
|
void *data);
|
|
static void ngx_http_upstream_free_keepalive_peer(ngx_peer_connection_t *pc,
|
|
void *data, ngx_uint_t state);
|
|
|
|
static void ngx_http_upstream_keepalive_dummy_handler(ngx_event_t *ev);
|
|
static void ngx_http_upstream_keepalive_close_handler(ngx_event_t *ev);
|
|
static void ngx_http_upstream_keepalive_close(ngx_connection_t *c);
|
|
|
|
|
|
#if (NGX_HTTP_SSL)
|
|
static ngx_int_t ngx_http_upstream_keepalive_set_session(
|
|
ngx_peer_connection_t *pc, void *data);
|
|
static void ngx_http_upstream_keepalive_save_session(ngx_peer_connection_t *pc,
|
|
void *data);
|
|
#endif
|
|
|
|
static void *ngx_http_upstream_keepalive_create_conf(ngx_conf_t *cf);
|
|
static char *ngx_http_upstream_keepalive(ngx_conf_t *cf, ngx_command_t *cmd,
|
|
void *conf);
|
|
|
|
|
|
static ngx_command_t ngx_http_upstream_keepalive_commands[] = {
|
|
|
|
{ ngx_string("keepalive"),
|
|
NGX_HTTP_UPS_CONF|NGX_CONF_TAKE12,
|
|
ngx_http_upstream_keepalive,
|
|
0,
|
|
0,
|
|
NULL },
|
|
|
|
ngx_null_command
|
|
};
|
|
|
|
|
|
static ngx_http_module_t ngx_http_upstream_keepalive_module_ctx = {
|
|
NULL, /* preconfiguration */
|
|
NULL, /* postconfiguration */
|
|
|
|
NULL, /* create main configuration */
|
|
NULL, /* init main configuration */
|
|
|
|
ngx_http_upstream_keepalive_create_conf, /* create server configuration */
|
|
NULL, /* merge server configuration */
|
|
|
|
NULL, /* create location configuration */
|
|
NULL /* merge location configuration */
|
|
};
|
|
|
|
|
|
ngx_module_t ngx_http_upstream_keepalive_module = {
|
|
NGX_MODULE_V1,
|
|
&ngx_http_upstream_keepalive_module_ctx, /* module context */
|
|
ngx_http_upstream_keepalive_commands, /* module directives */
|
|
NGX_HTTP_MODULE, /* module type */
|
|
NULL, /* init master */
|
|
NULL, /* init module */
|
|
NULL, /* init process */
|
|
NULL, /* init thread */
|
|
NULL, /* exit thread */
|
|
NULL, /* exit process */
|
|
NULL, /* exit master */
|
|
NGX_MODULE_V1_PADDING
|
|
};
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_http_upstream_init_keepalive(ngx_conf_t *cf,
|
|
ngx_http_upstream_srv_conf_t *us)
|
|
{
|
|
ngx_uint_t i;
|
|
ngx_http_upstream_keepalive_srv_conf_t *kcf;
|
|
ngx_http_upstream_keepalive_cache_t *cached;
|
|
|
|
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0,
|
|
"init keepalive");
|
|
|
|
kcf = ngx_http_conf_upstream_srv_conf(us,
|
|
ngx_http_upstream_keepalive_module);
|
|
|
|
if (kcf->original_init_upstream(cf, us) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
kcf->original_init_peer = us->peer.init;
|
|
|
|
us->peer.init = ngx_http_upstream_init_keepalive_peer;
|
|
|
|
/* allocate cache items and add to free queue */
|
|
|
|
cached = ngx_pcalloc(cf->pool,
|
|
sizeof(ngx_http_upstream_keepalive_cache_t) * kcf->max_cached);
|
|
if (cached == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ngx_queue_init(&kcf->cache);
|
|
ngx_queue_init(&kcf->free);
|
|
|
|
for (i = 0; i < kcf->max_cached; i++) {
|
|
ngx_queue_insert_head(&kcf->free, &cached[i].queue);
|
|
cached[i].conf = kcf;
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_http_upstream_init_keepalive_peer(ngx_http_request_t *r,
|
|
ngx_http_upstream_srv_conf_t *us)
|
|
{
|
|
ngx_http_upstream_keepalive_peer_data_t *kp;
|
|
ngx_http_upstream_keepalive_srv_conf_t *kcf;
|
|
|
|
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
|
"init keepalive peer");
|
|
|
|
kcf = ngx_http_conf_upstream_srv_conf(us,
|
|
ngx_http_upstream_keepalive_module);
|
|
|
|
kp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_keepalive_peer_data_t));
|
|
if (kp == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (kcf->original_init_peer(r, us) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
kp->conf = kcf;
|
|
kp->upstream = r->upstream;
|
|
kp->data = r->upstream->peer.data;
|
|
kp->original_get_peer = r->upstream->peer.get;
|
|
kp->original_free_peer = r->upstream->peer.free;
|
|
|
|
r->upstream->peer.data = kp;
|
|
r->upstream->peer.get = ngx_http_upstream_get_keepalive_peer;
|
|
r->upstream->peer.free = ngx_http_upstream_free_keepalive_peer;
|
|
|
|
#if (NGX_HTTP_SSL)
|
|
kp->original_set_session = r->upstream->peer.set_session;
|
|
kp->original_save_session = r->upstream->peer.save_session;
|
|
r->upstream->peer.set_session = ngx_http_upstream_keepalive_set_session;
|
|
r->upstream->peer.save_session = ngx_http_upstream_keepalive_save_session;
|
|
#endif
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_http_upstream_get_keepalive_peer(ngx_peer_connection_t *pc, void *data)
|
|
{
|
|
ngx_http_upstream_keepalive_peer_data_t *kp = data;
|
|
ngx_http_upstream_keepalive_cache_t *item;
|
|
|
|
ngx_int_t rc;
|
|
ngx_queue_t *q, *cache;
|
|
ngx_connection_t *c;
|
|
|
|
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
|
|
"get keepalive peer");
|
|
|
|
kp->failed = 0;
|
|
|
|
/* ask balancer */
|
|
|
|
rc = kp->original_get_peer(pc, kp->data);
|
|
|
|
if (rc != NGX_OK) {
|
|
return rc;
|
|
}
|
|
|
|
/* search cache for suitable connection */
|
|
|
|
cache = &kp->conf->cache;
|
|
|
|
for (q = ngx_queue_head(cache);
|
|
q != ngx_queue_sentinel(cache);
|
|
q = ngx_queue_next(q))
|
|
{
|
|
item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);
|
|
c = item->connection;
|
|
|
|
if (ngx_memn2cmp((u_char *) &item->sockaddr, (u_char *) pc->sockaddr,
|
|
item->socklen, pc->socklen)
|
|
== 0)
|
|
{
|
|
ngx_queue_remove(q);
|
|
ngx_queue_insert_head(&kp->conf->free, q);
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
|
|
"get keepalive peer: using connection %p", c);
|
|
|
|
c->idle = 0;
|
|
c->log = pc->log;
|
|
c->read->log = pc->log;
|
|
c->write->log = pc->log;
|
|
c->pool->log = pc->log;
|
|
|
|
pc->connection = c;
|
|
pc->cached = 1;
|
|
|
|
return NGX_DONE;
|
|
}
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static void
|
|
ngx_http_upstream_free_keepalive_peer(ngx_peer_connection_t *pc, void *data,
|
|
ngx_uint_t state)
|
|
{
|
|
ngx_http_upstream_keepalive_peer_data_t *kp = data;
|
|
ngx_http_upstream_keepalive_cache_t *item;
|
|
|
|
ngx_queue_t *q;
|
|
ngx_connection_t *c;
|
|
ngx_http_upstream_t *u;
|
|
|
|
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
|
|
"free keepalive peer");
|
|
|
|
/* remember failed state - peer.free() may be called more than once */
|
|
|
|
if (state & NGX_PEER_FAILED) {
|
|
kp->failed = 1;
|
|
}
|
|
|
|
/* cache valid connections */
|
|
|
|
u = kp->upstream;
|
|
c = pc->connection;
|
|
|
|
if (kp->failed
|
|
|| c == NULL
|
|
|| c->read->eof
|
|
|| c->read->error
|
|
|| c->read->timedout
|
|
|| c->write->error
|
|
|| c->write->timedout)
|
|
{
|
|
goto invalid;
|
|
}
|
|
|
|
if (!u->keepalive) {
|
|
goto invalid;
|
|
}
|
|
|
|
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
|
goto invalid;
|
|
}
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
|
|
"free keepalive peer: saving connection %p", c);
|
|
|
|
if (ngx_queue_empty(&kp->conf->free)) {
|
|
|
|
q = ngx_queue_last(&kp->conf->cache);
|
|
ngx_queue_remove(q);
|
|
|
|
item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);
|
|
|
|
ngx_http_upstream_keepalive_close(item->connection);
|
|
|
|
} else {
|
|
q = ngx_queue_head(&kp->conf->free);
|
|
ngx_queue_remove(q);
|
|
|
|
item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);
|
|
}
|
|
|
|
item->connection = c;
|
|
ngx_queue_insert_head(&kp->conf->cache, q);
|
|
|
|
pc->connection = NULL;
|
|
|
|
if (c->read->timer_set) {
|
|
ngx_del_timer(c->read);
|
|
}
|
|
if (c->write->timer_set) {
|
|
ngx_del_timer(c->write);
|
|
}
|
|
|
|
c->write->handler = ngx_http_upstream_keepalive_dummy_handler;
|
|
c->read->handler = ngx_http_upstream_keepalive_close_handler;
|
|
|
|
c->data = item;
|
|
c->idle = 1;
|
|
c->log = ngx_cycle->log;
|
|
c->read->log = ngx_cycle->log;
|
|
c->write->log = ngx_cycle->log;
|
|
c->pool->log = ngx_cycle->log;
|
|
|
|
item->socklen = pc->socklen;
|
|
ngx_memcpy(&item->sockaddr, pc->sockaddr, pc->socklen);
|
|
|
|
if (c->read->ready) {
|
|
ngx_http_upstream_keepalive_close_handler(c->read);
|
|
}
|
|
|
|
invalid:
|
|
|
|
kp->original_free_peer(pc, kp->data, state);
|
|
}
|
|
|
|
|
|
static void
|
|
ngx_http_upstream_keepalive_dummy_handler(ngx_event_t *ev)
|
|
{
|
|
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0,
|
|
"keepalive dummy handler");
|
|
}
|
|
|
|
|
|
static void
|
|
ngx_http_upstream_keepalive_close_handler(ngx_event_t *ev)
|
|
{
|
|
ngx_http_upstream_keepalive_srv_conf_t *conf;
|
|
ngx_http_upstream_keepalive_cache_t *item;
|
|
|
|
int n;
|
|
char buf[1];
|
|
ngx_connection_t *c;
|
|
|
|
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0,
|
|
"keepalive close handler");
|
|
|
|
c = ev->data;
|
|
|
|
if (c->close) {
|
|
goto close;
|
|
}
|
|
|
|
n = recv(c->fd, buf, 1, MSG_PEEK);
|
|
|
|
if (n == -1 && ngx_socket_errno == NGX_EAGAIN) {
|
|
/* stale event */
|
|
|
|
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
|
goto close;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
close:
|
|
|
|
item = c->data;
|
|
conf = item->conf;
|
|
|
|
ngx_http_upstream_keepalive_close(c);
|
|
|
|
ngx_queue_remove(&item->queue);
|
|
ngx_queue_insert_head(&conf->free, &item->queue);
|
|
}
|
|
|
|
|
|
static void
|
|
ngx_http_upstream_keepalive_close(ngx_connection_t *c)
|
|
{
|
|
|
|
#if (NGX_HTTP_SSL)
|
|
|
|
if (c->ssl) {
|
|
c->ssl->no_wait_shutdown = 1;
|
|
c->ssl->no_send_shutdown = 1;
|
|
|
|
if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
|
|
c->ssl->handler = ngx_http_upstream_keepalive_close;
|
|
return;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
ngx_destroy_pool(c->pool);
|
|
ngx_close_connection(c);
|
|
}
|
|
|
|
|
|
#if (NGX_HTTP_SSL)
|
|
|
|
static ngx_int_t
|
|
ngx_http_upstream_keepalive_set_session(ngx_peer_connection_t *pc, void *data)
|
|
{
|
|
ngx_http_upstream_keepalive_peer_data_t *kp = data;
|
|
|
|
return kp->original_set_session(pc, kp->data);
|
|
}
|
|
|
|
|
|
static void
|
|
ngx_http_upstream_keepalive_save_session(ngx_peer_connection_t *pc, void *data)
|
|
{
|
|
ngx_http_upstream_keepalive_peer_data_t *kp = data;
|
|
|
|
kp->original_save_session(pc, kp->data);
|
|
return;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
static void *
|
|
ngx_http_upstream_keepalive_create_conf(ngx_conf_t *cf)
|
|
{
|
|
ngx_http_upstream_keepalive_srv_conf_t *conf;
|
|
|
|
conf = ngx_pcalloc(cf->pool,
|
|
sizeof(ngx_http_upstream_keepalive_srv_conf_t));
|
|
if (conf == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* set by ngx_pcalloc():
|
|
*
|
|
* conf->original_init_upstream = NULL;
|
|
* conf->original_init_peer = NULL;
|
|
*/
|
|
|
|
conf->max_cached = 1;
|
|
|
|
return conf;
|
|
}
|
|
|
|
|
|
static char *
|
|
ngx_http_upstream_keepalive(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|
{
|
|
ngx_http_upstream_srv_conf_t *uscf;
|
|
ngx_http_upstream_keepalive_srv_conf_t *kcf;
|
|
|
|
ngx_int_t n;
|
|
ngx_str_t *value;
|
|
ngx_uint_t i;
|
|
|
|
uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
|
|
|
|
kcf = ngx_http_conf_upstream_srv_conf(uscf,
|
|
ngx_http_upstream_keepalive_module);
|
|
|
|
if (kcf->original_init_upstream) {
|
|
return "is duplicate";
|
|
}
|
|
|
|
kcf->original_init_upstream = uscf->peer.init_upstream
|
|
? uscf->peer.init_upstream
|
|
: ngx_http_upstream_init_round_robin;
|
|
|
|
uscf->peer.init_upstream = ngx_http_upstream_init_keepalive;
|
|
|
|
/* read options */
|
|
|
|
value = cf->args->elts;
|
|
|
|
n = ngx_atoi(value[1].data, value[1].len);
|
|
|
|
if (n == NGX_ERROR || n == 0) {
|
|
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
|
"invalid value \"%V\" in \"%V\" directive",
|
|
&value[1], &cmd->name);
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
|
|
kcf->max_cached = n;
|
|
|
|
for (i = 2; i < cf->args->nelts; i++) {
|
|
|
|
if (ngx_strcmp(value[i].data, "single") == 0) {
|
|
ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
|
|
"the \"single\" parameter is deprecated");
|
|
continue;
|
|
}
|
|
|
|
goto invalid;
|
|
}
|
|
|
|
return NGX_CONF_OK;
|
|
|
|
invalid:
|
|
|
|
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
|
"invalid parameter \"%V\"", &value[i]);
|
|
|
|
return NGX_CONF_ERROR;
|
|
}
|