mirror of
https://github.com/nginx/nginx.git
synced 2024-12-17 15:17:48 +08:00
f7f1607bf2
When configured, an individual listen socket on a given address is created for each worker process. This allows to reduce in-kernel lock contention on configurations with high accept rates, resulting in better performance. As of now it works on Linux and DragonFly BSD. Note that on Linux incoming connection requests are currently tied up to a specific listen socket, and if some sockets are closed, connection requests will be reset, see https://lwn.net/Articles/542629/. With nginx, this may happen if the number of worker processes is reduced. There is no such problem on DragonFly BSD. Based on previous work by Sepherosa Ziehau and Yingqi Lu.
556 lines
14 KiB
C
556 lines
14 KiB
C
|
|
/*
|
|
* Copyright (C) Roman Arutyunyan
|
|
* Copyright (C) Nginx, Inc.
|
|
*/
|
|
|
|
|
|
#include <ngx_config.h>
|
|
#include <ngx_core.h>
|
|
#include <ngx_event.h>
|
|
#include <ngx_stream.h>
|
|
|
|
|
|
static char *ngx_stream_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
|
|
static ngx_int_t ngx_stream_add_ports(ngx_conf_t *cf, ngx_array_t *ports,
|
|
ngx_stream_listen_t *listen);
|
|
static char *ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports);
|
|
static ngx_int_t ngx_stream_add_addrs(ngx_conf_t *cf, ngx_stream_port_t *stport,
|
|
ngx_stream_conf_addr_t *addr);
|
|
#if (NGX_HAVE_INET6)
|
|
static ngx_int_t ngx_stream_add_addrs6(ngx_conf_t *cf,
|
|
ngx_stream_port_t *stport, ngx_stream_conf_addr_t *addr);
|
|
#endif
|
|
static ngx_int_t ngx_stream_cmp_conf_addrs(const void *one, const void *two);
|
|
|
|
|
|
ngx_uint_t ngx_stream_max_module;
|
|
|
|
|
|
static ngx_command_t ngx_stream_commands[] = {
|
|
|
|
{ ngx_string("stream"),
|
|
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
|
|
ngx_stream_block,
|
|
0,
|
|
0,
|
|
NULL },
|
|
|
|
ngx_null_command
|
|
};
|
|
|
|
|
|
static ngx_core_module_t ngx_stream_module_ctx = {
|
|
ngx_string("stream"),
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
|
|
ngx_module_t ngx_stream_module = {
|
|
NGX_MODULE_V1,
|
|
&ngx_stream_module_ctx, /* module context */
|
|
ngx_stream_commands, /* module directives */
|
|
NGX_CORE_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 char *
|
|
ngx_stream_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|
{
|
|
char *rv;
|
|
ngx_uint_t i, m, mi, s;
|
|
ngx_conf_t pcf;
|
|
ngx_array_t ports;
|
|
ngx_stream_listen_t *listen;
|
|
ngx_stream_module_t *module;
|
|
ngx_stream_conf_ctx_t *ctx;
|
|
ngx_stream_core_srv_conf_t **cscfp;
|
|
ngx_stream_core_main_conf_t *cmcf;
|
|
|
|
/* the main stream context */
|
|
|
|
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_stream_conf_ctx_t));
|
|
if (ctx == NULL) {
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
|
|
*(ngx_stream_conf_ctx_t **) conf = ctx;
|
|
|
|
/* count the number of the stream modules and set up their indices */
|
|
|
|
ngx_stream_max_module = 0;
|
|
for (m = 0; ngx_modules[m]; m++) {
|
|
if (ngx_modules[m]->type != NGX_STREAM_MODULE) {
|
|
continue;
|
|
}
|
|
|
|
ngx_modules[m]->ctx_index = ngx_stream_max_module++;
|
|
}
|
|
|
|
|
|
/* the stream main_conf context, it's the same in the all stream contexts */
|
|
|
|
ctx->main_conf = ngx_pcalloc(cf->pool,
|
|
sizeof(void *) * ngx_stream_max_module);
|
|
if (ctx->main_conf == NULL) {
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
|
|
|
|
/*
|
|
* the stream null srv_conf context, it is used to merge
|
|
* the server{}s' srv_conf's
|
|
*/
|
|
|
|
ctx->srv_conf = ngx_pcalloc(cf->pool,
|
|
sizeof(void *) * ngx_stream_max_module);
|
|
if (ctx->srv_conf == NULL) {
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
|
|
|
|
/*
|
|
* create the main_conf's and the null srv_conf's of the all stream modules
|
|
*/
|
|
|
|
for (m = 0; ngx_modules[m]; m++) {
|
|
if (ngx_modules[m]->type != NGX_STREAM_MODULE) {
|
|
continue;
|
|
}
|
|
|
|
module = ngx_modules[m]->ctx;
|
|
mi = ngx_modules[m]->ctx_index;
|
|
|
|
if (module->create_main_conf) {
|
|
ctx->main_conf[mi] = module->create_main_conf(cf);
|
|
if (ctx->main_conf[mi] == NULL) {
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
}
|
|
|
|
if (module->create_srv_conf) {
|
|
ctx->srv_conf[mi] = module->create_srv_conf(cf);
|
|
if (ctx->srv_conf[mi] == NULL) {
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* parse inside the stream{} block */
|
|
|
|
pcf = *cf;
|
|
cf->ctx = ctx;
|
|
|
|
cf->module_type = NGX_STREAM_MODULE;
|
|
cf->cmd_type = NGX_STREAM_MAIN_CONF;
|
|
rv = ngx_conf_parse(cf, NULL);
|
|
|
|
if (rv != NGX_CONF_OK) {
|
|
*cf = pcf;
|
|
return rv;
|
|
}
|
|
|
|
|
|
/* init stream{} main_conf's, merge the server{}s' srv_conf's */
|
|
|
|
cmcf = ctx->main_conf[ngx_stream_core_module.ctx_index];
|
|
cscfp = cmcf->servers.elts;
|
|
|
|
for (m = 0; ngx_modules[m]; m++) {
|
|
if (ngx_modules[m]->type != NGX_STREAM_MODULE) {
|
|
continue;
|
|
}
|
|
|
|
module = ngx_modules[m]->ctx;
|
|
mi = ngx_modules[m]->ctx_index;
|
|
|
|
/* init stream{} main_conf's */
|
|
|
|
cf->ctx = ctx;
|
|
|
|
if (module->init_main_conf) {
|
|
rv = module->init_main_conf(cf, ctx->main_conf[mi]);
|
|
if (rv != NGX_CONF_OK) {
|
|
*cf = pcf;
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
for (s = 0; s < cmcf->servers.nelts; s++) {
|
|
|
|
/* merge the server{}s' srv_conf's */
|
|
|
|
cf->ctx = cscfp[s]->ctx;
|
|
|
|
if (module->merge_srv_conf) {
|
|
rv = module->merge_srv_conf(cf,
|
|
ctx->srv_conf[mi],
|
|
cscfp[s]->ctx->srv_conf[mi]);
|
|
if (rv != NGX_CONF_OK) {
|
|
*cf = pcf;
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
*cf = pcf;
|
|
|
|
|
|
if (ngx_array_init(&ports, cf->temp_pool, 4, sizeof(ngx_stream_conf_port_t))
|
|
!= NGX_OK)
|
|
{
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
|
|
listen = cmcf->listen.elts;
|
|
|
|
for (i = 0; i < cmcf->listen.nelts; i++) {
|
|
if (ngx_stream_add_ports(cf, &ports, &listen[i]) != NGX_OK) {
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
}
|
|
|
|
return ngx_stream_optimize_servers(cf, &ports);
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_stream_add_ports(ngx_conf_t *cf, ngx_array_t *ports,
|
|
ngx_stream_listen_t *listen)
|
|
{
|
|
in_port_t p;
|
|
ngx_uint_t i;
|
|
struct sockaddr *sa;
|
|
struct sockaddr_in *sin;
|
|
ngx_stream_conf_port_t *port;
|
|
ngx_stream_conf_addr_t *addr;
|
|
#if (NGX_HAVE_INET6)
|
|
struct sockaddr_in6 *sin6;
|
|
#endif
|
|
|
|
sa = (struct sockaddr *) &listen->sockaddr;
|
|
|
|
switch (sa->sa_family) {
|
|
|
|
#if (NGX_HAVE_INET6)
|
|
case AF_INET6:
|
|
sin6 = (struct sockaddr_in6 *) sa;
|
|
p = sin6->sin6_port;
|
|
break;
|
|
#endif
|
|
|
|
#if (NGX_HAVE_UNIX_DOMAIN)
|
|
case AF_UNIX:
|
|
p = 0;
|
|
break;
|
|
#endif
|
|
|
|
default: /* AF_INET */
|
|
sin = (struct sockaddr_in *) sa;
|
|
p = sin->sin_port;
|
|
break;
|
|
}
|
|
|
|
port = ports->elts;
|
|
for (i = 0; i < ports->nelts; i++) {
|
|
if (p == port[i].port && sa->sa_family == port[i].family) {
|
|
|
|
/* a port is already in the port list */
|
|
|
|
port = &port[i];
|
|
goto found;
|
|
}
|
|
}
|
|
|
|
/* add a port to the port list */
|
|
|
|
port = ngx_array_push(ports);
|
|
if (port == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
port->family = sa->sa_family;
|
|
port->port = p;
|
|
|
|
if (ngx_array_init(&port->addrs, cf->temp_pool, 2,
|
|
sizeof(ngx_stream_conf_addr_t))
|
|
!= NGX_OK)
|
|
{
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
found:
|
|
|
|
addr = ngx_array_push(&port->addrs);
|
|
if (addr == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
addr->sockaddr = (struct sockaddr *) &listen->sockaddr;
|
|
addr->socklen = listen->socklen;
|
|
addr->ctx = listen->ctx;
|
|
addr->bind = listen->bind;
|
|
addr->wildcard = listen->wildcard;
|
|
addr->so_keepalive = listen->so_keepalive;
|
|
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
|
|
addr->tcp_keepidle = listen->tcp_keepidle;
|
|
addr->tcp_keepintvl = listen->tcp_keepintvl;
|
|
addr->tcp_keepcnt = listen->tcp_keepcnt;
|
|
#endif
|
|
#if (NGX_STREAM_SSL)
|
|
addr->ssl = listen->ssl;
|
|
#endif
|
|
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
|
|
addr->ipv6only = listen->ipv6only;
|
|
#endif
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static char *
|
|
ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports)
|
|
{
|
|
ngx_uint_t i, p, last, bind_wildcard;
|
|
ngx_listening_t *ls;
|
|
ngx_stream_port_t *stport;
|
|
ngx_stream_conf_port_t *port;
|
|
ngx_stream_conf_addr_t *addr;
|
|
ngx_stream_core_srv_conf_t *cscf;
|
|
|
|
port = ports->elts;
|
|
for (p = 0; p < ports->nelts; p++) {
|
|
|
|
ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
|
|
sizeof(ngx_stream_conf_addr_t), ngx_stream_cmp_conf_addrs);
|
|
|
|
addr = port[p].addrs.elts;
|
|
last = port[p].addrs.nelts;
|
|
|
|
/*
|
|
* if there is the binding to the "*:port" then we need to bind()
|
|
* to the "*:port" only and ignore the other bindings
|
|
*/
|
|
|
|
if (addr[last - 1].wildcard) {
|
|
addr[last - 1].bind = 1;
|
|
bind_wildcard = 1;
|
|
|
|
} else {
|
|
bind_wildcard = 0;
|
|
}
|
|
|
|
i = 0;
|
|
|
|
while (i < last) {
|
|
|
|
if (bind_wildcard && !addr[i].bind) {
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
ls = ngx_create_listening(cf, addr[i].sockaddr, addr[i].socklen);
|
|
if (ls == NULL) {
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
|
|
ls->addr_ntop = 1;
|
|
ls->handler = ngx_stream_init_connection;
|
|
ls->pool_size = 256;
|
|
|
|
cscf = addr->ctx->srv_conf[ngx_stream_core_module.ctx_index];
|
|
|
|
ls->logp = cscf->error_log;
|
|
ls->log.data = &ls->addr_text;
|
|
ls->log.handler = ngx_accept_log_error;
|
|
|
|
ls->keepalive = addr[i].so_keepalive;
|
|
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
|
|
ls->keepidle = addr[i].tcp_keepidle;
|
|
ls->keepintvl = addr[i].tcp_keepintvl;
|
|
ls->keepcnt = addr[i].tcp_keepcnt;
|
|
#endif
|
|
|
|
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
|
|
ls->ipv6only = addr[i].ipv6only;
|
|
#endif
|
|
|
|
stport = ngx_palloc(cf->pool, sizeof(ngx_stream_port_t));
|
|
if (stport == NULL) {
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
|
|
ls->servers = stport;
|
|
|
|
stport->naddrs = i + 1;
|
|
|
|
switch (ls->sockaddr->sa_family) {
|
|
#if (NGX_HAVE_INET6)
|
|
case AF_INET6:
|
|
if (ngx_stream_add_addrs6(cf, stport, addr) != NGX_OK) {
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
break;
|
|
#endif
|
|
default: /* AF_INET */
|
|
if (ngx_stream_add_addrs(cf, stport, addr) != NGX_OK) {
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (ngx_clone_listening(cf, ls) != NGX_OK) {
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
|
|
addr++;
|
|
last--;
|
|
}
|
|
}
|
|
|
|
return NGX_CONF_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_stream_add_addrs(ngx_conf_t *cf, ngx_stream_port_t *stport,
|
|
ngx_stream_conf_addr_t *addr)
|
|
{
|
|
u_char *p;
|
|
size_t len;
|
|
ngx_uint_t i;
|
|
struct sockaddr_in *sin;
|
|
ngx_stream_in_addr_t *addrs;
|
|
u_char buf[NGX_SOCKADDR_STRLEN];
|
|
|
|
stport->addrs = ngx_pcalloc(cf->pool,
|
|
stport->naddrs * sizeof(ngx_stream_in_addr_t));
|
|
if (stport->addrs == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
addrs = stport->addrs;
|
|
|
|
for (i = 0; i < stport->naddrs; i++) {
|
|
|
|
sin = (struct sockaddr_in *) addr[i].sockaddr;
|
|
addrs[i].addr = sin->sin_addr.s_addr;
|
|
|
|
addrs[i].conf.ctx = addr[i].ctx;
|
|
#if (NGX_STREAM_SSL)
|
|
addrs[i].conf.ssl = addr[i].ssl;
|
|
#endif
|
|
|
|
len = ngx_sock_ntop(addr[i].sockaddr, addr[i].socklen, buf,
|
|
NGX_SOCKADDR_STRLEN, 1);
|
|
|
|
p = ngx_pnalloc(cf->pool, len);
|
|
if (p == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ngx_memcpy(p, buf, len);
|
|
|
|
addrs[i].conf.addr_text.len = len;
|
|
addrs[i].conf.addr_text.data = p;
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
#if (NGX_HAVE_INET6)
|
|
|
|
static ngx_int_t
|
|
ngx_stream_add_addrs6(ngx_conf_t *cf, ngx_stream_port_t *stport,
|
|
ngx_stream_conf_addr_t *addr)
|
|
{
|
|
u_char *p;
|
|
size_t len;
|
|
ngx_uint_t i;
|
|
struct sockaddr_in6 *sin6;
|
|
ngx_stream_in6_addr_t *addrs6;
|
|
u_char buf[NGX_SOCKADDR_STRLEN];
|
|
|
|
stport->addrs = ngx_pcalloc(cf->pool,
|
|
stport->naddrs * sizeof(ngx_stream_in6_addr_t));
|
|
if (stport->addrs == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
addrs6 = stport->addrs;
|
|
|
|
for (i = 0; i < stport->naddrs; i++) {
|
|
|
|
sin6 = (struct sockaddr_in6 *) addr[i].sockaddr;
|
|
addrs6[i].addr6 = sin6->sin6_addr;
|
|
|
|
addrs6[i].conf.ctx = addr[i].ctx;
|
|
#if (NGX_STREAM_SSL)
|
|
addrs6[i].conf.ssl = addr[i].ssl;
|
|
#endif
|
|
|
|
len = ngx_sock_ntop(addr[i].sockaddr, addr[i].socklen, buf,
|
|
NGX_SOCKADDR_STRLEN, 1);
|
|
|
|
p = ngx_pnalloc(cf->pool, len);
|
|
if (p == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ngx_memcpy(p, buf, len);
|
|
|
|
addrs6[i].conf.addr_text.len = len;
|
|
addrs6[i].conf.addr_text.data = p;
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_stream_cmp_conf_addrs(const void *one, const void *two)
|
|
{
|
|
ngx_stream_conf_addr_t *first, *second;
|
|
|
|
first = (ngx_stream_conf_addr_t *) one;
|
|
second = (ngx_stream_conf_addr_t *) two;
|
|
|
|
if (first->wildcard) {
|
|
/* a wildcard must be the last resort, shift it to the end */
|
|
return 1;
|
|
}
|
|
|
|
if (second->wildcard) {
|
|
/* a wildcard must be the last resort, shift it to the end */
|
|
return -1;
|
|
}
|
|
|
|
if (first->bind && !second->bind) {
|
|
/* shift explicit bind()ed addresses to the start */
|
|
return -1;
|
|
}
|
|
|
|
if (!first->bind && second->bind) {
|
|
/* shift explicit bind()ed addresses to the start */
|
|
return 1;
|
|
}
|
|
|
|
/* do not sort by default */
|
|
|
|
return 0;
|
|
}
|