From e18a033b921d0d79fa8278f853548e6125b93e0c Mon Sep 17 00:00:00 2001 From: Konstantin Ananyev Date: Tue, 31 Oct 2017 12:40:17 +0000 Subject: Integrate TLDK with NGINX Created a clone of nginx (from https://github.com/nginx/nginx) to demonstrate and benchmark TLDK library integrated with real world application. A new nginx module is created and and BSD socket-like API is implemented on top of native TLDK API. Note, that right now only minimalistic subset of socket-like API is provided: - accept - close - readv - recv - writev so only limited nginx functionality is available for a moment. Change-Id: Ie1efe9349a0538da4348a48fb8306cbf636b5a92 Signed-off-by: Mohammad Abdul Awal Signed-off-by: Reshma Pattan Signed-off-by: Remy Horton Signed-off-by: Konstantin Ananyev --- app/nginx/src/stream/ngx_stream.c | 683 ++++++ app/nginx/src/stream/ngx_stream.h | 304 +++ app/nginx/src/stream/ngx_stream_access_module.c | 459 +++++ app/nginx/src/stream/ngx_stream_core_module.c | 888 ++++++++ app/nginx/src/stream/ngx_stream_geo_module.c | 1586 ++++++++++++++ app/nginx/src/stream/ngx_stream_geoip_module.c | 814 ++++++++ app/nginx/src/stream/ngx_stream_handler.c | 385 ++++ .../src/stream/ngx_stream_limit_conn_module.c | 646 ++++++ app/nginx/src/stream/ngx_stream_log_module.c | 1543 ++++++++++++++ app/nginx/src/stream/ngx_stream_map_module.c | 588 ++++++ app/nginx/src/stream/ngx_stream_proxy_module.c | 2170 ++++++++++++++++++++ app/nginx/src/stream/ngx_stream_realip_module.c | 348 ++++ app/nginx/src/stream/ngx_stream_return_module.c | 218 ++ app/nginx/src/stream/ngx_stream_script.c | 921 +++++++++ app/nginx/src/stream/ngx_stream_script.h | 127 ++ .../src/stream/ngx_stream_split_clients_module.c | 244 +++ app/nginx/src/stream/ngx_stream_ssl_module.c | 839 ++++++++ app/nginx/src/stream/ngx_stream_ssl_module.h | 56 + .../src/stream/ngx_stream_ssl_preread_module.c | 449 ++++ app/nginx/src/stream/ngx_stream_upstream.c | 717 +++++++ app/nginx/src/stream/ngx_stream_upstream.h | 154 ++ .../src/stream/ngx_stream_upstream_hash_module.c | 675 ++++++ .../stream/ngx_stream_upstream_least_conn_module.c | 310 +++ .../src/stream/ngx_stream_upstream_round_robin.c | 874 ++++++++ .../src/stream/ngx_stream_upstream_round_robin.h | 146 ++ .../src/stream/ngx_stream_upstream_zone_module.c | 243 +++ app/nginx/src/stream/ngx_stream_variables.c | 1234 +++++++++++ app/nginx/src/stream/ngx_stream_variables.h | 111 + .../src/stream/ngx_stream_write_filter_module.c | 273 +++ 29 files changed, 18005 insertions(+) create mode 100644 app/nginx/src/stream/ngx_stream.c create mode 100644 app/nginx/src/stream/ngx_stream.h create mode 100644 app/nginx/src/stream/ngx_stream_access_module.c create mode 100644 app/nginx/src/stream/ngx_stream_core_module.c create mode 100644 app/nginx/src/stream/ngx_stream_geo_module.c create mode 100644 app/nginx/src/stream/ngx_stream_geoip_module.c create mode 100644 app/nginx/src/stream/ngx_stream_handler.c create mode 100644 app/nginx/src/stream/ngx_stream_limit_conn_module.c create mode 100644 app/nginx/src/stream/ngx_stream_log_module.c create mode 100644 app/nginx/src/stream/ngx_stream_map_module.c create mode 100644 app/nginx/src/stream/ngx_stream_proxy_module.c create mode 100644 app/nginx/src/stream/ngx_stream_realip_module.c create mode 100644 app/nginx/src/stream/ngx_stream_return_module.c create mode 100644 app/nginx/src/stream/ngx_stream_script.c create mode 100644 app/nginx/src/stream/ngx_stream_script.h create mode 100644 app/nginx/src/stream/ngx_stream_split_clients_module.c create mode 100644 app/nginx/src/stream/ngx_stream_ssl_module.c create mode 100644 app/nginx/src/stream/ngx_stream_ssl_module.h create mode 100644 app/nginx/src/stream/ngx_stream_ssl_preread_module.c create mode 100644 app/nginx/src/stream/ngx_stream_upstream.c create mode 100644 app/nginx/src/stream/ngx_stream_upstream.h create mode 100644 app/nginx/src/stream/ngx_stream_upstream_hash_module.c create mode 100644 app/nginx/src/stream/ngx_stream_upstream_least_conn_module.c create mode 100644 app/nginx/src/stream/ngx_stream_upstream_round_robin.c create mode 100644 app/nginx/src/stream/ngx_stream_upstream_round_robin.h create mode 100644 app/nginx/src/stream/ngx_stream_upstream_zone_module.c create mode 100644 app/nginx/src/stream/ngx_stream_variables.c create mode 100644 app/nginx/src/stream/ngx_stream_variables.h create mode 100644 app/nginx/src/stream/ngx_stream_write_filter_module.c (limited to 'app/nginx/src/stream') diff --git a/app/nginx/src/stream/ngx_stream.c b/app/nginx/src/stream/ngx_stream.c new file mode 100644 index 0000000..4a394d7 --- /dev/null +++ b/app/nginx/src/stream/ngx_stream.c @@ -0,0 +1,683 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +static char *ngx_stream_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static ngx_int_t ngx_stream_init_phases(ngx_conf_t *cf, + ngx_stream_core_main_conf_t *cmcf); +static ngx_int_t ngx_stream_init_phase_handlers(ngx_conf_t *cf, + ngx_stream_core_main_conf_t *cmcf); +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; + + +ngx_stream_filter_pt ngx_stream_top_filter; + + +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; + + if (*(ngx_stream_conf_ctx_t **) conf) { + return "is duplicate"; + } + + /* 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 = ngx_count_modules(cf->cycle, NGX_STREAM_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; cf->cycle->modules[m]; m++) { + if (cf->cycle->modules[m]->type != NGX_STREAM_MODULE) { + continue; + } + + module = cf->cycle->modules[m]->ctx; + mi = cf->cycle->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; + } + } + } + + + pcf = *cf; + cf->ctx = ctx; + + for (m = 0; cf->cycle->modules[m]; m++) { + if (cf->cycle->modules[m]->type != NGX_STREAM_MODULE) { + continue; + } + + module = cf->cycle->modules[m]->ctx; + + if (module->preconfiguration) { + if (module->preconfiguration(cf) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + } + + + /* parse inside the stream{} block */ + + 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; cf->cycle->modules[m]; m++) { + if (cf->cycle->modules[m]->type != NGX_STREAM_MODULE) { + continue; + } + + module = cf->cycle->modules[m]->ctx; + mi = cf->cycle->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; + } + } + } + } + + if (ngx_stream_init_phases(cf, cmcf) != NGX_OK) { + return NGX_CONF_ERROR; + } + + for (m = 0; cf->cycle->modules[m]; m++) { + if (cf->cycle->modules[m]->type != NGX_STREAM_MODULE) { + continue; + } + + module = cf->cycle->modules[m]->ctx; + + if (module->postconfiguration) { + if (module->postconfiguration(cf) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + } + + if (ngx_stream_variables_init_vars(cf) != NGX_OK) { + return NGX_CONF_ERROR; + } + + *cf = pcf; + + if (ngx_stream_init_phase_handlers(cf, cmcf) != NGX_OK) { + return NGX_CONF_ERROR; + } + + 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_init_phases(ngx_conf_t *cf, ngx_stream_core_main_conf_t *cmcf) +{ + if (ngx_array_init(&cmcf->phases[NGX_STREAM_POST_ACCEPT_PHASE].handlers, + cf->pool, 1, sizeof(ngx_stream_handler_pt)) + != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_array_init(&cmcf->phases[NGX_STREAM_PREACCESS_PHASE].handlers, + cf->pool, 1, sizeof(ngx_stream_handler_pt)) + != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_array_init(&cmcf->phases[NGX_STREAM_ACCESS_PHASE].handlers, + cf->pool, 1, sizeof(ngx_stream_handler_pt)) + != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_array_init(&cmcf->phases[NGX_STREAM_SSL_PHASE].handlers, + cf->pool, 1, sizeof(ngx_stream_handler_pt)) + != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_array_init(&cmcf->phases[NGX_STREAM_PREREAD_PHASE].handlers, + cf->pool, 1, sizeof(ngx_stream_handler_pt)) + != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_array_init(&cmcf->phases[NGX_STREAM_LOG_PHASE].handlers, + cf->pool, 1, sizeof(ngx_stream_handler_pt)) + != NGX_OK) + { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_init_phase_handlers(ngx_conf_t *cf, + ngx_stream_core_main_conf_t *cmcf) +{ + ngx_int_t j; + ngx_uint_t i, n; + ngx_stream_handler_pt *h; + ngx_stream_phase_handler_t *ph; + ngx_stream_phase_handler_pt checker; + + n = 1 /* content phase */; + + for (i = 0; i < NGX_STREAM_LOG_PHASE; i++) { + n += cmcf->phases[i].handlers.nelts; + } + + ph = ngx_pcalloc(cf->pool, + n * sizeof(ngx_stream_phase_handler_t) + sizeof(void *)); + if (ph == NULL) { + return NGX_ERROR; + } + + cmcf->phase_engine.handlers = ph; + n = 0; + + for (i = 0; i < NGX_STREAM_LOG_PHASE; i++) { + h = cmcf->phases[i].handlers.elts; + + switch (i) { + + case NGX_STREAM_PREREAD_PHASE: + checker = ngx_stream_core_preread_phase; + break; + + case NGX_STREAM_CONTENT_PHASE: + ph->checker = ngx_stream_core_content_phase; + n++; + ph++; + + continue; + + default: + checker = ngx_stream_core_generic_phase; + } + + n += cmcf->phases[i].handlers.nelts; + + for (j = cmcf->phases[i].handlers.nelts - 1; j >= 0; j--) { + ph->checker = checker; + ph->handler = h[j]; + ph->next = n; + ph++; + } + } + + return NGX_OK; +} + + +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; + ngx_stream_conf_port_t *port; + ngx_stream_conf_addr_t *addr; + + sa = &listen->sockaddr.sockaddr; + p = ngx_inet_get_port(sa); + + port = ports->elts; + for (i = 0; i < ports->nelts; i++) { + + if (p == port[i].port + && listen->type == port[i].type + && 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->type = listen->type; + 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->opt = *listen; + + 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].opt.wildcard) { + addr[last - 1].opt.bind = 1; + bind_wildcard = 1; + + } else { + bind_wildcard = 0; + } + + i = 0; + + while (i < last) { + + if (bind_wildcard && !addr[i].opt.bind) { + i++; + continue; + } + + ls = ngx_create_listening(cf, &addr[i].opt.sockaddr.sockaddr, + addr[i].opt.socklen); + if (ls == NULL) { + return NGX_CONF_ERROR; + } + + ls->addr_ntop = 1; + ls->handler = ngx_stream_init_connection; + ls->pool_size = 256; + ls->type = addr[i].opt.type; + + cscf = addr->opt.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->backlog = addr[i].opt.backlog; + + ls->wildcard = addr[i].opt.wildcard; + + ls->keepalive = addr[i].opt.so_keepalive; +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + ls->keepidle = addr[i].opt.tcp_keepidle; + ls->keepintvl = addr[i].opt.tcp_keepintvl; + ls->keepcnt = addr[i].opt.tcp_keepcnt; +#endif + +#if (NGX_HAVE_INET6) + ls->ipv6only = addr[i].opt.ipv6only; +#endif + +#if (NGX_HAVE_REUSEPORT) + ls->reuseport = addr[i].opt.reuseport; +#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 = &addr[i].opt.sockaddr.sockaddr_in; + addrs[i].addr = sin->sin_addr.s_addr; + + addrs[i].conf.ctx = addr[i].opt.ctx; +#if (NGX_STREAM_SSL) + addrs[i].conf.ssl = addr[i].opt.ssl; +#endif + addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; + + len = ngx_sock_ntop(&addr[i].opt.sockaddr.sockaddr, addr[i].opt.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 = &addr[i].opt.sockaddr.sockaddr_in6; + addrs6[i].addr6 = sin6->sin6_addr; + + addrs6[i].conf.ctx = addr[i].opt.ctx; +#if (NGX_STREAM_SSL) + addrs6[i].conf.ssl = addr[i].opt.ssl; +#endif + addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; + + len = ngx_sock_ntop(&addr[i].opt.sockaddr.sockaddr, addr[i].opt.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->opt.wildcard) { + /* a wildcard must be the last resort, shift it to the end */ + return 1; + } + + if (second->opt.wildcard) { + /* a wildcard must be the last resort, shift it to the end */ + return -1; + } + + if (first->opt.bind && !second->opt.bind) { + /* shift explicit bind()ed addresses to the start */ + return -1; + } + + if (!first->opt.bind && second->opt.bind) { + /* shift explicit bind()ed addresses to the start */ + return 1; + } + + /* do not sort by default */ + + return 0; +} diff --git a/app/nginx/src/stream/ngx_stream.h b/app/nginx/src/stream/ngx_stream.h new file mode 100644 index 0000000..814e3b9 --- /dev/null +++ b/app/nginx/src/stream/ngx_stream.h @@ -0,0 +1,304 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_STREAM_H_INCLUDED_ +#define _NGX_STREAM_H_INCLUDED_ + + +#include +#include + +#if (NGX_STREAM_SSL) +#include +#endif + + +typedef struct ngx_stream_session_s ngx_stream_session_t; + + +#include +#include +#include +#include + + +#define NGX_STREAM_OK 200 +#define NGX_STREAM_BAD_REQUEST 400 +#define NGX_STREAM_FORBIDDEN 403 +#define NGX_STREAM_INTERNAL_SERVER_ERROR 500 +#define NGX_STREAM_BAD_GATEWAY 502 +#define NGX_STREAM_SERVICE_UNAVAILABLE 503 + + +typedef struct { + void **main_conf; + void **srv_conf; +} ngx_stream_conf_ctx_t; + + +typedef struct { + ngx_sockaddr_t sockaddr; + socklen_t socklen; + + /* server ctx */ + ngx_stream_conf_ctx_t *ctx; + + unsigned bind:1; + unsigned wildcard:1; + unsigned ssl:1; +#if (NGX_HAVE_INET6) + unsigned ipv6only:1; +#endif + unsigned reuseport:1; + unsigned so_keepalive:2; + unsigned proxy_protocol:1; +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + int tcp_keepidle; + int tcp_keepintvl; + int tcp_keepcnt; +#endif + int backlog; + int type; +} ngx_stream_listen_t; + + +typedef struct { + ngx_stream_conf_ctx_t *ctx; + ngx_str_t addr_text; + unsigned ssl:1; + unsigned proxy_protocol:1; +} ngx_stream_addr_conf_t; + +typedef struct { + in_addr_t addr; + ngx_stream_addr_conf_t conf; +} ngx_stream_in_addr_t; + + +#if (NGX_HAVE_INET6) + +typedef struct { + struct in6_addr addr6; + ngx_stream_addr_conf_t conf; +} ngx_stream_in6_addr_t; + +#endif + + +typedef struct { + /* ngx_stream_in_addr_t or ngx_stream_in6_addr_t */ + void *addrs; + ngx_uint_t naddrs; +} ngx_stream_port_t; + + +typedef struct { + int family; + int type; + in_port_t port; + ngx_array_t addrs; /* array of ngx_stream_conf_addr_t */ +} ngx_stream_conf_port_t; + + +typedef struct { + ngx_stream_listen_t opt; +} ngx_stream_conf_addr_t; + + +typedef enum { + NGX_STREAM_POST_ACCEPT_PHASE = 0, + NGX_STREAM_PREACCESS_PHASE, + NGX_STREAM_ACCESS_PHASE, + NGX_STREAM_SSL_PHASE, + NGX_STREAM_PREREAD_PHASE, + NGX_STREAM_CONTENT_PHASE, + NGX_STREAM_LOG_PHASE +} ngx_stream_phases; + + +typedef struct ngx_stream_phase_handler_s ngx_stream_phase_handler_t; + +typedef ngx_int_t (*ngx_stream_phase_handler_pt)(ngx_stream_session_t *s, + ngx_stream_phase_handler_t *ph); +typedef ngx_int_t (*ngx_stream_handler_pt)(ngx_stream_session_t *s); +typedef void (*ngx_stream_content_handler_pt)(ngx_stream_session_t *s); + + +struct ngx_stream_phase_handler_s { + ngx_stream_phase_handler_pt checker; + ngx_stream_handler_pt handler; + ngx_uint_t next; +}; + + +typedef struct { + ngx_stream_phase_handler_t *handlers; +} ngx_stream_phase_engine_t; + + +typedef struct { + ngx_array_t handlers; +} ngx_stream_phase_t; + + +typedef struct { + ngx_array_t servers; /* ngx_stream_core_srv_conf_t */ + ngx_array_t listen; /* ngx_stream_listen_t */ + + ngx_stream_phase_engine_t phase_engine; + + ngx_hash_t variables_hash; + + ngx_array_t variables; /* ngx_stream_variable_t */ + ngx_array_t prefix_variables; /* ngx_stream_variable_t */ + ngx_uint_t ncaptures; + + ngx_uint_t variables_hash_max_size; + ngx_uint_t variables_hash_bucket_size; + + ngx_hash_keys_arrays_t *variables_keys; + + ngx_stream_phase_t phases[NGX_STREAM_LOG_PHASE + 1]; +} ngx_stream_core_main_conf_t; + + +typedef struct { + ngx_stream_content_handler_pt handler; + + ngx_stream_conf_ctx_t *ctx; + + u_char *file_name; + ngx_uint_t line; + + ngx_flag_t tcp_nodelay; + size_t preread_buffer_size; + ngx_msec_t preread_timeout; + + ngx_log_t *error_log; + + ngx_msec_t resolver_timeout; + ngx_resolver_t *resolver; + + ngx_msec_t proxy_protocol_timeout; + + ngx_uint_t listen; /* unsigned listen:1; */ +} ngx_stream_core_srv_conf_t; + + +struct ngx_stream_session_s { + uint32_t signature; /* "STRM" */ + + ngx_connection_t *connection; + + off_t received; + time_t start_sec; + ngx_msec_t start_msec; + + ngx_log_handler_pt log_handler; + + void **ctx; + void **main_conf; + void **srv_conf; + + ngx_stream_upstream_t *upstream; + ngx_array_t *upstream_states; + /* of ngx_stream_upstream_state_t */ + ngx_stream_variable_value_t *variables; + +#if (NGX_PCRE) + ngx_uint_t ncaptures; + int *captures; + u_char *captures_data; +#endif + + ngx_int_t phase_handler; + ngx_uint_t status; + + unsigned ssl:1; + + unsigned stat_processing:1; + + unsigned health_check:1; +}; + + +typedef struct { + ngx_int_t (*preconfiguration)(ngx_conf_t *cf); + ngx_int_t (*postconfiguration)(ngx_conf_t *cf); + + void *(*create_main_conf)(ngx_conf_t *cf); + char *(*init_main_conf)(ngx_conf_t *cf, void *conf); + + void *(*create_srv_conf)(ngx_conf_t *cf); + char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, + void *conf); +} ngx_stream_module_t; + + +#define NGX_STREAM_MODULE 0x4d525453 /* "STRM" */ + +#define NGX_STREAM_MAIN_CONF 0x02000000 +#define NGX_STREAM_SRV_CONF 0x04000000 +#define NGX_STREAM_UPS_CONF 0x08000000 + + +#define NGX_STREAM_MAIN_CONF_OFFSET offsetof(ngx_stream_conf_ctx_t, main_conf) +#define NGX_STREAM_SRV_CONF_OFFSET offsetof(ngx_stream_conf_ctx_t, srv_conf) + + +#define ngx_stream_get_module_ctx(s, module) (s)->ctx[module.ctx_index] +#define ngx_stream_set_ctx(s, c, module) s->ctx[module.ctx_index] = c; +#define ngx_stream_delete_ctx(s, module) s->ctx[module.ctx_index] = NULL; + + +#define ngx_stream_get_module_main_conf(s, module) \ + (s)->main_conf[module.ctx_index] +#define ngx_stream_get_module_srv_conf(s, module) \ + (s)->srv_conf[module.ctx_index] + +#define ngx_stream_conf_get_module_main_conf(cf, module) \ + ((ngx_stream_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index] +#define ngx_stream_conf_get_module_srv_conf(cf, module) \ + ((ngx_stream_conf_ctx_t *) cf->ctx)->srv_conf[module.ctx_index] + +#define ngx_stream_cycle_get_module_main_conf(cycle, module) \ + (cycle->conf_ctx[ngx_stream_module.index] ? \ + ((ngx_stream_conf_ctx_t *) cycle->conf_ctx[ngx_stream_module.index]) \ + ->main_conf[module.ctx_index]: \ + NULL) + + +#define NGX_STREAM_WRITE_BUFFERED 0x10 + + +void ngx_stream_core_run_phases(ngx_stream_session_t *s); +ngx_int_t ngx_stream_core_generic_phase(ngx_stream_session_t *s, + ngx_stream_phase_handler_t *ph); +ngx_int_t ngx_stream_core_preread_phase(ngx_stream_session_t *s, + ngx_stream_phase_handler_t *ph); +ngx_int_t ngx_stream_core_content_phase(ngx_stream_session_t *s, + ngx_stream_phase_handler_t *ph); + + +void ngx_stream_init_connection(ngx_connection_t *c); +void ngx_stream_session_handler(ngx_event_t *rev); +void ngx_stream_finalize_session(ngx_stream_session_t *s, ngx_uint_t rc); + + +extern ngx_module_t ngx_stream_module; +extern ngx_uint_t ngx_stream_max_module; +extern ngx_module_t ngx_stream_core_module; + + +typedef ngx_int_t (*ngx_stream_filter_pt)(ngx_stream_session_t *s, + ngx_chain_t *chain, ngx_uint_t from_upstream); + + +extern ngx_stream_filter_pt ngx_stream_top_filter; + + +#endif /* _NGX_STREAM_H_INCLUDED_ */ diff --git a/app/nginx/src/stream/ngx_stream_access_module.c b/app/nginx/src/stream/ngx_stream_access_module.c new file mode 100644 index 0000000..1745cdf --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_access_module.c @@ -0,0 +1,459 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef struct { + in_addr_t mask; + in_addr_t addr; + ngx_uint_t deny; /* unsigned deny:1; */ +} ngx_stream_access_rule_t; + +#if (NGX_HAVE_INET6) + +typedef struct { + struct in6_addr addr; + struct in6_addr mask; + ngx_uint_t deny; /* unsigned deny:1; */ +} ngx_stream_access_rule6_t; + +#endif + +#if (NGX_HAVE_UNIX_DOMAIN) + +typedef struct { + ngx_uint_t deny; /* unsigned deny:1; */ +} ngx_stream_access_rule_un_t; + +#endif + +typedef struct { + ngx_array_t *rules; /* array of ngx_stream_access_rule_t */ +#if (NGX_HAVE_INET6) + ngx_array_t *rules6; /* array of ngx_stream_access_rule6_t */ +#endif +#if (NGX_HAVE_UNIX_DOMAIN) + ngx_array_t *rules_un; /* array of ngx_stream_access_rule_un_t */ +#endif +} ngx_stream_access_srv_conf_t; + + +static ngx_int_t ngx_stream_access_handler(ngx_stream_session_t *s); +static ngx_int_t ngx_stream_access_inet(ngx_stream_session_t *s, + ngx_stream_access_srv_conf_t *ascf, in_addr_t addr); +#if (NGX_HAVE_INET6) +static ngx_int_t ngx_stream_access_inet6(ngx_stream_session_t *s, + ngx_stream_access_srv_conf_t *ascf, u_char *p); +#endif +#if (NGX_HAVE_UNIX_DOMAIN) +static ngx_int_t ngx_stream_access_unix(ngx_stream_session_t *s, + ngx_stream_access_srv_conf_t *ascf); +#endif +static ngx_int_t ngx_stream_access_found(ngx_stream_session_t *s, + ngx_uint_t deny); +static char *ngx_stream_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static void *ngx_stream_access_create_srv_conf(ngx_conf_t *cf); +static char *ngx_stream_access_merge_srv_conf(ngx_conf_t *cf, + void *parent, void *child); +static ngx_int_t ngx_stream_access_init(ngx_conf_t *cf); + + +static ngx_command_t ngx_stream_access_commands[] = { + + { ngx_string("allow"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_stream_access_rule, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("deny"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_stream_access_rule, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + + +static ngx_stream_module_t ngx_stream_access_module_ctx = { + NULL, /* preconfiguration */ + ngx_stream_access_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_stream_access_create_srv_conf, /* create server configuration */ + ngx_stream_access_merge_srv_conf /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_access_module = { + NGX_MODULE_V1, + &ngx_stream_access_module_ctx, /* module context */ + ngx_stream_access_commands, /* module directives */ + NGX_STREAM_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_stream_access_handler(ngx_stream_session_t *s) +{ + struct sockaddr_in *sin; + ngx_stream_access_srv_conf_t *ascf; +#if (NGX_HAVE_INET6) + u_char *p; + in_addr_t addr; + struct sockaddr_in6 *sin6; +#endif + + ascf = ngx_stream_get_module_srv_conf(s, ngx_stream_access_module); + + switch (s->connection->sockaddr->sa_family) { + + case AF_INET: + if (ascf->rules) { + sin = (struct sockaddr_in *) s->connection->sockaddr; + return ngx_stream_access_inet(s, ascf, sin->sin_addr.s_addr); + } + break; + +#if (NGX_HAVE_INET6) + + case AF_INET6: + sin6 = (struct sockaddr_in6 *) s->connection->sockaddr; + p = sin6->sin6_addr.s6_addr; + + if (ascf->rules && IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { + addr = p[12] << 24; + addr += p[13] << 16; + addr += p[14] << 8; + addr += p[15]; + return ngx_stream_access_inet(s, ascf, htonl(addr)); + } + + if (ascf->rules6) { + return ngx_stream_access_inet6(s, ascf, p); + } + + break; + +#endif + +#if (NGX_HAVE_UNIX_DOMAIN) + + case AF_UNIX: + if (ascf->rules_un) { + return ngx_stream_access_unix(s, ascf); + } + + break; + +#endif + } + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_stream_access_inet(ngx_stream_session_t *s, + ngx_stream_access_srv_conf_t *ascf, in_addr_t addr) +{ + ngx_uint_t i; + ngx_stream_access_rule_t *rule; + + rule = ascf->rules->elts; + for (i = 0; i < ascf->rules->nelts; i++) { + + ngx_log_debug3(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "access: %08XD %08XD %08XD", + addr, rule[i].mask, rule[i].addr); + + if ((addr & rule[i].mask) == rule[i].addr) { + return ngx_stream_access_found(s, rule[i].deny); + } + } + + return NGX_DECLINED; +} + + +#if (NGX_HAVE_INET6) + +static ngx_int_t +ngx_stream_access_inet6(ngx_stream_session_t *s, + ngx_stream_access_srv_conf_t *ascf, u_char *p) +{ + ngx_uint_t n; + ngx_uint_t i; + ngx_stream_access_rule6_t *rule6; + + rule6 = ascf->rules6->elts; + for (i = 0; i < ascf->rules6->nelts; i++) { + +#if (NGX_DEBUG) + { + size_t cl, ml, al; + u_char ct[NGX_INET6_ADDRSTRLEN]; + u_char mt[NGX_INET6_ADDRSTRLEN]; + u_char at[NGX_INET6_ADDRSTRLEN]; + + cl = ngx_inet6_ntop(p, ct, NGX_INET6_ADDRSTRLEN); + ml = ngx_inet6_ntop(rule6[i].mask.s6_addr, mt, NGX_INET6_ADDRSTRLEN); + al = ngx_inet6_ntop(rule6[i].addr.s6_addr, at, NGX_INET6_ADDRSTRLEN); + + ngx_log_debug6(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "access: %*s %*s %*s", cl, ct, ml, mt, al, at); + } +#endif + + for (n = 0; n < 16; n++) { + if ((p[n] & rule6[i].mask.s6_addr[n]) != rule6[i].addr.s6_addr[n]) { + goto next; + } + } + + return ngx_stream_access_found(s, rule6[i].deny); + + next: + continue; + } + + return NGX_DECLINED; +} + +#endif + + +#if (NGX_HAVE_UNIX_DOMAIN) + +static ngx_int_t +ngx_stream_access_unix(ngx_stream_session_t *s, + ngx_stream_access_srv_conf_t *ascf) +{ + ngx_uint_t i; + ngx_stream_access_rule_un_t *rule_un; + + rule_un = ascf->rules_un->elts; + for (i = 0; i < ascf->rules_un->nelts; i++) { + + /* TODO: check path */ + if (1) { + return ngx_stream_access_found(s, rule_un[i].deny); + } + } + + return NGX_DECLINED; +} + +#endif + + +static ngx_int_t +ngx_stream_access_found(ngx_stream_session_t *s, ngx_uint_t deny) +{ + if (deny) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "access forbidden by rule"); + return NGX_STREAM_FORBIDDEN; + } + + return NGX_OK; +} + + +static char * +ngx_stream_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_access_srv_conf_t *ascf = conf; + + ngx_int_t rc; + ngx_uint_t all; + ngx_str_t *value; + ngx_cidr_t cidr; + ngx_stream_access_rule_t *rule; +#if (NGX_HAVE_INET6) + ngx_stream_access_rule6_t *rule6; +#endif +#if (NGX_HAVE_UNIX_DOMAIN) + ngx_stream_access_rule_un_t *rule_un; +#endif + + ngx_memzero(&cidr, sizeof(ngx_cidr_t)); + + value = cf->args->elts; + + all = (value[1].len == 3 && ngx_strcmp(value[1].data, "all") == 0); + + if (!all) { + +#if (NGX_HAVE_UNIX_DOMAIN) + + if (value[1].len == 5 && ngx_strcmp(value[1].data, "unix:") == 0) { + cidr.family = AF_UNIX; + rc = NGX_OK; + + } else { + rc = ngx_ptocidr(&value[1], &cidr); + } + +#else + rc = ngx_ptocidr(&value[1], &cidr); +#endif + + if (rc == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[1]); + return NGX_CONF_ERROR; + } + + if (rc == NGX_DONE) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "low address bits of %V are meaningless", &value[1]); + } + } + + if (cidr.family == AF_INET || all) { + + if (ascf->rules == NULL) { + ascf->rules = ngx_array_create(cf->pool, 4, + sizeof(ngx_stream_access_rule_t)); + if (ascf->rules == NULL) { + return NGX_CONF_ERROR; + } + } + + rule = ngx_array_push(ascf->rules); + if (rule == NULL) { + return NGX_CONF_ERROR; + } + + rule->mask = cidr.u.in.mask; + rule->addr = cidr.u.in.addr; + rule->deny = (value[0].data[0] == 'd') ? 1 : 0; + } + +#if (NGX_HAVE_INET6) + if (cidr.family == AF_INET6 || all) { + + if (ascf->rules6 == NULL) { + ascf->rules6 = ngx_array_create(cf->pool, 4, + sizeof(ngx_stream_access_rule6_t)); + if (ascf->rules6 == NULL) { + return NGX_CONF_ERROR; + } + } + + rule6 = ngx_array_push(ascf->rules6); + if (rule6 == NULL) { + return NGX_CONF_ERROR; + } + + rule6->mask = cidr.u.in6.mask; + rule6->addr = cidr.u.in6.addr; + rule6->deny = (value[0].data[0] == 'd') ? 1 : 0; + } +#endif + +#if (NGX_HAVE_UNIX_DOMAIN) + if (cidr.family == AF_UNIX || all) { + + if (ascf->rules_un == NULL) { + ascf->rules_un = ngx_array_create(cf->pool, 1, + sizeof(ngx_stream_access_rule_un_t)); + if (ascf->rules_un == NULL) { + return NGX_CONF_ERROR; + } + } + + rule_un = ngx_array_push(ascf->rules_un); + if (rule_un == NULL) { + return NGX_CONF_ERROR; + } + + rule_un->deny = (value[0].data[0] == 'd') ? 1 : 0; + } +#endif + + return NGX_CONF_OK; +} + + +static void * +ngx_stream_access_create_srv_conf(ngx_conf_t *cf) +{ + ngx_stream_access_srv_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_access_srv_conf_t)); + if (conf == NULL) { + return NULL; + } + + return conf; +} + + +static char * +ngx_stream_access_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_stream_access_srv_conf_t *prev = parent; + ngx_stream_access_srv_conf_t *conf = child; + + if (conf->rules == NULL +#if (NGX_HAVE_INET6) + && conf->rules6 == NULL +#endif +#if (NGX_HAVE_UNIX_DOMAIN) + && conf->rules_un == NULL +#endif + ) { + conf->rules = prev->rules; +#if (NGX_HAVE_INET6) + conf->rules6 = prev->rules6; +#endif +#if (NGX_HAVE_UNIX_DOMAIN) + conf->rules_un = prev->rules_un; +#endif + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_stream_access_init(ngx_conf_t *cf) +{ + ngx_stream_handler_pt *h; + ngx_stream_core_main_conf_t *cmcf; + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + + h = ngx_array_push(&cmcf->phases[NGX_STREAM_ACCESS_PHASE].handlers); + if (h == NULL) { + return NGX_ERROR; + } + + *h = ngx_stream_access_handler; + + return NGX_OK; +} diff --git a/app/nginx/src/stream/ngx_stream_core_module.c b/app/nginx/src/stream/ngx_stream_core_module.c new file mode 100644 index 0000000..f7870ee --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_core_module.c @@ -0,0 +1,888 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +static ngx_int_t ngx_stream_core_preconfiguration(ngx_conf_t *cf); +static void *ngx_stream_core_create_main_conf(ngx_conf_t *cf); +static char *ngx_stream_core_init_main_conf(ngx_conf_t *cf, void *conf); +static void *ngx_stream_core_create_srv_conf(ngx_conf_t *cf); +static char *ngx_stream_core_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); +static char *ngx_stream_core_error_log(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_stream_core_server(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_stream_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +static ngx_command_t ngx_stream_core_commands[] = { + + { ngx_string("variables_hash_max_size"), + NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_MAIN_CONF_OFFSET, + offsetof(ngx_stream_core_main_conf_t, variables_hash_max_size), + NULL }, + + { ngx_string("variables_hash_bucket_size"), + NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_MAIN_CONF_OFFSET, + offsetof(ngx_stream_core_main_conf_t, variables_hash_bucket_size), + NULL }, + + { ngx_string("server"), + NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, + ngx_stream_core_server, + 0, + 0, + NULL }, + + { ngx_string("listen"), + NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, + ngx_stream_core_listen, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("error_log"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, + ngx_stream_core_error_log, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("resolver"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, + ngx_stream_core_resolver, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("resolver_timeout"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_core_srv_conf_t, resolver_timeout), + NULL }, + + { ngx_string("proxy_protocol_timeout"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_core_srv_conf_t, proxy_protocol_timeout), + NULL }, + + { ngx_string("tcp_nodelay"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_core_srv_conf_t, tcp_nodelay), + NULL }, + + { ngx_string("preread_buffer_size"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_core_srv_conf_t, preread_buffer_size), + NULL }, + + { ngx_string("preread_timeout"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_core_srv_conf_t, preread_timeout), + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_core_module_ctx = { + ngx_stream_core_preconfiguration, /* preconfiguration */ + NULL, /* postconfiguration */ + + ngx_stream_core_create_main_conf, /* create main configuration */ + ngx_stream_core_init_main_conf, /* init main configuration */ + + ngx_stream_core_create_srv_conf, /* create server configuration */ + ngx_stream_core_merge_srv_conf /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_core_module = { + NGX_MODULE_V1, + &ngx_stream_core_module_ctx, /* module context */ + ngx_stream_core_commands, /* module directives */ + NGX_STREAM_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 +}; + + +void +ngx_stream_core_run_phases(ngx_stream_session_t *s) +{ + ngx_int_t rc; + ngx_stream_phase_handler_t *ph; + ngx_stream_core_main_conf_t *cmcf; + + cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module); + + ph = cmcf->phase_engine.handlers; + + while (ph[s->phase_handler].checker) { + + rc = ph[s->phase_handler].checker(s, &ph[s->phase_handler]); + + if (rc == NGX_OK) { + return; + } + } +} + + +ngx_int_t +ngx_stream_core_generic_phase(ngx_stream_session_t *s, + ngx_stream_phase_handler_t *ph) +{ + ngx_int_t rc; + + /* + * generic phase checker, + * used by all phases, except for preread and content + */ + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "generic phase: %ui", s->phase_handler); + + rc = ph->handler(s); + + if (rc == NGX_OK) { + s->phase_handler = ph->next; + return NGX_AGAIN; + } + + if (rc == NGX_DECLINED) { + s->phase_handler++; + return NGX_AGAIN; + } + + if (rc == NGX_AGAIN || rc == NGX_DONE) { + return NGX_OK; + } + + if (rc == NGX_ERROR) { + rc = NGX_STREAM_INTERNAL_SERVER_ERROR; + } + + ngx_stream_finalize_session(s, rc); + + return NGX_OK; +} + + +ngx_int_t +ngx_stream_core_preread_phase(ngx_stream_session_t *s, + ngx_stream_phase_handler_t *ph) +{ + size_t size; + ssize_t n; + ngx_int_t rc; + ngx_connection_t *c; + ngx_stream_core_srv_conf_t *cscf; + + c = s->connection; + + c->log->action = "prereading client data"; + + cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module); + + if (c->read->timedout) { + rc = NGX_STREAM_OK; + + } else if (c->read->timer_set) { + rc = NGX_AGAIN; + + } else { + rc = ph->handler(s); + } + + while (rc == NGX_AGAIN) { + + if (c->buffer == NULL) { + c->buffer = ngx_create_temp_buf(c->pool, cscf->preread_buffer_size); + if (c->buffer == NULL) { + rc = NGX_ERROR; + break; + } + } + + size = c->buffer->end - c->buffer->last; + + if (size == 0) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "preread buffer full"); + rc = NGX_STREAM_BAD_REQUEST; + break; + } + + if (c->read->eof) { + rc = NGX_STREAM_OK; + break; + } + + if (!c->read->ready) { + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + rc = NGX_ERROR; + break; + } + + if (!c->read->timer_set) { + ngx_add_timer(c->read, cscf->preread_timeout); + } + + c->read->handler = ngx_stream_session_handler; + + return NGX_OK; + } + + n = c->recv(c, c->buffer->last, size); + + if (n == NGX_ERROR) { + rc = NGX_STREAM_OK; + break; + } + + if (n > 0) { + c->buffer->last += n; + } + + rc = ph->handler(s); + } + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + if (rc == NGX_OK) { + s->phase_handler = ph->next; + return NGX_AGAIN; + } + + if (rc == NGX_DECLINED) { + s->phase_handler++; + return NGX_AGAIN; + } + + if (rc == NGX_DONE) { + return NGX_OK; + } + + if (rc == NGX_ERROR) { + rc = NGX_STREAM_INTERNAL_SERVER_ERROR; + } + + ngx_stream_finalize_session(s, rc); + + return NGX_OK; +} + + +ngx_int_t +ngx_stream_core_content_phase(ngx_stream_session_t *s, + ngx_stream_phase_handler_t *ph) +{ + int tcp_nodelay; + ngx_connection_t *c; + ngx_stream_core_srv_conf_t *cscf; + + c = s->connection; + + c->log->action = NULL; + + cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module); + + if (c->type == SOCK_STREAM + && cscf->tcp_nodelay + && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) + { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, "tcp_nodelay"); + + tcp_nodelay = 1; + + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) == -1) + { + ngx_connection_error(c, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return NGX_OK; + } + + c->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + + cscf->handler(s); + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_core_preconfiguration(ngx_conf_t *cf) +{ + return ngx_stream_variables_add_core_vars(cf); +} + + +static void * +ngx_stream_core_create_main_conf(ngx_conf_t *cf) +{ + ngx_stream_core_main_conf_t *cmcf; + + cmcf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_core_main_conf_t)); + if (cmcf == NULL) { + return NULL; + } + + if (ngx_array_init(&cmcf->servers, cf->pool, 4, + sizeof(ngx_stream_core_srv_conf_t *)) + != NGX_OK) + { + return NULL; + } + + if (ngx_array_init(&cmcf->listen, cf->pool, 4, sizeof(ngx_stream_listen_t)) + != NGX_OK) + { + return NULL; + } + + cmcf->variables_hash_max_size = NGX_CONF_UNSET_UINT; + cmcf->variables_hash_bucket_size = NGX_CONF_UNSET_UINT; + + return cmcf; +} + + +static char * +ngx_stream_core_init_main_conf(ngx_conf_t *cf, void *conf) +{ + ngx_stream_core_main_conf_t *cmcf = conf; + + ngx_conf_init_uint_value(cmcf->variables_hash_max_size, 1024); + ngx_conf_init_uint_value(cmcf->variables_hash_bucket_size, 64); + + cmcf->variables_hash_bucket_size = + ngx_align(cmcf->variables_hash_bucket_size, ngx_cacheline_size); + + if (cmcf->ncaptures) { + cmcf->ncaptures = (cmcf->ncaptures + 1) * 3; + } + + return NGX_CONF_OK; +} + + +static void * +ngx_stream_core_create_srv_conf(ngx_conf_t *cf) +{ + ngx_stream_core_srv_conf_t *cscf; + + cscf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_core_srv_conf_t)); + if (cscf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * cscf->handler = NULL; + * cscf->error_log = NULL; + */ + + cscf->file_name = cf->conf_file->file.name.data; + cscf->line = cf->conf_file->line; + cscf->resolver_timeout = NGX_CONF_UNSET_MSEC; + cscf->proxy_protocol_timeout = NGX_CONF_UNSET_MSEC; + cscf->tcp_nodelay = NGX_CONF_UNSET; + cscf->preread_buffer_size = NGX_CONF_UNSET_SIZE; + cscf->preread_timeout = NGX_CONF_UNSET_MSEC; + + return cscf; +} + + +static char * +ngx_stream_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_stream_core_srv_conf_t *prev = parent; + ngx_stream_core_srv_conf_t *conf = child; + + ngx_conf_merge_msec_value(conf->resolver_timeout, + prev->resolver_timeout, 30000); + + if (conf->resolver == NULL) { + + if (prev->resolver == NULL) { + + /* + * create dummy resolver in stream {} context + * to inherit it in all servers + */ + + prev->resolver = ngx_resolver_create(cf, NULL, 0); + if (prev->resolver == NULL) { + return NGX_CONF_ERROR; + } + } + + conf->resolver = prev->resolver; + } + + if (conf->handler == NULL) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no handler for server in %s:%ui", + conf->file_name, conf->line); + return NGX_CONF_ERROR; + } + + if (conf->error_log == NULL) { + if (prev->error_log) { + conf->error_log = prev->error_log; + } else { + conf->error_log = &cf->cycle->new_log; + } + } + + ngx_conf_merge_msec_value(conf->proxy_protocol_timeout, + prev->proxy_protocol_timeout, 30000); + + ngx_conf_merge_value(conf->tcp_nodelay, prev->tcp_nodelay, 1); + + ngx_conf_merge_size_value(conf->preread_buffer_size, + prev->preread_buffer_size, 16384); + + ngx_conf_merge_msec_value(conf->preread_timeout, + prev->preread_timeout, 30000); + + return NGX_CONF_OK; +} + + +static char * +ngx_stream_core_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_core_srv_conf_t *cscf = conf; + + return ngx_log_set_log(cf, &cscf->error_log); +} + + +static char * +ngx_stream_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *rv; + void *mconf; + ngx_uint_t m; + ngx_conf_t pcf; + ngx_stream_module_t *module; + ngx_stream_conf_ctx_t *ctx, *stream_ctx; + ngx_stream_core_srv_conf_t *cscf, **cscfp; + ngx_stream_core_main_conf_t *cmcf; + + ctx = ngx_pcalloc(cf->pool, sizeof(ngx_stream_conf_ctx_t)); + if (ctx == NULL) { + return NGX_CONF_ERROR; + } + + stream_ctx = cf->ctx; + ctx->main_conf = stream_ctx->main_conf; + + /* the server{}'s srv_conf */ + + ctx->srv_conf = ngx_pcalloc(cf->pool, + sizeof(void *) * ngx_stream_max_module); + if (ctx->srv_conf == NULL) { + return NGX_CONF_ERROR; + } + + for (m = 0; cf->cycle->modules[m]; m++) { + if (cf->cycle->modules[m]->type != NGX_STREAM_MODULE) { + continue; + } + + module = cf->cycle->modules[m]->ctx; + + if (module->create_srv_conf) { + mconf = module->create_srv_conf(cf); + if (mconf == NULL) { + return NGX_CONF_ERROR; + } + + ctx->srv_conf[cf->cycle->modules[m]->ctx_index] = mconf; + } + } + + /* the server configuration context */ + + cscf = ctx->srv_conf[ngx_stream_core_module.ctx_index]; + cscf->ctx = ctx; + + cmcf = ctx->main_conf[ngx_stream_core_module.ctx_index]; + + cscfp = ngx_array_push(&cmcf->servers); + if (cscfp == NULL) { + return NGX_CONF_ERROR; + } + + *cscfp = cscf; + + + /* parse inside server{} */ + + pcf = *cf; + cf->ctx = ctx; + cf->cmd_type = NGX_STREAM_SRV_CONF; + + rv = ngx_conf_parse(cf, NULL); + + *cf = pcf; + + if (rv == NGX_CONF_OK && !cscf->listen) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"listen\" is defined for server in %s:%ui", + cscf->file_name, cscf->line); + return NGX_CONF_ERROR; + } + + return rv; +} + + +static char * +ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_core_srv_conf_t *cscf = conf; + + ngx_str_t *value; + ngx_url_t u; + ngx_uint_t i, backlog; + ngx_stream_listen_t *ls, *als; + ngx_stream_core_main_conf_t *cmcf; + + cscf->listen = 1; + + value = cf->args->elts; + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = value[1]; + u.listen = 1; + + if (ngx_parse_url(cf->pool, &u) != NGX_OK) { + if (u.err) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%s in \"%V\" of the \"listen\" directive", + u.err, &u.url); + } + + return NGX_CONF_ERROR; + } + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + + ls = ngx_array_push(&cmcf->listen); + if (ls == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(ls, sizeof(ngx_stream_listen_t)); + + ngx_memcpy(&ls->sockaddr.sockaddr, &u.sockaddr, u.socklen); + + ls->socklen = u.socklen; + ls->backlog = NGX_LISTEN_BACKLOG; + ls->type = SOCK_STREAM; + ls->wildcard = u.wildcard; + ls->ctx = cf->ctx; + +#if (NGX_HAVE_INET6) + ls->ipv6only = 1; +#endif + + backlog = 0; + + for (i = 2; i < cf->args->nelts; i++) { + +#if !(NGX_WIN32) + if (ngx_strcmp(value[i].data, "udp") == 0) { + ls->type = SOCK_DGRAM; + continue; + } +#endif + + if (ngx_strcmp(value[i].data, "bind") == 0) { + ls->bind = 1; + continue; + } + + if (ngx_strncmp(value[i].data, "backlog=", 8) == 0) { + ls->backlog = ngx_atoi(value[i].data + 8, value[i].len - 8); + ls->bind = 1; + + if (ls->backlog == NGX_ERROR || ls->backlog == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid backlog \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + backlog = 1; + + continue; + } + + if (ngx_strncmp(value[i].data, "ipv6only=o", 10) == 0) { +#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) + size_t len; + u_char buf[NGX_SOCKADDR_STRLEN]; + + if (ls->sockaddr.sockaddr.sa_family == AF_INET6) { + + if (ngx_strcmp(&value[i].data[10], "n") == 0) { + ls->ipv6only = 1; + + } else if (ngx_strcmp(&value[i].data[10], "ff") == 0) { + ls->ipv6only = 0; + + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid ipv6only flags \"%s\"", + &value[i].data[9]); + return NGX_CONF_ERROR; + } + + ls->bind = 1; + + } else { + len = ngx_sock_ntop(&ls->sockaddr.sockaddr, ls->socklen, buf, + NGX_SOCKADDR_STRLEN, 1); + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "ipv6only is not supported " + "on addr \"%*s\", ignored", len, buf); + } + + continue; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "bind ipv6only is not supported " + "on this platform"); + return NGX_CONF_ERROR; +#endif + } + + if (ngx_strcmp(value[i].data, "reuseport") == 0) { +#if (NGX_HAVE_REUSEPORT) + ls->reuseport = 1; + ls->bind = 1; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "reuseport is not supported " + "on this platform, ignored"); +#endif + continue; + } + + if (ngx_strcmp(value[i].data, "ssl") == 0) { +#if (NGX_STREAM_SSL) + ls->ssl = 1; + continue; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the \"ssl\" parameter requires " + "ngx_stream_ssl_module"); + return NGX_CONF_ERROR; +#endif + } + + if (ngx_strncmp(value[i].data, "so_keepalive=", 13) == 0) { + + if (ngx_strcmp(&value[i].data[13], "on") == 0) { + ls->so_keepalive = 1; + + } else if (ngx_strcmp(&value[i].data[13], "off") == 0) { + ls->so_keepalive = 2; + + } else { + +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + u_char *p, *end; + ngx_str_t s; + + end = value[i].data + value[i].len; + s.data = value[i].data + 13; + + p = ngx_strlchr(s.data, end, ':'); + if (p == NULL) { + p = end; + } + + if (p > s.data) { + s.len = p - s.data; + + ls->tcp_keepidle = ngx_parse_time(&s, 1); + if (ls->tcp_keepidle == (time_t) NGX_ERROR) { + goto invalid_so_keepalive; + } + } + + s.data = (p < end) ? (p + 1) : end; + + p = ngx_strlchr(s.data, end, ':'); + if (p == NULL) { + p = end; + } + + if (p > s.data) { + s.len = p - s.data; + + ls->tcp_keepintvl = ngx_parse_time(&s, 1); + if (ls->tcp_keepintvl == (time_t) NGX_ERROR) { + goto invalid_so_keepalive; + } + } + + s.data = (p < end) ? (p + 1) : end; + + if (s.data < end) { + s.len = end - s.data; + + ls->tcp_keepcnt = ngx_atoi(s.data, s.len); + if (ls->tcp_keepcnt == NGX_ERROR) { + goto invalid_so_keepalive; + } + } + + if (ls->tcp_keepidle == 0 && ls->tcp_keepintvl == 0 + && ls->tcp_keepcnt == 0) + { + goto invalid_so_keepalive; + } + + ls->so_keepalive = 1; + +#else + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the \"so_keepalive\" parameter accepts " + "only \"on\" or \"off\" on this platform"); + return NGX_CONF_ERROR; + +#endif + } + + ls->bind = 1; + + continue; + +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + invalid_so_keepalive: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid so_keepalive value: \"%s\"", + &value[i].data[13]); + return NGX_CONF_ERROR; +#endif + } + + if (ngx_strcmp(value[i].data, "proxy_protocol") == 0) { + ls->proxy_protocol = 1; + continue; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the invalid \"%V\" parameter", &value[i]); + return NGX_CONF_ERROR; + } + + if (ls->type == SOCK_DGRAM) { + if (backlog) { + return "\"backlog\" parameter is incompatible with \"udp\""; + } + +#if (NGX_STREAM_SSL) + if (ls->ssl) { + return "\"ssl\" parameter is incompatible with \"udp\""; + } +#endif + + if (ls->so_keepalive) { + return "\"so_keepalive\" parameter is incompatible with \"udp\""; + } + + if (ls->proxy_protocol) { + return "\"proxy_protocol\" parameter is incompatible with \"udp\""; + } + } + + als = cmcf->listen.elts; + + for (i = 0; i < cmcf->listen.nelts - 1; i++) { + if (ls->type != als[i].type) { + continue; + } + + if (ngx_cmp_sockaddr(&als[i].sockaddr.sockaddr, als[i].socklen, + &ls->sockaddr.sockaddr, ls->socklen, 1) + != NGX_OK) + { + continue; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "duplicate \"%V\" address and port pair", &u.url); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_stream_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_core_srv_conf_t *cscf = conf; + + ngx_str_t *value; + + if (cscf->resolver) { + return "is duplicate"; + } + + value = cf->args->elts; + + cscf->resolver = ngx_resolver_create(cf, &value[1], cf->args->nelts - 1); + if (cscf->resolver == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} diff --git a/app/nginx/src/stream/ngx_stream_geo_module.c b/app/nginx/src/stream/ngx_stream_geo_module.c new file mode 100644 index 0000000..2204546 --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_geo_module.c @@ -0,0 +1,1586 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef struct { + ngx_stream_variable_value_t *value; + u_short start; + u_short end; +} ngx_stream_geo_range_t; + + +typedef struct { + ngx_radix_tree_t *tree; +#if (NGX_HAVE_INET6) + ngx_radix_tree_t *tree6; +#endif +} ngx_stream_geo_trees_t; + + +typedef struct { + ngx_stream_geo_range_t **low; + ngx_stream_variable_value_t *default_value; +} ngx_stream_geo_high_ranges_t; + + +typedef struct { + ngx_str_node_t sn; + ngx_stream_variable_value_t *value; + size_t offset; +} ngx_stream_geo_variable_value_node_t; + + +typedef struct { + ngx_stream_variable_value_t *value; + ngx_str_t *net; + ngx_stream_geo_high_ranges_t high; + ngx_radix_tree_t *tree; +#if (NGX_HAVE_INET6) + ngx_radix_tree_t *tree6; +#endif + ngx_rbtree_t rbtree; + ngx_rbtree_node_t sentinel; + ngx_pool_t *pool; + ngx_pool_t *temp_pool; + + size_t data_size; + + ngx_str_t include_name; + ngx_uint_t includes; + ngx_uint_t entries; + + unsigned ranges:1; + unsigned outside_entries:1; + unsigned allow_binary_include:1; + unsigned binary_include:1; +} ngx_stream_geo_conf_ctx_t; + + +typedef struct { + union { + ngx_stream_geo_trees_t trees; + ngx_stream_geo_high_ranges_t high; + } u; + + ngx_int_t index; +} ngx_stream_geo_ctx_t; + + +static ngx_int_t ngx_stream_geo_addr(ngx_stream_session_t *s, + ngx_stream_geo_ctx_t *ctx, ngx_addr_t *addr); + +static char *ngx_stream_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_stream_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf); +static char *ngx_stream_geo_range(ngx_conf_t *cf, + ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *value); +static char *ngx_stream_geo_add_range(ngx_conf_t *cf, + ngx_stream_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end); +static ngx_uint_t ngx_stream_geo_delete_range(ngx_conf_t *cf, + ngx_stream_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end); +static char *ngx_stream_geo_cidr(ngx_conf_t *cf, + ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *value); +static char *ngx_stream_geo_cidr_add(ngx_conf_t *cf, + ngx_stream_geo_conf_ctx_t *ctx, ngx_cidr_t *cidr, ngx_str_t *value, + ngx_str_t *net); +static ngx_stream_variable_value_t *ngx_stream_geo_value(ngx_conf_t *cf, + ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *value); +static ngx_int_t ngx_stream_geo_cidr_value(ngx_conf_t *cf, ngx_str_t *net, + ngx_cidr_t *cidr); +static char *ngx_stream_geo_include(ngx_conf_t *cf, + ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *name); +static ngx_int_t ngx_stream_geo_include_binary_base(ngx_conf_t *cf, + ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *name); +static void ngx_stream_geo_create_binary_base(ngx_stream_geo_conf_ctx_t *ctx); +static u_char *ngx_stream_geo_copy_values(u_char *base, u_char *p, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); + + +static ngx_command_t ngx_stream_geo_commands[] = { + + { ngx_string("geo"), + NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12, + ngx_stream_geo_block, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_geo_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_geo_module = { + NGX_MODULE_V1, + &ngx_stream_geo_module_ctx, /* module context */ + ngx_stream_geo_commands, /* module directives */ + NGX_STREAM_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 +}; + + +typedef struct { + u_char GEORNG[6]; + u_char version; + u_char ptr_size; + uint32_t endianness; + uint32_t crc32; +} ngx_stream_geo_header_t; + + +static ngx_stream_geo_header_t ngx_stream_geo_header = { + { 'G', 'E', 'O', 'R', 'N', 'G' }, 0, sizeof(void *), 0x12345678, 0 +}; + + +/* geo range is AF_INET only */ + +static ngx_int_t +ngx_stream_geo_cidr_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + ngx_stream_geo_ctx_t *ctx = (ngx_stream_geo_ctx_t *) data; + + in_addr_t inaddr; + ngx_addr_t addr; + struct sockaddr_in *sin; + ngx_stream_variable_value_t *vv; +#if (NGX_HAVE_INET6) + u_char *p; + struct in6_addr *inaddr6; +#endif + + if (ngx_stream_geo_addr(s, ctx, &addr) != NGX_OK) { + vv = (ngx_stream_variable_value_t *) + ngx_radix32tree_find(ctx->u.trees.tree, INADDR_NONE); + goto done; + } + + switch (addr.sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr; + p = inaddr6->s6_addr; + + if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { + inaddr = p[12] << 24; + inaddr += p[13] << 16; + inaddr += p[14] << 8; + inaddr += p[15]; + + vv = (ngx_stream_variable_value_t *) + ngx_radix32tree_find(ctx->u.trees.tree, inaddr); + + } else { + vv = (ngx_stream_variable_value_t *) + ngx_radix128tree_find(ctx->u.trees.tree6, p); + } + + break; +#endif + + default: /* AF_INET */ + sin = (struct sockaddr_in *) addr.sockaddr; + inaddr = ntohl(sin->sin_addr.s_addr); + + vv = (ngx_stream_variable_value_t *) + ngx_radix32tree_find(ctx->u.trees.tree, inaddr); + + break; + } + +done: + + *v = *vv; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream geo: %v", v); + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_geo_range_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + ngx_stream_geo_ctx_t *ctx = (ngx_stream_geo_ctx_t *) data; + + in_addr_t inaddr; + ngx_addr_t addr; + ngx_uint_t n; + struct sockaddr_in *sin; + ngx_stream_geo_range_t *range; +#if (NGX_HAVE_INET6) + u_char *p; + struct in6_addr *inaddr6; +#endif + + *v = *ctx->u.high.default_value; + + if (ngx_stream_geo_addr(s, ctx, &addr) == NGX_OK) { + + switch (addr.sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr; + + if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { + p = inaddr6->s6_addr; + + inaddr = p[12] << 24; + inaddr += p[13] << 16; + inaddr += p[14] << 8; + inaddr += p[15]; + + } else { + inaddr = INADDR_NONE; + } + + break; +#endif + + default: /* AF_INET */ + sin = (struct sockaddr_in *) addr.sockaddr; + inaddr = ntohl(sin->sin_addr.s_addr); + break; + } + + } else { + inaddr = INADDR_NONE; + } + + if (ctx->u.high.low) { + range = ctx->u.high.low[inaddr >> 16]; + + if (range) { + n = inaddr & 0xffff; + do { + if (n >= (ngx_uint_t) range->start + && n <= (ngx_uint_t) range->end) + { + *v = *range->value; + break; + } + } while ((++range)->value); + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream geo: %v", v); + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_geo_addr(ngx_stream_session_t *s, ngx_stream_geo_ctx_t *ctx, + ngx_addr_t *addr) +{ + ngx_stream_variable_value_t *v; + + if (ctx->index == -1) { + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream geo started: %V", &s->connection->addr_text); + + addr->sockaddr = s->connection->sockaddr; + addr->socklen = s->connection->socklen; + /* addr->name = s->connection->addr_text; */ + + return NGX_OK; + } + + v = ngx_stream_get_flushed_variable(s, ctx->index); + + if (v == NULL || v->not_found) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream geo not found"); + + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream geo started: %v", v); + + if (ngx_parse_addr(s->connection->pool, addr, v->data, v->len) == NGX_OK) { + return NGX_OK; + } + + return NGX_ERROR; +} + + +static char * +ngx_stream_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *rv; + size_t len; + ngx_str_t *value, name; + ngx_uint_t i; + ngx_conf_t save; + ngx_pool_t *pool; + ngx_array_t *a; + ngx_stream_variable_t *var; + ngx_stream_geo_ctx_t *geo; + ngx_stream_geo_conf_ctx_t ctx; +#if (NGX_HAVE_INET6) + static struct in6_addr zero; +#endif + + value = cf->args->elts; + + geo = ngx_palloc(cf->pool, sizeof(ngx_stream_geo_ctx_t)); + if (geo == NULL) { + return NGX_CONF_ERROR; + } + + name = value[1]; + + if (name.data[0] != '$') { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid variable name \"%V\"", &name); + return NGX_CONF_ERROR; + } + + name.len--; + name.data++; + + if (cf->args->nelts == 3) { + + geo->index = ngx_stream_get_variable_index(cf, &name); + if (geo->index == NGX_ERROR) { + return NGX_CONF_ERROR; + } + + name = value[2]; + + if (name.data[0] != '$') { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid variable name \"%V\"", &name); + return NGX_CONF_ERROR; + } + + name.len--; + name.data++; + + } else { + geo->index = -1; + } + + var = ngx_stream_add_variable(cf, &name, NGX_STREAM_VAR_CHANGEABLE); + if (var == NULL) { + return NGX_CONF_ERROR; + } + + pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); + if (pool == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(&ctx, sizeof(ngx_stream_geo_conf_ctx_t)); + + ctx.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); + if (ctx.temp_pool == NULL) { + return NGX_CONF_ERROR; + } + + ngx_rbtree_init(&ctx.rbtree, &ctx.sentinel, ngx_str_rbtree_insert_value); + + ctx.pool = cf->pool; + ctx.data_size = sizeof(ngx_stream_geo_header_t) + + sizeof(ngx_stream_variable_value_t) + + 0x10000 * sizeof(ngx_stream_geo_range_t *); + ctx.allow_binary_include = 1; + + save = *cf; + cf->pool = pool; + cf->ctx = &ctx; + cf->handler = ngx_stream_geo; + cf->handler_conf = conf; + + rv = ngx_conf_parse(cf, NULL); + + *cf = save; + + if (ctx.ranges) { + + if (ctx.high.low && !ctx.binary_include) { + for (i = 0; i < 0x10000; i++) { + a = (ngx_array_t *) ctx.high.low[i]; + + if (a == NULL) { + continue; + } + + if (a->nelts == 0) { + ctx.high.low[i] = NULL; + continue; + } + + len = a->nelts * sizeof(ngx_stream_geo_range_t); + + ctx.high.low[i] = ngx_palloc(cf->pool, len + sizeof(void *)); + if (ctx.high.low[i] == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memcpy(ctx.high.low[i], a->elts, len); + ctx.high.low[i][a->nelts].value = NULL; + ctx.data_size += len + sizeof(void *); + } + + if (ctx.allow_binary_include + && !ctx.outside_entries + && ctx.entries > 100000 + && ctx.includes == 1) + { + ngx_stream_geo_create_binary_base(&ctx); + } + } + + if (ctx.high.default_value == NULL) { + ctx.high.default_value = &ngx_stream_variable_null_value; + } + + geo->u.high = ctx.high; + + var->get_handler = ngx_stream_geo_range_variable; + var->data = (uintptr_t) geo; + + ngx_destroy_pool(ctx.temp_pool); + ngx_destroy_pool(pool); + + } else { + if (ctx.tree == NULL) { + ctx.tree = ngx_radix_tree_create(cf->pool, -1); + if (ctx.tree == NULL) { + return NGX_CONF_ERROR; + } + } + + geo->u.trees.tree = ctx.tree; + +#if (NGX_HAVE_INET6) + if (ctx.tree6 == NULL) { + ctx.tree6 = ngx_radix_tree_create(cf->pool, -1); + if (ctx.tree6 == NULL) { + return NGX_CONF_ERROR; + } + } + + geo->u.trees.tree6 = ctx.tree6; +#endif + + var->get_handler = ngx_stream_geo_cidr_variable; + var->data = (uintptr_t) geo; + + ngx_destroy_pool(ctx.temp_pool); + ngx_destroy_pool(pool); + + if (ngx_radix32tree_insert(ctx.tree, 0, 0, + (uintptr_t) &ngx_stream_variable_null_value) + == NGX_ERROR) + { + return NGX_CONF_ERROR; + } + + /* NGX_BUSY is okay (default was set explicitly) */ + +#if (NGX_HAVE_INET6) + if (ngx_radix128tree_insert(ctx.tree6, zero.s6_addr, zero.s6_addr, + (uintptr_t) &ngx_stream_variable_null_value) + == NGX_ERROR) + { + return NGX_CONF_ERROR; + } +#endif + } + + return rv; +} + + +static char * +ngx_stream_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) +{ + char *rv; + ngx_str_t *value; + ngx_stream_geo_conf_ctx_t *ctx; + + ctx = cf->ctx; + + value = cf->args->elts; + + if (cf->args->nelts == 1) { + + if (ngx_strcmp(value[0].data, "ranges") == 0) { + + if (ctx->tree +#if (NGX_HAVE_INET6) + || ctx->tree6 +#endif + ) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the \"ranges\" directive must be " + "the first directive inside \"geo\" block"); + goto failed; + } + + ctx->ranges = 1; + + rv = NGX_CONF_OK; + + goto done; + } + } + + if (cf->args->nelts != 2) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid number of the geo parameters"); + goto failed; + } + + if (ngx_strcmp(value[0].data, "include") == 0) { + + rv = ngx_stream_geo_include(cf, ctx, &value[1]); + + goto done; + } + + if (ctx->ranges) { + rv = ngx_stream_geo_range(cf, ctx, value); + + } else { + rv = ngx_stream_geo_cidr(cf, ctx, value); + } + +done: + + ngx_reset_pool(cf->pool); + + return rv; + +failed: + + ngx_reset_pool(cf->pool); + + return NGX_CONF_ERROR; +} + + +static char * +ngx_stream_geo_range(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx, + ngx_str_t *value) +{ + u_char *p, *last; + in_addr_t start, end; + ngx_str_t *net; + ngx_uint_t del; + + if (ngx_strcmp(value[0].data, "default") == 0) { + + if (ctx->high.default_value) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "duplicate default geo range value: \"%V\", old value: \"%v\"", + &value[1], ctx->high.default_value); + } + + ctx->high.default_value = ngx_stream_geo_value(cf, ctx, &value[1]); + if (ctx->high.default_value == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; + } + + if (ctx->binary_include) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "binary geo range base \"%s\" cannot be mixed with usual entries", + ctx->include_name.data); + return NGX_CONF_ERROR; + } + + if (ctx->high.low == NULL) { + ctx->high.low = ngx_pcalloc(ctx->pool, + 0x10000 * sizeof(ngx_stream_geo_range_t *)); + if (ctx->high.low == NULL) { + return NGX_CONF_ERROR; + } + } + + ctx->entries++; + ctx->outside_entries = 1; + + if (ngx_strcmp(value[0].data, "delete") == 0) { + net = &value[1]; + del = 1; + + } else { + net = &value[0]; + del = 0; + } + + last = net->data + net->len; + + p = ngx_strlchr(net->data, last, '-'); + + if (p == NULL) { + goto invalid; + } + + start = ngx_inet_addr(net->data, p - net->data); + + if (start == INADDR_NONE) { + goto invalid; + } + + start = ntohl(start); + + p++; + + end = ngx_inet_addr(p, last - p); + + if (end == INADDR_NONE) { + goto invalid; + } + + end = ntohl(end); + + if (start > end) { + goto invalid; + } + + if (del) { + if (ngx_stream_geo_delete_range(cf, ctx, start, end)) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "no address range \"%V\" to delete", net); + } + + return NGX_CONF_OK; + } + + ctx->value = ngx_stream_geo_value(cf, ctx, &value[1]); + + if (ctx->value == NULL) { + return NGX_CONF_ERROR; + } + + ctx->net = net; + + return ngx_stream_geo_add_range(cf, ctx, start, end); + +invalid: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid range \"%V\"", net); + + return NGX_CONF_ERROR; +} + + +/* the add procedure is optimized to add a growing up sequence */ + +static char * +ngx_stream_geo_add_range(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx, + in_addr_t start, in_addr_t end) +{ + in_addr_t n; + ngx_uint_t h, i, s, e; + ngx_array_t *a; + ngx_stream_geo_range_t *range; + + for (n = start; n <= end; n = (n + 0x10000) & 0xffff0000) { + + h = n >> 16; + + if (n == start) { + s = n & 0xffff; + } else { + s = 0; + } + + if ((n | 0xffff) > end) { + e = end & 0xffff; + + } else { + e = 0xffff; + } + + a = (ngx_array_t *) ctx->high.low[h]; + + if (a == NULL) { + a = ngx_array_create(ctx->temp_pool, 64, + sizeof(ngx_stream_geo_range_t)); + if (a == NULL) { + return NGX_CONF_ERROR; + } + + ctx->high.low[h] = (ngx_stream_geo_range_t *) a; + } + + i = a->nelts; + range = a->elts; + + while (i) { + + i--; + + if (e < (ngx_uint_t) range[i].start) { + continue; + } + + if (s > (ngx_uint_t) range[i].end) { + + /* add after the range */ + + range = ngx_array_push(a); + if (range == NULL) { + return NGX_CONF_ERROR; + } + + range = a->elts; + + ngx_memmove(&range[i + 2], &range[i + 1], + (a->nelts - 2 - i) * sizeof(ngx_stream_geo_range_t)); + + range[i + 1].start = (u_short) s; + range[i + 1].end = (u_short) e; + range[i + 1].value = ctx->value; + + goto next; + } + + if (s == (ngx_uint_t) range[i].start + && e == (ngx_uint_t) range[i].end) + { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "duplicate range \"%V\", value: \"%v\", old value: \"%v\"", + ctx->net, ctx->value, range[i].value); + + range[i].value = ctx->value; + + goto next; + } + + if (s > (ngx_uint_t) range[i].start + && e < (ngx_uint_t) range[i].end) + { + /* split the range and insert the new one */ + + range = ngx_array_push(a); + if (range == NULL) { + return NGX_CONF_ERROR; + } + + range = ngx_array_push(a); + if (range == NULL) { + return NGX_CONF_ERROR; + } + + range = a->elts; + + ngx_memmove(&range[i + 3], &range[i + 1], + (a->nelts - 3 - i) * sizeof(ngx_stream_geo_range_t)); + + range[i + 2].start = (u_short) (e + 1); + range[i + 2].end = range[i].end; + range[i + 2].value = range[i].value; + + range[i + 1].start = (u_short) s; + range[i + 1].end = (u_short) e; + range[i + 1].value = ctx->value; + + range[i].end = (u_short) (s - 1); + + goto next; + } + + if (s == (ngx_uint_t) range[i].start + && e < (ngx_uint_t) range[i].end) + { + /* shift the range start and insert the new range */ + + range = ngx_array_push(a); + if (range == NULL) { + return NGX_CONF_ERROR; + } + + range = a->elts; + + ngx_memmove(&range[i + 1], &range[i], + (a->nelts - 1 - i) * sizeof(ngx_stream_geo_range_t)); + + range[i + 1].start = (u_short) (e + 1); + + range[i].start = (u_short) s; + range[i].end = (u_short) e; + range[i].value = ctx->value; + + goto next; + } + + if (s > (ngx_uint_t) range[i].start + && e == (ngx_uint_t) range[i].end) + { + /* shift the range end and insert the new range */ + + range = ngx_array_push(a); + if (range == NULL) { + return NGX_CONF_ERROR; + } + + range = a->elts; + + ngx_memmove(&range[i + 2], &range[i + 1], + (a->nelts - 2 - i) * sizeof(ngx_stream_geo_range_t)); + + range[i + 1].start = (u_short) s; + range[i + 1].end = (u_short) e; + range[i + 1].value = ctx->value; + + range[i].end = (u_short) (s - 1); + + goto next; + } + + s = (ngx_uint_t) range[i].start; + e = (ngx_uint_t) range[i].end; + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "range \"%V\" overlaps \"%d.%d.%d.%d-%d.%d.%d.%d\"", + ctx->net, + h >> 8, h & 0xff, s >> 8, s & 0xff, + h >> 8, h & 0xff, e >> 8, e & 0xff); + + return NGX_CONF_ERROR; + } + + /* add the first range */ + + range = ngx_array_push(a); + if (range == NULL) { + return NGX_CONF_ERROR; + } + + range = a->elts; + + ngx_memmove(&range[1], &range[0], + (a->nelts - 1) * sizeof(ngx_stream_geo_range_t)); + + range[0].start = (u_short) s; + range[0].end = (u_short) e; + range[0].value = ctx->value; + + next: + + if (h == 0xffff) { + break; + } + } + + return NGX_CONF_OK; +} + + +static ngx_uint_t +ngx_stream_geo_delete_range(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx, + in_addr_t start, in_addr_t end) +{ + in_addr_t n; + ngx_uint_t h, i, s, e, warn; + ngx_array_t *a; + ngx_stream_geo_range_t *range; + + warn = 0; + + for (n = start; n <= end; n = (n + 0x10000) & 0xffff0000) { + + h = n >> 16; + + if (n == start) { + s = n & 0xffff; + } else { + s = 0; + } + + if ((n | 0xffff) > end) { + e = end & 0xffff; + + } else { + e = 0xffff; + } + + a = (ngx_array_t *) ctx->high.low[h]; + + if (a == NULL || a->nelts == 0) { + warn = 1; + goto next; + } + + range = a->elts; + for (i = 0; i < a->nelts; i++) { + + if (s == (ngx_uint_t) range[i].start + && e == (ngx_uint_t) range[i].end) + { + ngx_memmove(&range[i], &range[i + 1], + (a->nelts - 1 - i) * sizeof(ngx_stream_geo_range_t)); + + a->nelts--; + + break; + } + + if (i == a->nelts - 1) { + warn = 1; + } + } + + next: + + if (h == 0xffff) { + break; + } + } + + return warn; +} + + +static char * +ngx_stream_geo_cidr(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx, + ngx_str_t *value) +{ + char *rv; + ngx_int_t rc, del; + ngx_str_t *net; + ngx_cidr_t cidr; + + if (ctx->tree == NULL) { + ctx->tree = ngx_radix_tree_create(ctx->pool, -1); + if (ctx->tree == NULL) { + return NGX_CONF_ERROR; + } + } + +#if (NGX_HAVE_INET6) + if (ctx->tree6 == NULL) { + ctx->tree6 = ngx_radix_tree_create(ctx->pool, -1); + if (ctx->tree6 == NULL) { + return NGX_CONF_ERROR; + } + } +#endif + + if (ngx_strcmp(value[0].data, "default") == 0) { + cidr.family = AF_INET; + cidr.u.in.addr = 0; + cidr.u.in.mask = 0; + + rv = ngx_stream_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]); + + if (rv != NGX_CONF_OK) { + return rv; + } + +#if (NGX_HAVE_INET6) + cidr.family = AF_INET6; + ngx_memzero(&cidr.u.in6, sizeof(ngx_in6_cidr_t)); + + rv = ngx_stream_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]); + + if (rv != NGX_CONF_OK) { + return rv; + } +#endif + + return NGX_CONF_OK; + } + + if (ngx_strcmp(value[0].data, "delete") == 0) { + net = &value[1]; + del = 1; + + } else { + net = &value[0]; + del = 0; + } + + if (ngx_stream_geo_cidr_value(cf, net, &cidr) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (cidr.family == AF_INET) { + cidr.u.in.addr = ntohl(cidr.u.in.addr); + cidr.u.in.mask = ntohl(cidr.u.in.mask); + } + + if (del) { + switch (cidr.family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + rc = ngx_radix128tree_delete(ctx->tree6, + cidr.u.in6.addr.s6_addr, + cidr.u.in6.mask.s6_addr); + break; +#endif + + default: /* AF_INET */ + rc = ngx_radix32tree_delete(ctx->tree, cidr.u.in.addr, + cidr.u.in.mask); + break; + } + + if (rc != NGX_OK) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "no network \"%V\" to delete", net); + } + + return NGX_CONF_OK; + } + + return ngx_stream_geo_cidr_add(cf, ctx, &cidr, &value[1], net); +} + + +static char * +ngx_stream_geo_cidr_add(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx, + ngx_cidr_t *cidr, ngx_str_t *value, ngx_str_t *net) +{ + ngx_int_t rc; + ngx_stream_variable_value_t *val, *old; + + val = ngx_stream_geo_value(cf, ctx, value); + + if (val == NULL) { + return NGX_CONF_ERROR; + } + + switch (cidr->family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + rc = ngx_radix128tree_insert(ctx->tree6, cidr->u.in6.addr.s6_addr, + cidr->u.in6.mask.s6_addr, + (uintptr_t) val); + + if (rc == NGX_OK) { + return NGX_CONF_OK; + } + + if (rc == NGX_ERROR) { + return NGX_CONF_ERROR; + } + + /* rc == NGX_BUSY */ + + old = (ngx_stream_variable_value_t *) + ngx_radix128tree_find(ctx->tree6, + cidr->u.in6.addr.s6_addr); + + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "duplicate network \"%V\", value: \"%v\", old value: \"%v\"", + net, val, old); + + rc = ngx_radix128tree_delete(ctx->tree6, + cidr->u.in6.addr.s6_addr, + cidr->u.in6.mask.s6_addr); + + if (rc == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree"); + return NGX_CONF_ERROR; + } + + rc = ngx_radix128tree_insert(ctx->tree6, cidr->u.in6.addr.s6_addr, + cidr->u.in6.mask.s6_addr, + (uintptr_t) val); + + break; +#endif + + default: /* AF_INET */ + rc = ngx_radix32tree_insert(ctx->tree, cidr->u.in.addr, + cidr->u.in.mask, (uintptr_t) val); + + if (rc == NGX_OK) { + return NGX_CONF_OK; + } + + if (rc == NGX_ERROR) { + return NGX_CONF_ERROR; + } + + /* rc == NGX_BUSY */ + + old = (ngx_stream_variable_value_t *) + ngx_radix32tree_find(ctx->tree, cidr->u.in.addr); + + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "duplicate network \"%V\", value: \"%v\", old value: \"%v\"", + net, val, old); + + rc = ngx_radix32tree_delete(ctx->tree, + cidr->u.in.addr, cidr->u.in.mask); + + if (rc == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree"); + return NGX_CONF_ERROR; + } + + rc = ngx_radix32tree_insert(ctx->tree, cidr->u.in.addr, + cidr->u.in.mask, (uintptr_t) val); + + break; + } + + if (rc == NGX_OK) { + return NGX_CONF_OK; + } + + return NGX_CONF_ERROR; +} + + +static ngx_stream_variable_value_t * +ngx_stream_geo_value(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx, + ngx_str_t *value) +{ + uint32_t hash; + ngx_stream_variable_value_t *val; + ngx_stream_geo_variable_value_node_t *gvvn; + + hash = ngx_crc32_long(value->data, value->len); + + gvvn = (ngx_stream_geo_variable_value_node_t *) + ngx_str_rbtree_lookup(&ctx->rbtree, value, hash); + + if (gvvn) { + return gvvn->value; + } + + val = ngx_palloc(ctx->pool, sizeof(ngx_stream_variable_value_t)); + if (val == NULL) { + return NULL; + } + + val->len = value->len; + val->data = ngx_pstrdup(ctx->pool, value); + if (val->data == NULL) { + return NULL; + } + + val->valid = 1; + val->no_cacheable = 0; + val->not_found = 0; + + gvvn = ngx_palloc(ctx->temp_pool, + sizeof(ngx_stream_geo_variable_value_node_t)); + if (gvvn == NULL) { + return NULL; + } + + gvvn->sn.node.key = hash; + gvvn->sn.str.len = val->len; + gvvn->sn.str.data = val->data; + gvvn->value = val; + gvvn->offset = 0; + + ngx_rbtree_insert(&ctx->rbtree, &gvvn->sn.node); + + ctx->data_size += ngx_align(sizeof(ngx_stream_variable_value_t) + + value->len, sizeof(void *)); + + return val; +} + + +static ngx_int_t +ngx_stream_geo_cidr_value(ngx_conf_t *cf, ngx_str_t *net, ngx_cidr_t *cidr) +{ + ngx_int_t rc; + + if (ngx_strcmp(net->data, "255.255.255.255") == 0) { + cidr->family = AF_INET; + cidr->u.in.addr = 0xffffffff; + cidr->u.in.mask = 0xffffffff; + + return NGX_OK; + } + + rc = ngx_ptocidr(net, cidr); + + if (rc == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid network \"%V\"", net); + return NGX_ERROR; + } + + if (rc == NGX_DONE) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "low address bits of %V are meaningless", net); + } + + return NGX_OK; +} + + +static char * +ngx_stream_geo_include(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx, + ngx_str_t *name) +{ + char *rv; + ngx_str_t file; + + file.len = name->len + 4; + file.data = ngx_pnalloc(ctx->temp_pool, name->len + 5); + if (file.data == NULL) { + return NGX_CONF_ERROR; + } + + ngx_sprintf(file.data, "%V.bin%Z", name); + + if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (ctx->ranges) { + ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data); + + switch (ngx_stream_geo_include_binary_base(cf, ctx, &file)) { + case NGX_OK: + return NGX_CONF_OK; + case NGX_ERROR: + return NGX_CONF_ERROR; + default: + break; + } + } + + file.len -= 4; + file.data[file.len] = '\0'; + + ctx->include_name = file; + + if (ctx->outside_entries) { + ctx->allow_binary_include = 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data); + + rv = ngx_conf_parse(cf, &file); + + ctx->includes++; + ctx->outside_entries = 0; + + return rv; +} + + +static ngx_int_t +ngx_stream_geo_include_binary_base(ngx_conf_t *cf, + ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *name) +{ + u_char *base, ch; + time_t mtime; + size_t size, len; + ssize_t n; + uint32_t crc32; + ngx_err_t err; + ngx_int_t rc; + ngx_uint_t i; + ngx_file_t file; + ngx_file_info_t fi; + ngx_stream_geo_range_t *range, **ranges; + ngx_stream_geo_header_t *header; + ngx_stream_variable_value_t *vv; + + ngx_memzero(&file, sizeof(ngx_file_t)); + file.name = *name; + file.log = cf->log; + + file.fd = ngx_open_file(name->data, NGX_FILE_RDONLY, 0, 0); + if (file.fd == NGX_INVALID_FILE) { + err = ngx_errno; + if (err != NGX_ENOENT) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, err, + ngx_open_file_n " \"%s\" failed", name->data); + } + return NGX_DECLINED; + } + + if (ctx->outside_entries) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "binary geo range base \"%s\" cannot be mixed with usual entries", + name->data); + rc = NGX_ERROR; + goto done; + } + + if (ctx->binary_include) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "second binary geo range base \"%s\" cannot be mixed with \"%s\"", + name->data, ctx->include_name.data); + rc = NGX_ERROR; + goto done; + } + + if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_fd_info_n " \"%s\" failed", name->data); + goto failed; + } + + size = (size_t) ngx_file_size(&fi); + mtime = ngx_file_mtime(&fi); + + ch = name->data[name->len - 4]; + name->data[name->len - 4] = '\0'; + + if (ngx_file_info(name->data, &fi) == NGX_FILE_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_file_info_n " \"%s\" failed", name->data); + goto failed; + } + + name->data[name->len - 4] = ch; + + if (mtime < ngx_file_mtime(&fi)) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "stale binary geo range base \"%s\"", name->data); + goto failed; + } + + base = ngx_palloc(ctx->pool, size); + if (base == NULL) { + goto failed; + } + + n = ngx_read_file(&file, base, size, 0); + + if (n == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_read_file_n " \"%s\" failed", name->data); + goto failed; + } + + if ((size_t) n != size) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, + ngx_read_file_n " \"%s\" returned only %z bytes instead of %z", + name->data, n, size); + goto failed; + } + + header = (ngx_stream_geo_header_t *) base; + + if (size < 16 || ngx_memcmp(&ngx_stream_geo_header, header, 12) != 0) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "incompatible binary geo range base \"%s\"", name->data); + goto failed; + } + + ngx_crc32_init(crc32); + + vv = (ngx_stream_variable_value_t *) + (base + sizeof(ngx_stream_geo_header_t)); + + while (vv->data) { + len = ngx_align(sizeof(ngx_stream_variable_value_t) + vv->len, + sizeof(void *)); + ngx_crc32_update(&crc32, (u_char *) vv, len); + vv->data += (size_t) base; + vv = (ngx_stream_variable_value_t *) ((u_char *) vv + len); + } + ngx_crc32_update(&crc32, (u_char *) vv, + sizeof(ngx_stream_variable_value_t)); + vv++; + + ranges = (ngx_stream_geo_range_t **) vv; + + for (i = 0; i < 0x10000; i++) { + ngx_crc32_update(&crc32, (u_char *) &ranges[i], sizeof(void *)); + if (ranges[i]) { + ranges[i] = (ngx_stream_geo_range_t *) + ((u_char *) ranges[i] + (size_t) base); + } + } + + range = (ngx_stream_geo_range_t *) &ranges[0x10000]; + + while ((u_char *) range < base + size) { + while (range->value) { + ngx_crc32_update(&crc32, (u_char *) range, + sizeof(ngx_stream_geo_range_t)); + range->value = (ngx_stream_variable_value_t *) + ((u_char *) range->value + (size_t) base); + range++; + } + ngx_crc32_update(&crc32, (u_char *) range, sizeof(void *)); + range = (ngx_stream_geo_range_t *) ((u_char *) range + sizeof(void *)); + } + + ngx_crc32_final(crc32); + + if (crc32 != header->crc32) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "CRC32 mismatch in binary geo range base \"%s\"", name->data); + goto failed; + } + + ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0, + "using binary geo range base \"%s\"", name->data); + + ctx->include_name = *name; + ctx->binary_include = 1; + ctx->high.low = ranges; + rc = NGX_OK; + + goto done; + +failed: + + rc = NGX_DECLINED; + +done: + + if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, + ngx_close_file_n " \"%s\" failed", name->data); + } + + return rc; +} + + +static void +ngx_stream_geo_create_binary_base(ngx_stream_geo_conf_ctx_t *ctx) +{ + u_char *p; + uint32_t hash; + ngx_str_t s; + ngx_uint_t i; + ngx_file_mapping_t fm; + ngx_stream_geo_range_t *r, *range, **ranges; + ngx_stream_geo_header_t *header; + ngx_stream_geo_variable_value_node_t *gvvn; + + fm.name = ngx_pnalloc(ctx->temp_pool, ctx->include_name.len + 5); + if (fm.name == NULL) { + return; + } + + ngx_sprintf(fm.name, "%V.bin%Z", &ctx->include_name); + + fm.size = ctx->data_size; + fm.log = ctx->pool->log; + + ngx_log_error(NGX_LOG_NOTICE, fm.log, 0, + "creating binary geo range base \"%s\"", fm.name); + + if (ngx_create_file_mapping(&fm) != NGX_OK) { + return; + } + + p = ngx_cpymem(fm.addr, &ngx_stream_geo_header, + sizeof(ngx_stream_geo_header_t)); + + p = ngx_stream_geo_copy_values(fm.addr, p, ctx->rbtree.root, + ctx->rbtree.sentinel); + + p += sizeof(ngx_stream_variable_value_t); + + ranges = (ngx_stream_geo_range_t **) p; + + p += 0x10000 * sizeof(ngx_stream_geo_range_t *); + + for (i = 0; i < 0x10000; i++) { + r = ctx->high.low[i]; + if (r == NULL) { + continue; + } + + range = (ngx_stream_geo_range_t *) p; + ranges[i] = (ngx_stream_geo_range_t *) (p - (u_char *) fm.addr); + + do { + s.len = r->value->len; + s.data = r->value->data; + hash = ngx_crc32_long(s.data, s.len); + gvvn = (ngx_stream_geo_variable_value_node_t *) + ngx_str_rbtree_lookup(&ctx->rbtree, &s, hash); + + range->value = (ngx_stream_variable_value_t *) gvvn->offset; + range->start = r->start; + range->end = r->end; + range++; + + } while ((++r)->value); + + range->value = NULL; + + p = (u_char *) range + sizeof(void *); + } + + header = fm.addr; + header->crc32 = ngx_crc32_long((u_char *) fm.addr + + sizeof(ngx_stream_geo_header_t), + fm.size - sizeof(ngx_stream_geo_header_t)); + + ngx_close_file_mapping(&fm); +} + + +static u_char * +ngx_stream_geo_copy_values(u_char *base, u_char *p, ngx_rbtree_node_t *node, + ngx_rbtree_node_t *sentinel) +{ + ngx_stream_variable_value_t *vv; + ngx_stream_geo_variable_value_node_t *gvvn; + + if (node == sentinel) { + return p; + } + + gvvn = (ngx_stream_geo_variable_value_node_t *) node; + gvvn->offset = p - base; + + vv = (ngx_stream_variable_value_t *) p; + *vv = *gvvn->value; + p += sizeof(ngx_stream_variable_value_t); + vv->data = (u_char *) (p - base); + + p = ngx_cpymem(p, gvvn->sn.str.data, gvvn->sn.str.len); + + p = ngx_align_ptr(p, sizeof(void *)); + + p = ngx_stream_geo_copy_values(base, p, node->left, sentinel); + + return ngx_stream_geo_copy_values(base, p, node->right, sentinel); +} diff --git a/app/nginx/src/stream/ngx_stream_geoip_module.c b/app/nginx/src/stream/ngx_stream_geoip_module.c new file mode 100644 index 0000000..f694033 --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_geoip_module.c @@ -0,0 +1,814 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + +#include +#include + + +#define NGX_GEOIP_COUNTRY_CODE 0 +#define NGX_GEOIP_COUNTRY_CODE3 1 +#define NGX_GEOIP_COUNTRY_NAME 2 + + +typedef struct { + GeoIP *country; + GeoIP *org; + GeoIP *city; +#if (NGX_HAVE_GEOIP_V6) + unsigned country_v6:1; + unsigned org_v6:1; + unsigned city_v6:1; +#endif +} ngx_stream_geoip_conf_t; + + +typedef struct { + ngx_str_t *name; + uintptr_t data; +} ngx_stream_geoip_var_t; + + +typedef const char *(*ngx_stream_geoip_variable_handler_pt)(GeoIP *, + u_long addr); + + +ngx_stream_geoip_variable_handler_pt ngx_stream_geoip_country_functions[] = { + GeoIP_country_code_by_ipnum, + GeoIP_country_code3_by_ipnum, + GeoIP_country_name_by_ipnum, +}; + + +#if (NGX_HAVE_GEOIP_V6) + +typedef const char *(*ngx_stream_geoip_variable_handler_v6_pt)(GeoIP *, + geoipv6_t addr); + + +ngx_stream_geoip_variable_handler_v6_pt + ngx_stream_geoip_country_v6_functions[] = +{ + GeoIP_country_code_by_ipnum_v6, + GeoIP_country_code3_by_ipnum_v6, + GeoIP_country_name_by_ipnum_v6, +}; + +#endif + + +static ngx_int_t ngx_stream_geoip_country_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_geoip_org_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_geoip_city_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_geoip_region_name_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_geoip_city_float_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_geoip_city_int_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static GeoIPRecord *ngx_stream_geoip_get_city_record(ngx_stream_session_t *s); + +static ngx_int_t ngx_stream_geoip_add_variables(ngx_conf_t *cf); +static void *ngx_stream_geoip_create_conf(ngx_conf_t *cf); +static char *ngx_stream_geoip_country(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_stream_geoip_org(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_stream_geoip_city(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static void ngx_stream_geoip_cleanup(void *data); + + +static ngx_command_t ngx_stream_geoip_commands[] = { + + { ngx_string("geoip_country"), + NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE12, + ngx_stream_geoip_country, + NGX_STREAM_MAIN_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("geoip_org"), + NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE12, + ngx_stream_geoip_org, + NGX_STREAM_MAIN_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("geoip_city"), + NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE12, + ngx_stream_geoip_city, + NGX_STREAM_MAIN_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_geoip_module_ctx = { + ngx_stream_geoip_add_variables, /* preconfiguration */ + NULL, /* postconfiguration */ + + ngx_stream_geoip_create_conf, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_geoip_module = { + NGX_MODULE_V1, + &ngx_stream_geoip_module_ctx, /* module context */ + ngx_stream_geoip_commands, /* module directives */ + NGX_STREAM_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_stream_variable_t ngx_stream_geoip_vars[] = { + + { ngx_string("geoip_country_code"), NULL, + ngx_stream_geoip_country_variable, + NGX_GEOIP_COUNTRY_CODE, 0, 0 }, + + { ngx_string("geoip_country_code3"), NULL, + ngx_stream_geoip_country_variable, + NGX_GEOIP_COUNTRY_CODE3, 0, 0 }, + + { ngx_string("geoip_country_name"), NULL, + ngx_stream_geoip_country_variable, + NGX_GEOIP_COUNTRY_NAME, 0, 0 }, + + { ngx_string("geoip_org"), NULL, + ngx_stream_geoip_org_variable, + 0, 0, 0 }, + + { ngx_string("geoip_city_continent_code"), NULL, + ngx_stream_geoip_city_variable, + offsetof(GeoIPRecord, continent_code), 0, 0 }, + + { ngx_string("geoip_city_country_code"), NULL, + ngx_stream_geoip_city_variable, + offsetof(GeoIPRecord, country_code), 0, 0 }, + + { ngx_string("geoip_city_country_code3"), NULL, + ngx_stream_geoip_city_variable, + offsetof(GeoIPRecord, country_code3), 0, 0 }, + + { ngx_string("geoip_city_country_name"), NULL, + ngx_stream_geoip_city_variable, + offsetof(GeoIPRecord, country_name), 0, 0 }, + + { ngx_string("geoip_region"), NULL, + ngx_stream_geoip_city_variable, + offsetof(GeoIPRecord, region), 0, 0 }, + + { ngx_string("geoip_region_name"), NULL, + ngx_stream_geoip_region_name_variable, + 0, 0, 0 }, + + { ngx_string("geoip_city"), NULL, + ngx_stream_geoip_city_variable, + offsetof(GeoIPRecord, city), 0, 0 }, + + { ngx_string("geoip_postal_code"), NULL, + ngx_stream_geoip_city_variable, + offsetof(GeoIPRecord, postal_code), 0, 0 }, + + { ngx_string("geoip_latitude"), NULL, + ngx_stream_geoip_city_float_variable, + offsetof(GeoIPRecord, latitude), 0, 0 }, + + { ngx_string("geoip_longitude"), NULL, + ngx_stream_geoip_city_float_variable, + offsetof(GeoIPRecord, longitude), 0, 0 }, + + { ngx_string("geoip_dma_code"), NULL, + ngx_stream_geoip_city_int_variable, + offsetof(GeoIPRecord, dma_code), 0, 0 }, + + { ngx_string("geoip_area_code"), NULL, + ngx_stream_geoip_city_int_variable, + offsetof(GeoIPRecord, area_code), 0, 0 }, + + { ngx_null_string, NULL, NULL, 0, 0, 0 } +}; + + +static u_long +ngx_stream_geoip_addr(ngx_stream_session_t *s, ngx_stream_geoip_conf_t *gcf) +{ + ngx_addr_t addr; + struct sockaddr_in *sin; + + addr.sockaddr = s->connection->sockaddr; + addr.socklen = s->connection->socklen; + /* addr.name = s->connection->addr_text; */ + +#if (NGX_HAVE_INET6) + + if (addr.sockaddr->sa_family == AF_INET6) { + u_char *p; + in_addr_t inaddr; + struct in6_addr *inaddr6; + + inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr; + + if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { + p = inaddr6->s6_addr; + + inaddr = p[12] << 24; + inaddr += p[13] << 16; + inaddr += p[14] << 8; + inaddr += p[15]; + + return inaddr; + } + } + +#endif + + if (addr.sockaddr->sa_family != AF_INET) { + return INADDR_NONE; + } + + sin = (struct sockaddr_in *) addr.sockaddr; + return ntohl(sin->sin_addr.s_addr); +} + + +#if (NGX_HAVE_GEOIP_V6) + +static geoipv6_t +ngx_stream_geoip_addr_v6(ngx_stream_session_t *s, ngx_stream_geoip_conf_t *gcf) +{ + ngx_addr_t addr; + in_addr_t addr4; + struct in6_addr addr6; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + + addr.sockaddr = s->connection->sockaddr; + addr.socklen = s->connection->socklen; + /* addr.name = s->connection->addr_text; */ + + switch (addr.sockaddr->sa_family) { + + case AF_INET: + /* Produce IPv4-mapped IPv6 address. */ + sin = (struct sockaddr_in *) addr.sockaddr; + addr4 = ntohl(sin->sin_addr.s_addr); + + ngx_memzero(&addr6, sizeof(struct in6_addr)); + addr6.s6_addr[10] = 0xff; + addr6.s6_addr[11] = 0xff; + addr6.s6_addr[12] = addr4 >> 24; + addr6.s6_addr[13] = addr4 >> 16; + addr6.s6_addr[14] = addr4 >> 8; + addr6.s6_addr[15] = addr4; + return addr6; + + case AF_INET6: + sin6 = (struct sockaddr_in6 *) addr.sockaddr; + return sin6->sin6_addr; + + default: + return in6addr_any; + } +} + +#endif + + +static ngx_int_t +ngx_stream_geoip_country_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + ngx_stream_geoip_variable_handler_pt handler = + ngx_stream_geoip_country_functions[data]; +#if (NGX_HAVE_GEOIP_V6) + ngx_stream_geoip_variable_handler_v6_pt handler_v6 = + ngx_stream_geoip_country_v6_functions[data]; +#endif + + const char *val; + ngx_stream_geoip_conf_t *gcf; + + gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip_module); + + if (gcf->country == NULL) { + goto not_found; + } + +#if (NGX_HAVE_GEOIP_V6) + val = gcf->country_v6 + ? handler_v6(gcf->country, ngx_stream_geoip_addr_v6(s, gcf)) + : handler(gcf->country, ngx_stream_geoip_addr(s, gcf)); +#else + val = handler(gcf->country, ngx_stream_geoip_addr(s, gcf)); +#endif + + if (val == NULL) { + goto not_found; + } + + v->len = ngx_strlen(val); + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) val; + + return NGX_OK; + +not_found: + + v->not_found = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_geoip_org_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + size_t len; + char *val; + ngx_stream_geoip_conf_t *gcf; + + gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip_module); + + if (gcf->org == NULL) { + goto not_found; + } + +#if (NGX_HAVE_GEOIP_V6) + val = gcf->org_v6 + ? GeoIP_name_by_ipnum_v6(gcf->org, + ngx_stream_geoip_addr_v6(s, gcf)) + : GeoIP_name_by_ipnum(gcf->org, + ngx_stream_geoip_addr(s, gcf)); +#else + val = GeoIP_name_by_ipnum(gcf->org, ngx_stream_geoip_addr(s, gcf)); +#endif + + if (val == NULL) { + goto not_found; + } + + len = ngx_strlen(val); + v->data = ngx_pnalloc(s->connection->pool, len); + if (v->data == NULL) { + ngx_free(val); + return NGX_ERROR; + } + + ngx_memcpy(v->data, val, len); + + v->len = len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + ngx_free(val); + + return NGX_OK; + +not_found: + + v->not_found = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_geoip_city_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + char *val; + size_t len; + GeoIPRecord *gr; + + gr = ngx_stream_geoip_get_city_record(s); + if (gr == NULL) { + goto not_found; + } + + val = *(char **) ((char *) gr + data); + if (val == NULL) { + goto no_value; + } + + len = ngx_strlen(val); + v->data = ngx_pnalloc(s->connection->pool, len); + if (v->data == NULL) { + GeoIPRecord_delete(gr); + return NGX_ERROR; + } + + ngx_memcpy(v->data, val, len); + + v->len = len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + GeoIPRecord_delete(gr); + + return NGX_OK; + +no_value: + + GeoIPRecord_delete(gr); + +not_found: + + v->not_found = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_geoip_region_name_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + size_t len; + const char *val; + GeoIPRecord *gr; + + gr = ngx_stream_geoip_get_city_record(s); + if (gr == NULL) { + goto not_found; + } + + val = GeoIP_region_name_by_code(gr->country_code, gr->region); + + GeoIPRecord_delete(gr); + + if (val == NULL) { + goto not_found; + } + + len = ngx_strlen(val); + v->data = ngx_pnalloc(s->connection->pool, len); + if (v->data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(v->data, val, len); + + v->len = len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; + +not_found: + + v->not_found = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_geoip_city_float_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + float val; + GeoIPRecord *gr; + + gr = ngx_stream_geoip_get_city_record(s); + if (gr == NULL) { + v->not_found = 1; + return NGX_OK; + } + + v->data = ngx_pnalloc(s->connection->pool, NGX_INT64_LEN + 5); + if (v->data == NULL) { + GeoIPRecord_delete(gr); + return NGX_ERROR; + } + + val = *(float *) ((char *) gr + data); + + v->len = ngx_sprintf(v->data, "%.4f", val) - v->data; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + GeoIPRecord_delete(gr); + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_geoip_city_int_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + int val; + GeoIPRecord *gr; + + gr = ngx_stream_geoip_get_city_record(s); + if (gr == NULL) { + v->not_found = 1; + return NGX_OK; + } + + v->data = ngx_pnalloc(s->connection->pool, NGX_INT64_LEN); + if (v->data == NULL) { + GeoIPRecord_delete(gr); + return NGX_ERROR; + } + + val = *(int *) ((char *) gr + data); + + v->len = ngx_sprintf(v->data, "%d", val) - v->data; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + GeoIPRecord_delete(gr); + + return NGX_OK; +} + + +static GeoIPRecord * +ngx_stream_geoip_get_city_record(ngx_stream_session_t *s) +{ + ngx_stream_geoip_conf_t *gcf; + + gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip_module); + + if (gcf->city) { +#if (NGX_HAVE_GEOIP_V6) + return gcf->city_v6 + ? GeoIP_record_by_ipnum_v6(gcf->city, + ngx_stream_geoip_addr_v6(s, gcf)) + : GeoIP_record_by_ipnum(gcf->city, + ngx_stream_geoip_addr(s, gcf)); +#else + return GeoIP_record_by_ipnum(gcf->city, ngx_stream_geoip_addr(s, gcf)); +#endif + } + + return NULL; +} + + +static ngx_int_t +ngx_stream_geoip_add_variables(ngx_conf_t *cf) +{ + ngx_stream_variable_t *var, *v; + + for (v = ngx_stream_geoip_vars; v->name.len; v++) { + var = ngx_stream_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static void * +ngx_stream_geoip_create_conf(ngx_conf_t *cf) +{ + ngx_pool_cleanup_t *cln; + ngx_stream_geoip_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_geoip_conf_t)); + if (conf == NULL) { + return NULL; + } + + cln = ngx_pool_cleanup_add(cf->pool, 0); + if (cln == NULL) { + return NULL; + } + + cln->handler = ngx_stream_geoip_cleanup; + cln->data = conf; + + return conf; +} + + +static char * +ngx_stream_geoip_country(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_geoip_conf_t *gcf = conf; + + ngx_str_t *value; + + if (gcf->country) { + return "is duplicate"; + } + + value = cf->args->elts; + + gcf->country = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE); + + if (gcf->country == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "GeoIP_open(\"%V\") failed", &value[1]); + + return NGX_CONF_ERROR; + } + + if (cf->args->nelts == 3) { + if (ngx_strcmp(value[2].data, "utf8") == 0) { + GeoIP_set_charset(gcf->country, GEOIP_CHARSET_UTF8); + + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[2]); + return NGX_CONF_ERROR; + } + } + + switch (gcf->country->databaseType) { + + case GEOIP_COUNTRY_EDITION: + + return NGX_CONF_OK; + +#if (NGX_HAVE_GEOIP_V6) + case GEOIP_COUNTRY_EDITION_V6: + + gcf->country_v6 = 1; + return NGX_CONF_OK; +#endif + + default: + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid GeoIP database \"%V\" type:%d", + &value[1], gcf->country->databaseType); + return NGX_CONF_ERROR; + } +} + + +static char * +ngx_stream_geoip_org(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_geoip_conf_t *gcf = conf; + + ngx_str_t *value; + + if (gcf->org) { + return "is duplicate"; + } + + value = cf->args->elts; + + gcf->org = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE); + + if (gcf->org == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "GeoIP_open(\"%V\") failed", &value[1]); + + return NGX_CONF_ERROR; + } + + if (cf->args->nelts == 3) { + if (ngx_strcmp(value[2].data, "utf8") == 0) { + GeoIP_set_charset(gcf->org, GEOIP_CHARSET_UTF8); + + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[2]); + return NGX_CONF_ERROR; + } + } + + switch (gcf->org->databaseType) { + + case GEOIP_ISP_EDITION: + case GEOIP_ORG_EDITION: + case GEOIP_DOMAIN_EDITION: + case GEOIP_ASNUM_EDITION: + + return NGX_CONF_OK; + +#if (NGX_HAVE_GEOIP_V6) + case GEOIP_ISP_EDITION_V6: + case GEOIP_ORG_EDITION_V6: + case GEOIP_DOMAIN_EDITION_V6: + case GEOIP_ASNUM_EDITION_V6: + + gcf->org_v6 = 1; + return NGX_CONF_OK; +#endif + + default: + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid GeoIP database \"%V\" type:%d", + &value[1], gcf->org->databaseType); + return NGX_CONF_ERROR; + } +} + + +static char * +ngx_stream_geoip_city(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_geoip_conf_t *gcf = conf; + + ngx_str_t *value; + + if (gcf->city) { + return "is duplicate"; + } + + value = cf->args->elts; + + gcf->city = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE); + + if (gcf->city == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "GeoIP_open(\"%V\") failed", &value[1]); + + return NGX_CONF_ERROR; + } + + if (cf->args->nelts == 3) { + if (ngx_strcmp(value[2].data, "utf8") == 0) { + GeoIP_set_charset(gcf->city, GEOIP_CHARSET_UTF8); + + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[2]); + return NGX_CONF_ERROR; + } + } + + switch (gcf->city->databaseType) { + + case GEOIP_CITY_EDITION_REV0: + case GEOIP_CITY_EDITION_REV1: + + return NGX_CONF_OK; + +#if (NGX_HAVE_GEOIP_V6) + case GEOIP_CITY_EDITION_REV0_V6: + case GEOIP_CITY_EDITION_REV1_V6: + + gcf->city_v6 = 1; + return NGX_CONF_OK; +#endif + + default: + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid GeoIP City database \"%V\" type:%d", + &value[1], gcf->city->databaseType); + return NGX_CONF_ERROR; + } +} + + +static void +ngx_stream_geoip_cleanup(void *data) +{ + ngx_stream_geoip_conf_t *gcf = data; + + if (gcf->country) { + GeoIP_delete(gcf->country); + } + + if (gcf->org) { + GeoIP_delete(gcf->org); + } + + if (gcf->city) { + GeoIP_delete(gcf->city); + } +} diff --git a/app/nginx/src/stream/ngx_stream_handler.c b/app/nginx/src/stream/ngx_stream_handler.c new file mode 100644 index 0000000..669b6a1 --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_handler.c @@ -0,0 +1,385 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +static void ngx_stream_log_session(ngx_stream_session_t *s); +static void ngx_stream_close_connection(ngx_connection_t *c); +static u_char *ngx_stream_log_error(ngx_log_t *log, u_char *buf, size_t len); +static void ngx_stream_proxy_protocol_handler(ngx_event_t *rev); + + +void +ngx_stream_init_connection(ngx_connection_t *c) +{ + u_char text[NGX_SOCKADDR_STRLEN]; + size_t len; + ngx_uint_t i; + ngx_time_t *tp; + ngx_event_t *rev; + struct sockaddr *sa; + ngx_stream_port_t *port; + struct sockaddr_in *sin; + ngx_stream_in_addr_t *addr; + ngx_stream_session_t *s; + ngx_stream_addr_conf_t *addr_conf; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; + ngx_stream_in6_addr_t *addr6; +#endif + ngx_stream_core_srv_conf_t *cscf; + ngx_stream_core_main_conf_t *cmcf; + + /* find the server configuration for the address:port */ + + port = c->listening->servers; + + if (port->naddrs > 1) { + + /* + * There are several addresses on this port and one of them + * is the "*:port" wildcard so getsockname() is needed to determine + * the server address. + * + * AcceptEx() and recvmsg() already gave this address. + */ + + if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) { + ngx_stream_close_connection(c); + return; + } + + sa = c->local_sockaddr; + + switch (sa->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) sa; + + addr6 = port->addrs; + + /* the last address is "*" */ + + for (i = 0; i < port->naddrs - 1; i++) { + if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) { + break; + } + } + + addr_conf = &addr6[i].conf; + + break; +#endif + + default: /* AF_INET */ + sin = (struct sockaddr_in *) sa; + + addr = port->addrs; + + /* the last address is "*" */ + + for (i = 0; i < port->naddrs - 1; i++) { + if (addr[i].addr == sin->sin_addr.s_addr) { + break; + } + } + + addr_conf = &addr[i].conf; + + break; + } + + } else { + switch (c->local_sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + addr6 = port->addrs; + addr_conf = &addr6[0].conf; + break; +#endif + + default: /* AF_INET */ + addr = port->addrs; + addr_conf = &addr[0].conf; + break; + } + } + + s = ngx_pcalloc(c->pool, sizeof(ngx_stream_session_t)); + if (s == NULL) { + ngx_stream_close_connection(c); + return; + } + + s->signature = NGX_STREAM_MODULE; + s->main_conf = addr_conf->ctx->main_conf; + s->srv_conf = addr_conf->ctx->srv_conf; + +#if (NGX_STREAM_SSL) + s->ssl = addr_conf->ssl; +#endif + + if (c->buffer) { + s->received += c->buffer->last - c->buffer->pos; + } + + s->connection = c; + c->data = s; + + cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module); + + ngx_set_connection_log(c, cscf->error_log); + + len = ngx_sock_ntop(c->sockaddr, c->socklen, text, NGX_SOCKADDR_STRLEN, 1); + + ngx_log_error(NGX_LOG_INFO, c->log, 0, "*%uA %sclient %*s connected to %V", + c->number, c->type == SOCK_DGRAM ? "udp " : "", + len, text, &addr_conf->addr_text); + + c->log->connection = c->number; + c->log->handler = ngx_stream_log_error; + c->log->data = s; + c->log->action = "initializing session"; + c->log_error = NGX_ERROR_INFO; + + s->ctx = ngx_pcalloc(c->pool, sizeof(void *) * ngx_stream_max_module); + if (s->ctx == NULL) { + ngx_stream_close_connection(c); + return; + } + + cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module); + + s->variables = ngx_pcalloc(s->connection->pool, + cmcf->variables.nelts + * sizeof(ngx_stream_variable_value_t)); + + if (s->variables == NULL) { + ngx_stream_close_connection(c); + return; + } + + tp = ngx_timeofday(); + s->start_sec = tp->sec; + s->start_msec = tp->msec; + + rev = c->read; + rev->handler = ngx_stream_session_handler; + + if (addr_conf->proxy_protocol) { + c->log->action = "reading PROXY protocol"; + + rev->handler = ngx_stream_proxy_protocol_handler; + + if (!rev->ready) { + ngx_add_timer(rev, cscf->proxy_protocol_timeout); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_stream_finalize_session(s, + NGX_STREAM_INTERNAL_SERVER_ERROR); + } + + return; + } + } + + if (ngx_use_accept_mutex) { + ngx_post_event(rev, &ngx_posted_events); + return; + } + + rev->handler(rev); +} + + +static void +ngx_stream_proxy_protocol_handler(ngx_event_t *rev) +{ + u_char *p, buf[NGX_PROXY_PROTOCOL_MAX_HEADER]; + size_t size; + ssize_t n; + ngx_err_t err; + ngx_connection_t *c; + ngx_stream_session_t *s; + ngx_stream_core_srv_conf_t *cscf; + + c = rev->data; + s = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, + "stream PROXY protocol handler"); + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + ngx_stream_finalize_session(s, NGX_STREAM_OK); + return; + } + + n = recv(c->fd, (char *) buf, sizeof(buf), MSG_PEEK); + + err = ngx_socket_errno; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "recv(): %z", n); + + if (n == -1) { + if (err == NGX_EAGAIN) { + rev->ready = 0; + + if (!rev->timer_set) { + cscf = ngx_stream_get_module_srv_conf(s, + ngx_stream_core_module); + + ngx_add_timer(rev, cscf->proxy_protocol_timeout); + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_stream_finalize_session(s, + NGX_STREAM_INTERNAL_SERVER_ERROR); + } + + return; + } + + ngx_connection_error(c, err, "recv() failed"); + + ngx_stream_finalize_session(s, NGX_STREAM_OK); + return; + } + + if (rev->timer_set) { + ngx_del_timer(rev); + } + + p = ngx_proxy_protocol_read(c, buf, buf + n); + + if (p == NULL) { + ngx_stream_finalize_session(s, NGX_STREAM_BAD_REQUEST); + return; + } + + size = p - buf; + + if (c->recv(c, buf, size) != (ssize_t) size) { + ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + c->log->action = "initializing session"; + + ngx_stream_session_handler(rev); +} + + +void +ngx_stream_session_handler(ngx_event_t *rev) +{ + ngx_connection_t *c; + ngx_stream_session_t *s; + + c = rev->data; + s = c->data; + + ngx_stream_core_run_phases(s); +} + + +void +ngx_stream_finalize_session(ngx_stream_session_t *s, ngx_uint_t rc) +{ + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "finalize stream session: %i", rc); + + s->status = rc; + + ngx_stream_log_session(s); + + ngx_stream_close_connection(s->connection); +} + + +static void +ngx_stream_log_session(ngx_stream_session_t *s) +{ + ngx_uint_t i, n; + ngx_stream_handler_pt *log_handler; + ngx_stream_core_main_conf_t *cmcf; + + cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module); + + log_handler = cmcf->phases[NGX_STREAM_LOG_PHASE].handlers.elts; + n = cmcf->phases[NGX_STREAM_LOG_PHASE].handlers.nelts; + + for (i = 0; i < n; i++) { + log_handler[i](s); + } +} + + +static void +ngx_stream_close_connection(ngx_connection_t *c) +{ + ngx_pool_t *pool; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "close stream connection: %d", c->fd); + +#if (NGX_STREAM_SSL) + + if (c->ssl) { + if (ngx_ssl_shutdown(c) == NGX_AGAIN) { + c->ssl->handler = ngx_stream_close_connection; + return; + } + } + +#endif + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); +#endif + + pool = c->pool; + + ngx_close_connection(c); + + ngx_destroy_pool(pool); +} + + +static u_char * +ngx_stream_log_error(ngx_log_t *log, u_char *buf, size_t len) +{ + u_char *p; + ngx_stream_session_t *s; + + if (log->action) { + p = ngx_snprintf(buf, len, " while %s", log->action); + len -= p - buf; + buf = p; + } + + s = log->data; + + p = ngx_snprintf(buf, len, ", %sclient: %V, server: %V", + s->connection->type == SOCK_DGRAM ? "udp " : "", + &s->connection->addr_text, + &s->connection->listening->addr_text); + len -= p - buf; + buf = p; + + if (s->log_handler) { + p = s->log_handler(log, buf, len); + } + + return p; +} diff --git a/app/nginx/src/stream/ngx_stream_limit_conn_module.c b/app/nginx/src/stream/ngx_stream_limit_conn_module.c new file mode 100644 index 0000000..b64a426 --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_limit_conn_module.c @@ -0,0 +1,646 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef struct { + u_char color; + u_char len; + u_short conn; + u_char data[1]; +} ngx_stream_limit_conn_node_t; + + +typedef struct { + ngx_shm_zone_t *shm_zone; + ngx_rbtree_node_t *node; +} ngx_stream_limit_conn_cleanup_t; + + +typedef struct { + ngx_rbtree_t *rbtree; + ngx_stream_complex_value_t key; +} ngx_stream_limit_conn_ctx_t; + + +typedef struct { + ngx_shm_zone_t *shm_zone; + ngx_uint_t conn; +} ngx_stream_limit_conn_limit_t; + + +typedef struct { + ngx_array_t limits; + ngx_uint_t log_level; +} ngx_stream_limit_conn_conf_t; + + +static ngx_rbtree_node_t *ngx_stream_limit_conn_lookup(ngx_rbtree_t *rbtree, + ngx_str_t *key, uint32_t hash); +static void ngx_stream_limit_conn_cleanup(void *data); +static ngx_inline void ngx_stream_limit_conn_cleanup_all(ngx_pool_t *pool); + +static void *ngx_stream_limit_conn_create_conf(ngx_conf_t *cf); +static char *ngx_stream_limit_conn_merge_conf(ngx_conf_t *cf, void *parent, + void *child); +static char *ngx_stream_limit_conn_zone(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_stream_limit_conn(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static ngx_int_t ngx_stream_limit_conn_init(ngx_conf_t *cf); + + +static ngx_conf_enum_t ngx_stream_limit_conn_log_levels[] = { + { ngx_string("info"), NGX_LOG_INFO }, + { ngx_string("notice"), NGX_LOG_NOTICE }, + { ngx_string("warn"), NGX_LOG_WARN }, + { ngx_string("error"), NGX_LOG_ERR }, + { ngx_null_string, 0 } +}; + + +static ngx_command_t ngx_stream_limit_conn_commands[] = { + + { ngx_string("limit_conn_zone"), + NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE2, + ngx_stream_limit_conn_zone, + 0, + 0, + NULL }, + + { ngx_string("limit_conn"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE2, + ngx_stream_limit_conn, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("limit_conn_log_level"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_enum_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_limit_conn_conf_t, log_level), + &ngx_stream_limit_conn_log_levels }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_limit_conn_module_ctx = { + NULL, /* preconfiguration */ + ngx_stream_limit_conn_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_stream_limit_conn_create_conf, /* create server configuration */ + ngx_stream_limit_conn_merge_conf /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_limit_conn_module = { + NGX_MODULE_V1, + &ngx_stream_limit_conn_module_ctx, /* module context */ + ngx_stream_limit_conn_commands, /* module directives */ + NGX_STREAM_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_stream_limit_conn_handler(ngx_stream_session_t *s) +{ + size_t n; + uint32_t hash; + ngx_str_t key; + ngx_uint_t i; + ngx_slab_pool_t *shpool; + ngx_rbtree_node_t *node; + ngx_pool_cleanup_t *cln; + ngx_stream_limit_conn_ctx_t *ctx; + ngx_stream_limit_conn_node_t *lc; + ngx_stream_limit_conn_conf_t *lccf; + ngx_stream_limit_conn_limit_t *limits; + ngx_stream_limit_conn_cleanup_t *lccln; + + lccf = ngx_stream_get_module_srv_conf(s, ngx_stream_limit_conn_module); + limits = lccf->limits.elts; + + for (i = 0; i < lccf->limits.nelts; i++) { + ctx = limits[i].shm_zone->data; + + if (ngx_stream_complex_value(s, &ctx->key, &key) != NGX_OK) { + return NGX_ERROR; + } + + if (key.len == 0) { + continue; + } + + if (key.len > 255) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "the value of the \"%V\" key " + "is more than 255 bytes: \"%V\"", + &ctx->key.value, &key); + continue; + } + + hash = ngx_crc32_short(key.data, key.len); + + shpool = (ngx_slab_pool_t *) limits[i].shm_zone->shm.addr; + + ngx_shmtx_lock(&shpool->mutex); + + node = ngx_stream_limit_conn_lookup(ctx->rbtree, &key, hash); + + if (node == NULL) { + + n = offsetof(ngx_rbtree_node_t, color) + + offsetof(ngx_stream_limit_conn_node_t, data) + + key.len; + + node = ngx_slab_alloc_locked(shpool, n); + + if (node == NULL) { + ngx_shmtx_unlock(&shpool->mutex); + ngx_stream_limit_conn_cleanup_all(s->connection->pool); + return NGX_STREAM_SERVICE_UNAVAILABLE; + } + + lc = (ngx_stream_limit_conn_node_t *) &node->color; + + node->key = hash; + lc->len = (u_char) key.len; + lc->conn = 1; + ngx_memcpy(lc->data, key.data, key.len); + + ngx_rbtree_insert(ctx->rbtree, node); + + } else { + + lc = (ngx_stream_limit_conn_node_t *) &node->color; + + if ((ngx_uint_t) lc->conn >= limits[i].conn) { + + ngx_shmtx_unlock(&shpool->mutex); + + ngx_log_error(lccf->log_level, s->connection->log, 0, + "limiting connections by zone \"%V\"", + &limits[i].shm_zone->shm.name); + + ngx_stream_limit_conn_cleanup_all(s->connection->pool); + return NGX_STREAM_SERVICE_UNAVAILABLE; + } + + lc->conn++; + } + + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "limit conn: %08Xi %d", node->key, lc->conn); + + ngx_shmtx_unlock(&shpool->mutex); + + cln = ngx_pool_cleanup_add(s->connection->pool, + sizeof(ngx_stream_limit_conn_cleanup_t)); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_stream_limit_conn_cleanup; + lccln = cln->data; + + lccln->shm_zone = limits[i].shm_zone; + lccln->node = node; + } + + return NGX_DECLINED; +} + + +static void +ngx_stream_limit_conn_rbtree_insert_value(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) +{ + ngx_rbtree_node_t **p; + ngx_stream_limit_conn_node_t *lcn, *lcnt; + + for ( ;; ) { + + if (node->key < temp->key) { + + p = &temp->left; + + } else if (node->key > temp->key) { + + p = &temp->right; + + } else { /* node->key == temp->key */ + + lcn = (ngx_stream_limit_conn_node_t *) &node->color; + lcnt = (ngx_stream_limit_conn_node_t *) &temp->color; + + p = (ngx_memn2cmp(lcn->data, lcnt->data, lcn->len, lcnt->len) < 0) + ? &temp->left : &temp->right; + } + + if (*p == sentinel) { + break; + } + + temp = *p; + } + + *p = node; + node->parent = temp; + node->left = sentinel; + node->right = sentinel; + ngx_rbt_red(node); +} + + +static ngx_rbtree_node_t * +ngx_stream_limit_conn_lookup(ngx_rbtree_t *rbtree, ngx_str_t *key, + uint32_t hash) +{ + ngx_int_t rc; + ngx_rbtree_node_t *node, *sentinel; + ngx_stream_limit_conn_node_t *lcn; + + node = rbtree->root; + sentinel = rbtree->sentinel; + + while (node != sentinel) { + + if (hash < node->key) { + node = node->left; + continue; + } + + if (hash > node->key) { + node = node->right; + continue; + } + + /* hash == node->key */ + + lcn = (ngx_stream_limit_conn_node_t *) &node->color; + + rc = ngx_memn2cmp(key->data, lcn->data, key->len, (size_t) lcn->len); + + if (rc == 0) { + return node; + } + + node = (rc < 0) ? node->left : node->right; + } + + return NULL; +} + + +static void +ngx_stream_limit_conn_cleanup(void *data) +{ + ngx_stream_limit_conn_cleanup_t *lccln = data; + + ngx_slab_pool_t *shpool; + ngx_rbtree_node_t *node; + ngx_stream_limit_conn_ctx_t *ctx; + ngx_stream_limit_conn_node_t *lc; + + ctx = lccln->shm_zone->data; + shpool = (ngx_slab_pool_t *) lccln->shm_zone->shm.addr; + node = lccln->node; + lc = (ngx_stream_limit_conn_node_t *) &node->color; + + ngx_shmtx_lock(&shpool->mutex); + + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, lccln->shm_zone->shm.log, 0, + "limit conn cleanup: %08Xi %d", node->key, lc->conn); + + lc->conn--; + + if (lc->conn == 0) { + ngx_rbtree_delete(ctx->rbtree, node); + ngx_slab_free_locked(shpool, node); + } + + ngx_shmtx_unlock(&shpool->mutex); +} + + +static ngx_inline void +ngx_stream_limit_conn_cleanup_all(ngx_pool_t *pool) +{ + ngx_pool_cleanup_t *cln; + + cln = pool->cleanup; + + while (cln && cln->handler == ngx_stream_limit_conn_cleanup) { + ngx_stream_limit_conn_cleanup(cln->data); + cln = cln->next; + } + + pool->cleanup = cln; +} + + +static ngx_int_t +ngx_stream_limit_conn_init_zone(ngx_shm_zone_t *shm_zone, void *data) +{ + ngx_stream_limit_conn_ctx_t *octx = data; + + size_t len; + ngx_slab_pool_t *shpool; + ngx_rbtree_node_t *sentinel; + ngx_stream_limit_conn_ctx_t *ctx; + + ctx = shm_zone->data; + + if (octx) { + if (ctx->key.value.len != octx->key.value.len + || ngx_strncmp(ctx->key.value.data, octx->key.value.data, + ctx->key.value.len) + != 0) + { + ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0, + "limit_conn_zone \"%V\" uses the \"%V\" key " + "while previously it used the \"%V\" key", + &shm_zone->shm.name, &ctx->key.value, + &octx->key.value); + return NGX_ERROR; + } + + ctx->rbtree = octx->rbtree; + + return NGX_OK; + } + + shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; + + if (shm_zone->shm.exists) { + ctx->rbtree = shpool->data; + + return NGX_OK; + } + + ctx->rbtree = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_t)); + if (ctx->rbtree == NULL) { + return NGX_ERROR; + } + + shpool->data = ctx->rbtree; + + sentinel = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_node_t)); + if (sentinel == NULL) { + return NGX_ERROR; + } + + ngx_rbtree_init(ctx->rbtree, sentinel, + ngx_stream_limit_conn_rbtree_insert_value); + + len = sizeof(" in limit_conn_zone \"\"") + shm_zone->shm.name.len; + + shpool->log_ctx = ngx_slab_alloc(shpool, len); + if (shpool->log_ctx == NULL) { + return NGX_ERROR; + } + + ngx_sprintf(shpool->log_ctx, " in limit_conn_zone \"%V\"%Z", + &shm_zone->shm.name); + + return NGX_OK; +} + + +static void * +ngx_stream_limit_conn_create_conf(ngx_conf_t *cf) +{ + ngx_stream_limit_conn_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_limit_conn_conf_t)); + if (conf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * conf->limits.elts = NULL; + */ + + conf->log_level = NGX_CONF_UNSET_UINT; + + return conf; +} + + +static char * +ngx_stream_limit_conn_merge_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_stream_limit_conn_conf_t *prev = parent; + ngx_stream_limit_conn_conf_t *conf = child; + + if (conf->limits.elts == NULL) { + conf->limits = prev->limits; + } + + ngx_conf_merge_uint_value(conf->log_level, prev->log_level, NGX_LOG_ERR); + + return NGX_CONF_OK; +} + + +static char * +ngx_stream_limit_conn_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + u_char *p; + ssize_t size; + ngx_str_t *value, name, s; + ngx_uint_t i; + ngx_shm_zone_t *shm_zone; + ngx_stream_limit_conn_ctx_t *ctx; + ngx_stream_compile_complex_value_t ccv; + + value = cf->args->elts; + + ctx = ngx_pcalloc(cf->pool, sizeof(ngx_stream_limit_conn_ctx_t)); + if (ctx == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[1]; + ccv.complex_value = &ctx->key; + + if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + size = 0; + name.len = 0; + + for (i = 2; i < cf->args->nelts; i++) { + + if (ngx_strncmp(value[i].data, "zone=", 5) == 0) { + + name.data = value[i].data + 5; + + p = (u_char *) ngx_strchr(name.data, ':'); + + if (p == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid zone size \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + name.len = p - name.data; + + s.data = p + 1; + s.len = value[i].data + value[i].len - s.data; + + size = ngx_parse_size(&s); + + if (size == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid zone size \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + if (size < (ssize_t) (8 * ngx_pagesize)) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "zone \"%V\" is too small", &value[i]); + return NGX_CONF_ERROR; + } + + continue; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + if (name.len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" must have \"zone\" parameter", + &cmd->name); + return NGX_CONF_ERROR; + } + + shm_zone = ngx_shared_memory_add(cf, &name, size, + &ngx_stream_limit_conn_module); + if (shm_zone == NULL) { + return NGX_CONF_ERROR; + } + + if (shm_zone->data) { + ctx = shm_zone->data; + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%V \"%V\" is already bound to key \"%V\"", + &cmd->name, &name, &ctx->key.value); + return NGX_CONF_ERROR; + } + + shm_zone->init = ngx_stream_limit_conn_init_zone; + shm_zone->data = ctx; + + return NGX_CONF_OK; +} + + +static char * +ngx_stream_limit_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_shm_zone_t *shm_zone; + ngx_stream_limit_conn_conf_t *lccf = conf; + ngx_stream_limit_conn_limit_t *limit, *limits; + + ngx_str_t *value; + ngx_int_t n; + ngx_uint_t i; + + value = cf->args->elts; + + shm_zone = ngx_shared_memory_add(cf, &value[1], 0, + &ngx_stream_limit_conn_module); + if (shm_zone == NULL) { + return NGX_CONF_ERROR; + } + + limits = lccf->limits.elts; + + if (limits == NULL) { + if (ngx_array_init(&lccf->limits, cf->pool, 1, + sizeof(ngx_stream_limit_conn_limit_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + } + + for (i = 0; i < lccf->limits.nelts; i++) { + if (shm_zone == limits[i].shm_zone) { + return "is duplicate"; + } + } + + n = ngx_atoi(value[2].data, value[2].len); + if (n <= 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid number of connections \"%V\"", &value[2]); + return NGX_CONF_ERROR; + } + + if (n > 65535) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "connection limit must be less 65536"); + return NGX_CONF_ERROR; + } + + limit = ngx_array_push(&lccf->limits); + if (limit == NULL) { + return NGX_CONF_ERROR; + } + + limit->conn = n; + limit->shm_zone = shm_zone; + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_stream_limit_conn_init(ngx_conf_t *cf) +{ + ngx_stream_handler_pt *h; + ngx_stream_core_main_conf_t *cmcf; + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + + h = ngx_array_push(&cmcf->phases[NGX_STREAM_PREACCESS_PHASE].handlers); + if (h == NULL) { + return NGX_ERROR; + } + + *h = ngx_stream_limit_conn_handler; + + return NGX_OK; +} diff --git a/app/nginx/src/stream/ngx_stream_log_module.c b/app/nginx/src/stream/ngx_stream_log_module.c new file mode 100644 index 0000000..466bdda --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_log_module.c @@ -0,0 +1,1543 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + +#if (NGX_ZLIB) +#include +#endif + + +typedef struct ngx_stream_log_op_s ngx_stream_log_op_t; + +typedef u_char *(*ngx_stream_log_op_run_pt) (ngx_stream_session_t *s, + u_char *buf, ngx_stream_log_op_t *op); + +typedef size_t (*ngx_stream_log_op_getlen_pt) (ngx_stream_session_t *s, + uintptr_t data); + + +struct ngx_stream_log_op_s { + size_t len; + ngx_stream_log_op_getlen_pt getlen; + ngx_stream_log_op_run_pt run; + uintptr_t data; +}; + + +typedef struct { + ngx_str_t name; + ngx_array_t *flushes; + ngx_array_t *ops; /* array of ngx_stream_log_op_t */ +} ngx_stream_log_fmt_t; + + +typedef struct { + ngx_array_t formats; /* array of ngx_stream_log_fmt_t */ +} ngx_stream_log_main_conf_t; + + +typedef struct { + u_char *start; + u_char *pos; + u_char *last; + + ngx_event_t *event; + ngx_msec_t flush; + ngx_int_t gzip; +} ngx_stream_log_buf_t; + + +typedef struct { + ngx_array_t *lengths; + ngx_array_t *values; +} ngx_stream_log_script_t; + + +typedef struct { + ngx_open_file_t *file; + ngx_stream_log_script_t *script; + time_t disk_full_time; + time_t error_log_time; + ngx_syslog_peer_t *syslog_peer; + ngx_stream_log_fmt_t *format; + ngx_stream_complex_value_t *filter; +} ngx_stream_log_t; + + +typedef struct { + ngx_array_t *logs; /* array of ngx_stream_log_t */ + + ngx_open_file_cache_t *open_file_cache; + time_t open_file_cache_valid; + ngx_uint_t open_file_cache_min_uses; + + ngx_uint_t off; /* unsigned off:1 */ +} ngx_stream_log_srv_conf_t; + + +typedef struct { + ngx_str_t name; + size_t len; + ngx_stream_log_op_run_pt run; +} ngx_stream_log_var_t; + + +static void ngx_stream_log_write(ngx_stream_session_t *s, ngx_stream_log_t *log, + u_char *buf, size_t len); +static ssize_t ngx_stream_log_script_write(ngx_stream_session_t *s, + ngx_stream_log_script_t *script, u_char **name, u_char *buf, size_t len); + +#if (NGX_ZLIB) +static ssize_t ngx_stream_log_gzip(ngx_fd_t fd, u_char *buf, size_t len, + ngx_int_t level, ngx_log_t *log); + +static void *ngx_stream_log_gzip_alloc(void *opaque, u_int items, u_int size); +static void ngx_stream_log_gzip_free(void *opaque, void *address); +#endif + +static void ngx_stream_log_flush(ngx_open_file_t *file, ngx_log_t *log); +static void ngx_stream_log_flush_handler(ngx_event_t *ev); + +static ngx_int_t ngx_stream_log_variable_compile(ngx_conf_t *cf, + ngx_stream_log_op_t *op, ngx_str_t *value, ngx_uint_t json); +static size_t ngx_stream_log_variable_getlen(ngx_stream_session_t *s, + uintptr_t data); +static u_char *ngx_stream_log_variable(ngx_stream_session_t *s, u_char *buf, + ngx_stream_log_op_t *op); +static uintptr_t ngx_stream_log_escape(u_char *dst, u_char *src, size_t size); +static size_t ngx_stream_log_json_variable_getlen(ngx_stream_session_t *s, + uintptr_t data); +static u_char *ngx_stream_log_json_variable(ngx_stream_session_t *s, + u_char *buf, ngx_stream_log_op_t *op); + + +static void *ngx_stream_log_create_main_conf(ngx_conf_t *cf); +static void *ngx_stream_log_create_srv_conf(ngx_conf_t *cf); +static char *ngx_stream_log_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); +static char *ngx_stream_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_stream_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_stream_log_compile_format(ngx_conf_t *cf, + ngx_array_t *flushes, ngx_array_t *ops, ngx_array_t *args, ngx_uint_t s); +static char *ngx_stream_log_open_file_cache(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static ngx_int_t ngx_stream_log_init(ngx_conf_t *cf); + + +static ngx_command_t ngx_stream_log_commands[] = { + + { ngx_string("log_format"), + NGX_STREAM_MAIN_CONF|NGX_CONF_2MORE, + ngx_stream_log_set_format, + NGX_STREAM_MAIN_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("access_log"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, + ngx_stream_log_set_log, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("open_log_file_cache"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1234, + ngx_stream_log_open_file_cache, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_log_module_ctx = { + NULL, /* preconfiguration */ + ngx_stream_log_init, /* postconfiguration */ + + ngx_stream_log_create_main_conf, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_stream_log_create_srv_conf, /* create server configuration */ + ngx_stream_log_merge_srv_conf /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_log_module = { + NGX_MODULE_V1, + &ngx_stream_log_module_ctx, /* module context */ + ngx_stream_log_commands, /* module directives */ + NGX_STREAM_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_stream_log_handler(ngx_stream_session_t *s) +{ + u_char *line, *p; + size_t len, size; + ssize_t n; + ngx_str_t val; + ngx_uint_t i, l; + ngx_stream_log_t *log; + ngx_stream_log_op_t *op; + ngx_stream_log_buf_t *buffer; + ngx_stream_log_srv_conf_t *lscf; + + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream log handler"); + + lscf = ngx_stream_get_module_srv_conf(s, ngx_stream_log_module); + + if (lscf->off || lscf->logs == NULL) { + return NGX_OK; + } + + log = lscf->logs->elts; + for (l = 0; l < lscf->logs->nelts; l++) { + + if (log[l].filter) { + if (ngx_stream_complex_value(s, log[l].filter, &val) != NGX_OK) { + return NGX_ERROR; + } + + if (val.len == 0 || (val.len == 1 && val.data[0] == '0')) { + continue; + } + } + + if (ngx_time() == log[l].disk_full_time) { + + /* + * on FreeBSD writing to a full filesystem with enabled softupdates + * may block process for much longer time than writing to non-full + * filesystem, so we skip writing to a log for one second + */ + + continue; + } + + ngx_stream_script_flush_no_cacheable_variables(s, + log[l].format->flushes); + + len = 0; + op = log[l].format->ops->elts; + for (i = 0; i < log[l].format->ops->nelts; i++) { + if (op[i].len == 0) { + len += op[i].getlen(s, op[i].data); + + } else { + len += op[i].len; + } + } + + if (log[l].syslog_peer) { + + /* length of syslog's PRI and HEADER message parts */ + len += sizeof("<255>Jan 01 00:00:00 ") - 1 + + ngx_cycle->hostname.len + 1 + + log[l].syslog_peer->tag.len + 2; + + goto alloc_line; + } + + len += NGX_LINEFEED_SIZE; + + buffer = log[l].file ? log[l].file->data : NULL; + + if (buffer) { + + if (len > (size_t) (buffer->last - buffer->pos)) { + + ngx_stream_log_write(s, &log[l], buffer->start, + buffer->pos - buffer->start); + + buffer->pos = buffer->start; + } + + if (len <= (size_t) (buffer->last - buffer->pos)) { + + p = buffer->pos; + + if (buffer->event && p == buffer->start) { + ngx_add_timer(buffer->event, buffer->flush); + } + + for (i = 0; i < log[l].format->ops->nelts; i++) { + p = op[i].run(s, p, &op[i]); + } + + ngx_linefeed(p); + + buffer->pos = p; + + continue; + } + + if (buffer->event && buffer->event->timer_set) { + ngx_del_timer(buffer->event); + } + } + + alloc_line: + + line = ngx_pnalloc(s->connection->pool, len); + if (line == NULL) { + return NGX_ERROR; + } + + p = line; + + if (log[l].syslog_peer) { + p = ngx_syslog_add_header(log[l].syslog_peer, line); + } + + for (i = 0; i < log[l].format->ops->nelts; i++) { + p = op[i].run(s, p, &op[i]); + } + + if (log[l].syslog_peer) { + + size = p - line; + + n = ngx_syslog_send(log[l].syslog_peer, line, size); + + if (n < 0) { + ngx_log_error(NGX_LOG_WARN, s->connection->log, 0, + "send() to syslog failed"); + + } else if ((size_t) n != size) { + ngx_log_error(NGX_LOG_WARN, s->connection->log, 0, + "send() to syslog has written only %z of %uz", + n, size); + } + + continue; + } + + ngx_linefeed(p); + + ngx_stream_log_write(s, &log[l], line, p - line); + } + + return NGX_OK; +} + + +static void +ngx_stream_log_write(ngx_stream_session_t *s, ngx_stream_log_t *log, + u_char *buf, size_t len) +{ + u_char *name; + time_t now; + ssize_t n; + ngx_err_t err; +#if (NGX_ZLIB) + ngx_stream_log_buf_t *buffer; +#endif + + if (log->script == NULL) { + name = log->file->name.data; + +#if (NGX_ZLIB) + buffer = log->file->data; + + if (buffer && buffer->gzip) { + n = ngx_stream_log_gzip(log->file->fd, buf, len, buffer->gzip, + s->connection->log); + } else { + n = ngx_write_fd(log->file->fd, buf, len); + } +#else + n = ngx_write_fd(log->file->fd, buf, len); +#endif + + } else { + name = NULL; + n = ngx_stream_log_script_write(s, log->script, &name, buf, len); + } + + if (n == (ssize_t) len) { + return; + } + + now = ngx_time(); + + if (n == -1) { + err = ngx_errno; + + if (err == NGX_ENOSPC) { + log->disk_full_time = now; + } + + if (now - log->error_log_time > 59) { + ngx_log_error(NGX_LOG_ALERT, s->connection->log, err, + ngx_write_fd_n " to \"%s\" failed", name); + + log->error_log_time = now; + } + + return; + } + + if (now - log->error_log_time > 59) { + ngx_log_error(NGX_LOG_ALERT, s->connection->log, 0, + ngx_write_fd_n " to \"%s\" was incomplete: %z of %uz", + name, n, len); + + log->error_log_time = now; + } +} + + +static ssize_t +ngx_stream_log_script_write(ngx_stream_session_t *s, + ngx_stream_log_script_t *script, u_char **name, u_char *buf, size_t len) +{ + ssize_t n; + ngx_str_t log; + ngx_open_file_info_t of; + ngx_stream_log_srv_conf_t *lscf; + + if (ngx_stream_script_run(s, &log, script->lengths->elts, 1, + script->values->elts) + == NULL) + { + /* simulate successful logging */ + return len; + } + + log.data[log.len - 1] = '\0'; + *name = log.data; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream log \"%s\"", log.data); + + lscf = ngx_stream_get_module_srv_conf(s, ngx_stream_log_module); + + ngx_memzero(&of, sizeof(ngx_open_file_info_t)); + + of.log = 1; + of.valid = lscf->open_file_cache_valid; + of.min_uses = lscf->open_file_cache_min_uses; + of.directio = NGX_OPEN_FILE_DIRECTIO_OFF; + + if (ngx_open_cached_file(lscf->open_file_cache, &log, &of, + s->connection->pool) + != NGX_OK) + { + if (of.err == 0) { + /* simulate successful logging */ + return len; + } + + ngx_log_error(NGX_LOG_CRIT, s->connection->log, ngx_errno, + "%s \"%s\" failed", of.failed, log.data); + /* simulate successful logging */ + return len; + } + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream log #%d", of.fd); + + n = ngx_write_fd(of.fd, buf, len); + + return n; +} + + +#if (NGX_ZLIB) + +static ssize_t +ngx_stream_log_gzip(ngx_fd_t fd, u_char *buf, size_t len, ngx_int_t level, + ngx_log_t *log) +{ + int rc, wbits, memlevel; + u_char *out; + size_t size; + ssize_t n; + z_stream zstream; + ngx_err_t err; + ngx_pool_t *pool; + + wbits = MAX_WBITS; + memlevel = MAX_MEM_LEVEL - 1; + + while ((ssize_t) len < ((1 << (wbits - 1)) - 262)) { + wbits--; + memlevel--; + } + + /* + * This is a formula from deflateBound() for conservative upper bound of + * compressed data plus 18 bytes of gzip wrapper. + */ + + size = len + ((len + 7) >> 3) + ((len + 63) >> 6) + 5 + 18; + + ngx_memzero(&zstream, sizeof(z_stream)); + + pool = ngx_create_pool(256, log); + if (pool == NULL) { + /* simulate successful logging */ + return len; + } + + pool->log = log; + + zstream.zalloc = ngx_stream_log_gzip_alloc; + zstream.zfree = ngx_stream_log_gzip_free; + zstream.opaque = pool; + + out = ngx_pnalloc(pool, size); + if (out == NULL) { + goto done; + } + + zstream.next_in = buf; + zstream.avail_in = len; + zstream.next_out = out; + zstream.avail_out = size; + + rc = deflateInit2(&zstream, (int) level, Z_DEFLATED, wbits + 16, memlevel, + Z_DEFAULT_STRATEGY); + + if (rc != Z_OK) { + ngx_log_error(NGX_LOG_ALERT, log, 0, "deflateInit2() failed: %d", rc); + goto done; + } + + ngx_log_debug4(NGX_LOG_DEBUG_STREAM, log, 0, + "deflate in: ni:%p no:%p ai:%ud ao:%ud", + zstream.next_in, zstream.next_out, + zstream.avail_in, zstream.avail_out); + + rc = deflate(&zstream, Z_FINISH); + + if (rc != Z_STREAM_END) { + ngx_log_error(NGX_LOG_ALERT, log, 0, + "deflate(Z_FINISH) failed: %d", rc); + goto done; + } + + ngx_log_debug5(NGX_LOG_DEBUG_STREAM, log, 0, + "deflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", + zstream.next_in, zstream.next_out, + zstream.avail_in, zstream.avail_out, + rc); + + size -= zstream.avail_out; + + rc = deflateEnd(&zstream); + + if (rc != Z_OK) { + ngx_log_error(NGX_LOG_ALERT, log, 0, "deflateEnd() failed: %d", rc); + goto done; + } + + n = ngx_write_fd(fd, out, size); + + if (n != (ssize_t) size) { + err = (n == -1) ? ngx_errno : 0; + + ngx_destroy_pool(pool); + + ngx_set_errno(err); + return -1; + } + +done: + + ngx_destroy_pool(pool); + + /* simulate successful logging */ + return len; +} + + +static void * +ngx_stream_log_gzip_alloc(void *opaque, u_int items, u_int size) +{ + ngx_pool_t *pool = opaque; + + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, pool->log, 0, + "gzip alloc: n:%ud s:%ud", items, size); + + return ngx_palloc(pool, items * size); +} + + +static void +ngx_stream_log_gzip_free(void *opaque, void *address) +{ +#if 0 + ngx_pool_t *pool = opaque; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pool->log, 0, + "gzip free: %p", address); +#endif +} + +#endif + + +static void +ngx_stream_log_flush(ngx_open_file_t *file, ngx_log_t *log) +{ + size_t len; + ssize_t n; + ngx_stream_log_buf_t *buffer; + + buffer = file->data; + + len = buffer->pos - buffer->start; + + if (len == 0) { + return; + } + +#if (NGX_ZLIB) + if (buffer->gzip) { + n = ngx_stream_log_gzip(file->fd, buffer->start, len, buffer->gzip, + log); + } else { + n = ngx_write_fd(file->fd, buffer->start, len); + } +#else + n = ngx_write_fd(file->fd, buffer->start, len); +#endif + + if (n == -1) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + ngx_write_fd_n " to \"%s\" failed", + file->name.data); + + } else if ((size_t) n != len) { + ngx_log_error(NGX_LOG_ALERT, log, 0, + ngx_write_fd_n " to \"%s\" was incomplete: %z of %uz", + file->name.data, n, len); + } + + buffer->pos = buffer->start; + + if (buffer->event && buffer->event->timer_set) { + ngx_del_timer(buffer->event); + } +} + + +static void +ngx_stream_log_flush_handler(ngx_event_t *ev) +{ + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "stream log buffer flush handler"); + + ngx_stream_log_flush(ev->data, ev->log); +} + + +static u_char * +ngx_stream_log_copy_short(ngx_stream_session_t *s, u_char *buf, + ngx_stream_log_op_t *op) +{ + size_t len; + uintptr_t data; + + len = op->len; + data = op->data; + + while (len--) { + *buf++ = (u_char) (data & 0xff); + data >>= 8; + } + + return buf; +} + + +static u_char * +ngx_stream_log_copy_long(ngx_stream_session_t *s, u_char *buf, + ngx_stream_log_op_t *op) +{ + return ngx_cpymem(buf, (u_char *) op->data, op->len); +} + + +static ngx_int_t +ngx_stream_log_variable_compile(ngx_conf_t *cf, ngx_stream_log_op_t *op, + ngx_str_t *value, ngx_uint_t json) +{ + ngx_int_t index; + + index = ngx_stream_get_variable_index(cf, value); + if (index == NGX_ERROR) { + return NGX_ERROR; + } + + op->len = 0; + + if (json) { + op->getlen = ngx_stream_log_json_variable_getlen; + op->run = ngx_stream_log_json_variable; + + } else { + op->getlen = ngx_stream_log_variable_getlen; + op->run = ngx_stream_log_variable; + } + + op->data = index; + + return NGX_OK; +} + + +static size_t +ngx_stream_log_variable_getlen(ngx_stream_session_t *s, uintptr_t data) +{ + uintptr_t len; + ngx_stream_variable_value_t *value; + + value = ngx_stream_get_indexed_variable(s, data); + + if (value == NULL || value->not_found) { + return 1; + } + + len = ngx_stream_log_escape(NULL, value->data, value->len); + + value->escape = len ? 1 : 0; + + return value->len + len * 3; +} + + +static u_char * +ngx_stream_log_variable(ngx_stream_session_t *s, u_char *buf, + ngx_stream_log_op_t *op) +{ + ngx_stream_variable_value_t *value; + + value = ngx_stream_get_indexed_variable(s, op->data); + + if (value == NULL || value->not_found) { + *buf = '-'; + return buf + 1; + } + + if (value->escape == 0) { + return ngx_cpymem(buf, value->data, value->len); + + } else { + return (u_char *) ngx_stream_log_escape(buf, value->data, value->len); + } +} + + +static uintptr_t +ngx_stream_log_escape(u_char *dst, u_char *src, size_t size) +{ + ngx_uint_t n; + static u_char hex[] = "0123456789ABCDEF"; + + static uint32_t escape[] = { + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + + /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ + 0x00000004, /* 0000 0000 0000 0000 0000 0000 0000 0100 */ + + /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ + 0x10000000, /* 0001 0000 0000 0000 0000 0000 0000 0000 */ + + /* ~}| {zyx wvut srqp onml kjih gfed cba` */ + 0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */ + + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + }; + + + if (dst == NULL) { + + /* find the number of the characters to be escaped */ + + n = 0; + + while (size) { + if (escape[*src >> 5] & (1U << (*src & 0x1f))) { + n++; + } + src++; + size--; + } + + return (uintptr_t) n; + } + + while (size) { + if (escape[*src >> 5] & (1U << (*src & 0x1f))) { + *dst++ = '\\'; + *dst++ = 'x'; + *dst++ = hex[*src >> 4]; + *dst++ = hex[*src & 0xf]; + src++; + + } else { + *dst++ = *src++; + } + size--; + } + + return (uintptr_t) dst; +} + + +static size_t +ngx_stream_log_json_variable_getlen(ngx_stream_session_t *s, uintptr_t data) +{ + uintptr_t len; + ngx_stream_variable_value_t *value; + + value = ngx_stream_get_indexed_variable(s, data); + + if (value == NULL || value->not_found) { + return 0; + } + + len = ngx_escape_json(NULL, value->data, value->len); + + value->escape = len ? 1 : 0; + + return value->len + len; +} + + +static u_char * +ngx_stream_log_json_variable(ngx_stream_session_t *s, u_char *buf, + ngx_stream_log_op_t *op) +{ + ngx_stream_variable_value_t *value; + + value = ngx_stream_get_indexed_variable(s, op->data); + + if (value == NULL || value->not_found) { + return buf; + } + + if (value->escape == 0) { + return ngx_cpymem(buf, value->data, value->len); + + } else { + return (u_char *) ngx_escape_json(buf, value->data, value->len); + } +} + + +static void * +ngx_stream_log_create_main_conf(ngx_conf_t *cf) +{ + ngx_stream_log_main_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_log_main_conf_t)); + if (conf == NULL) { + return NULL; + } + + if (ngx_array_init(&conf->formats, cf->pool, 4, + sizeof(ngx_stream_log_fmt_t)) + != NGX_OK) + { + return NULL; + } + + return conf; +} + + +static void * +ngx_stream_log_create_srv_conf(ngx_conf_t *cf) +{ + ngx_stream_log_srv_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_log_srv_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->open_file_cache = NGX_CONF_UNSET_PTR; + + return conf; +} + + +static char * +ngx_stream_log_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_stream_log_srv_conf_t *prev = parent; + ngx_stream_log_srv_conf_t *conf = child; + + if (conf->open_file_cache == NGX_CONF_UNSET_PTR) { + + conf->open_file_cache = prev->open_file_cache; + conf->open_file_cache_valid = prev->open_file_cache_valid; + conf->open_file_cache_min_uses = prev->open_file_cache_min_uses; + + if (conf->open_file_cache == NGX_CONF_UNSET_PTR) { + conf->open_file_cache = NULL; + } + } + + if (conf->logs || conf->off) { + return NGX_CONF_OK; + } + + conf->logs = prev->logs; + conf->off = prev->off; + + return NGX_CONF_OK; +} + + +static char * +ngx_stream_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_log_srv_conf_t *lscf = conf; + + ssize_t size; + ngx_int_t gzip; + ngx_uint_t i, n; + ngx_msec_t flush; + ngx_str_t *value, name, s; + ngx_stream_log_t *log; + ngx_syslog_peer_t *peer; + ngx_stream_log_buf_t *buffer; + ngx_stream_log_fmt_t *fmt; + ngx_stream_script_compile_t sc; + ngx_stream_log_main_conf_t *lmcf; + ngx_stream_compile_complex_value_t ccv; + + value = cf->args->elts; + + if (ngx_strcmp(value[1].data, "off") == 0) { + lscf->off = 1; + if (cf->args->nelts == 2) { + return NGX_CONF_OK; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[2]); + return NGX_CONF_ERROR; + } + + if (lscf->logs == NULL) { + lscf->logs = ngx_array_create(cf->pool, 2, sizeof(ngx_stream_log_t)); + if (lscf->logs == NULL) { + return NGX_CONF_ERROR; + } + } + + lmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_log_module); + + log = ngx_array_push(lscf->logs); + if (log == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(log, sizeof(ngx_stream_log_t)); + + + if (ngx_strncmp(value[1].data, "syslog:", 7) == 0) { + + peer = ngx_pcalloc(cf->pool, sizeof(ngx_syslog_peer_t)); + if (peer == NULL) { + return NGX_CONF_ERROR; + } + + if (ngx_syslog_process_conf(cf, peer) != NGX_CONF_OK) { + return NGX_CONF_ERROR; + } + + log->syslog_peer = peer; + + goto process_formats; + } + + n = ngx_stream_script_variables_count(&value[1]); + + if (n == 0) { + log->file = ngx_conf_open_file(cf->cycle, &value[1]); + if (log->file == NULL) { + return NGX_CONF_ERROR; + } + + } else { + if (ngx_conf_full_name(cf->cycle, &value[1], 0) != NGX_OK) { + return NGX_CONF_ERROR; + } + + log->script = ngx_pcalloc(cf->pool, sizeof(ngx_stream_log_script_t)); + if (log->script == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(&sc, sizeof(ngx_stream_script_compile_t)); + + sc.cf = cf; + sc.source = &value[1]; + sc.lengths = &log->script->lengths; + sc.values = &log->script->values; + sc.variables = n; + sc.complete_lengths = 1; + sc.complete_values = 1; + + if (ngx_stream_script_compile(&sc) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + +process_formats: + + if (cf->args->nelts >= 3) { + name = value[2]; + + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "log format is not specified"); + return NGX_CONF_ERROR; + } + + fmt = lmcf->formats.elts; + for (i = 0; i < lmcf->formats.nelts; i++) { + if (fmt[i].name.len == name.len + && ngx_strcasecmp(fmt[i].name.data, name.data) == 0) + { + log->format = &fmt[i]; + break; + } + } + + if (log->format == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "unknown log format \"%V\"", &name); + return NGX_CONF_ERROR; + } + + size = 0; + flush = 0; + gzip = 0; + + for (i = 3; i < cf->args->nelts; i++) { + + if (ngx_strncmp(value[i].data, "buffer=", 7) == 0) { + s.len = value[i].len - 7; + s.data = value[i].data + 7; + + size = ngx_parse_size(&s); + + if (size == NGX_ERROR || size == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid buffer size \"%V\"", &s); + return NGX_CONF_ERROR; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "flush=", 6) == 0) { + s.len = value[i].len - 6; + s.data = value[i].data + 6; + + flush = ngx_parse_time(&s, 0); + + if (flush == (ngx_msec_t) NGX_ERROR || flush == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid flush time \"%V\"", &s); + return NGX_CONF_ERROR; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "gzip", 4) == 0 + && (value[i].len == 4 || value[i].data[4] == '=')) + { +#if (NGX_ZLIB) + if (size == 0) { + size = 64 * 1024; + } + + if (value[i].len == 4) { + gzip = Z_BEST_SPEED; + continue; + } + + s.len = value[i].len - 5; + s.data = value[i].data + 5; + + gzip = ngx_atoi(s.data, s.len); + + if (gzip < 1 || gzip > 9) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid compression level \"%V\"", &s); + return NGX_CONF_ERROR; + } + + continue; + +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "nginx was built without zlib support"); + return NGX_CONF_ERROR; +#endif + } + + if (ngx_strncmp(value[i].data, "if=", 3) == 0) { + s.len = value[i].len - 3; + s.data = value[i].data + 3; + + ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &s; + ccv.complex_value = ngx_palloc(cf->pool, + sizeof(ngx_stream_complex_value_t)); + if (ccv.complex_value == NULL) { + return NGX_CONF_ERROR; + } + + if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + log->filter = ccv.complex_value; + + continue; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + if (flush && size == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "no buffer is defined for access_log \"%V\"", + &value[1]); + return NGX_CONF_ERROR; + } + + if (size) { + + if (log->script) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "buffered logs cannot have variables in name"); + return NGX_CONF_ERROR; + } + + if (log->syslog_peer) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "logs to syslog cannot be buffered"); + return NGX_CONF_ERROR; + } + + if (log->file->data) { + buffer = log->file->data; + + if (buffer->last - buffer->start != size + || buffer->flush != flush + || buffer->gzip != gzip) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "access_log \"%V\" already defined " + "with conflicting parameters", + &value[1]); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; + } + + buffer = ngx_pcalloc(cf->pool, sizeof(ngx_stream_log_buf_t)); + if (buffer == NULL) { + return NGX_CONF_ERROR; + } + + buffer->start = ngx_pnalloc(cf->pool, size); + if (buffer->start == NULL) { + return NGX_CONF_ERROR; + } + + buffer->pos = buffer->start; + buffer->last = buffer->start + size; + + if (flush) { + buffer->event = ngx_pcalloc(cf->pool, sizeof(ngx_event_t)); + if (buffer->event == NULL) { + return NGX_CONF_ERROR; + } + + buffer->event->data = log->file; + buffer->event->handler = ngx_stream_log_flush_handler; + buffer->event->log = &cf->cycle->new_log; + buffer->event->cancelable = 1; + + buffer->flush = flush; + } + + buffer->gzip = gzip; + + log->file->flush = ngx_stream_log_flush; + log->file->data = buffer; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_stream_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_log_main_conf_t *lmcf = conf; + + ngx_str_t *value; + ngx_uint_t i; + ngx_stream_log_fmt_t *fmt; + + value = cf->args->elts; + + fmt = lmcf->formats.elts; + for (i = 0; i < lmcf->formats.nelts; i++) { + if (fmt[i].name.len == value[1].len + && ngx_strcmp(fmt[i].name.data, value[1].data) == 0) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "duplicate \"log_format\" name \"%V\"", + &value[1]); + return NGX_CONF_ERROR; + } + } + + fmt = ngx_array_push(&lmcf->formats); + if (fmt == NULL) { + return NGX_CONF_ERROR; + } + + fmt->name = value[1]; + + fmt->flushes = ngx_array_create(cf->pool, 4, sizeof(ngx_int_t)); + if (fmt->flushes == NULL) { + return NGX_CONF_ERROR; + } + + fmt->ops = ngx_array_create(cf->pool, 16, sizeof(ngx_stream_log_op_t)); + if (fmt->ops == NULL) { + return NGX_CONF_ERROR; + } + + return ngx_stream_log_compile_format(cf, fmt->flushes, fmt->ops, + cf->args, 2); +} + + +static char * +ngx_stream_log_compile_format(ngx_conf_t *cf, ngx_array_t *flushes, + ngx_array_t *ops, ngx_array_t *args, ngx_uint_t s) +{ + u_char *data, *p, ch; + size_t i, len; + ngx_str_t *value, var; + ngx_int_t *flush; + ngx_uint_t bracket, json; + ngx_stream_log_op_t *op; + + json = 0; + value = args->elts; + + if (s < args->nelts && ngx_strncmp(value[s].data, "escape=", 7) == 0) { + data = value[s].data + 7; + + if (ngx_strcmp(data, "json") == 0) { + json = 1; + + } else if (ngx_strcmp(data, "default") != 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "unknown log format escaping \"%s\"", data); + return NGX_CONF_ERROR; + } + + s++; + } + + for ( /* void */ ; s < args->nelts; s++) { + + i = 0; + + while (i < value[s].len) { + + op = ngx_array_push(ops); + if (op == NULL) { + return NGX_CONF_ERROR; + } + + data = &value[s].data[i]; + + if (value[s].data[i] == '$') { + + if (++i == value[s].len) { + goto invalid; + } + + if (value[s].data[i] == '{') { + bracket = 1; + + if (++i == value[s].len) { + goto invalid; + } + + var.data = &value[s].data[i]; + + } else { + bracket = 0; + var.data = &value[s].data[i]; + } + + for (var.len = 0; i < value[s].len; i++, var.len++) { + ch = value[s].data[i]; + + if (ch == '}' && bracket) { + i++; + bracket = 0; + break; + } + + if ((ch >= 'A' && ch <= 'Z') + || (ch >= 'a' && ch <= 'z') + || (ch >= '0' && ch <= '9') + || ch == '_') + { + continue; + } + + break; + } + + if (bracket) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the closing bracket in \"%V\" " + "variable is missing", &var); + return NGX_CONF_ERROR; + } + + if (var.len == 0) { + goto invalid; + } + + if (ngx_stream_log_variable_compile(cf, op, &var, json) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + if (flushes) { + + flush = ngx_array_push(flushes); + if (flush == NULL) { + return NGX_CONF_ERROR; + } + + *flush = op->data; /* variable index */ + } + + continue; + } + + i++; + + while (i < value[s].len && value[s].data[i] != '$') { + i++; + } + + len = &value[s].data[i] - data; + + if (len) { + + op->len = len; + op->getlen = NULL; + + if (len <= sizeof(uintptr_t)) { + op->run = ngx_stream_log_copy_short; + op->data = 0; + + while (len--) { + op->data <<= 8; + op->data |= data[len]; + } + + } else { + op->run = ngx_stream_log_copy_long; + + p = ngx_pnalloc(cf->pool, len); + if (p == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memcpy(p, data, len); + op->data = (uintptr_t) p; + } + } + } + } + + return NGX_CONF_OK; + +invalid: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%s\"", data); + + return NGX_CONF_ERROR; +} + + +static char * +ngx_stream_log_open_file_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_log_srv_conf_t *lscf = conf; + + time_t inactive, valid; + ngx_str_t *value, s; + ngx_int_t max, min_uses; + ngx_uint_t i; + + if (lscf->open_file_cache != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + value = cf->args->elts; + + max = 0; + inactive = 10; + valid = 60; + min_uses = 1; + + for (i = 1; i < cf->args->nelts; i++) { + + if (ngx_strncmp(value[i].data, "max=", 4) == 0) { + + max = ngx_atoi(value[i].data + 4, value[i].len - 4); + if (max == NGX_ERROR) { + goto failed; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "inactive=", 9) == 0) { + + s.len = value[i].len - 9; + s.data = value[i].data + 9; + + inactive = ngx_parse_time(&s, 1); + if (inactive == (time_t) NGX_ERROR) { + goto failed; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "min_uses=", 9) == 0) { + + min_uses = ngx_atoi(value[i].data + 9, value[i].len - 9); + if (min_uses == NGX_ERROR) { + goto failed; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "valid=", 6) == 0) { + + s.len = value[i].len - 6; + s.data = value[i].data + 6; + + valid = ngx_parse_time(&s, 1); + if (valid == (time_t) NGX_ERROR) { + goto failed; + } + + continue; + } + + if (ngx_strcmp(value[i].data, "off") == 0) { + + lscf->open_file_cache = NULL; + + continue; + } + + failed: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid \"open_log_file_cache\" parameter \"%V\"", + &value[i]); + return NGX_CONF_ERROR; + } + + if (lscf->open_file_cache == NULL) { + return NGX_CONF_OK; + } + + if (max == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"open_log_file_cache\" must have \"max\" parameter"); + return NGX_CONF_ERROR; + } + + lscf->open_file_cache = ngx_open_file_cache_init(cf->pool, max, inactive); + + if (lscf->open_file_cache) { + + lscf->open_file_cache_valid = valid; + lscf->open_file_cache_min_uses = min_uses; + + return NGX_CONF_OK; + } + + return NGX_CONF_ERROR; +} + + +static ngx_int_t +ngx_stream_log_init(ngx_conf_t *cf) +{ + ngx_stream_handler_pt *h; + ngx_stream_core_main_conf_t *cmcf; + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + + h = ngx_array_push(&cmcf->phases[NGX_STREAM_LOG_PHASE].handlers); + if (h == NULL) { + return NGX_ERROR; + } + + *h = ngx_stream_log_handler; + + return NGX_OK; +} diff --git a/app/nginx/src/stream/ngx_stream_map_module.c b/app/nginx/src/stream/ngx_stream_map_module.c new file mode 100644 index 0000000..ef06b2d --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_map_module.c @@ -0,0 +1,588 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef struct { + ngx_uint_t hash_max_size; + ngx_uint_t hash_bucket_size; +} ngx_stream_map_conf_t; + + +typedef struct { + ngx_hash_keys_arrays_t keys; + + ngx_array_t *values_hash; +#if (NGX_PCRE) + ngx_array_t regexes; +#endif + + ngx_stream_variable_value_t *default_value; + ngx_conf_t *cf; + unsigned hostnames:1; + unsigned no_cacheable:1; +} ngx_stream_map_conf_ctx_t; + + +typedef struct { + ngx_stream_map_t map; + ngx_stream_complex_value_t value; + ngx_stream_variable_value_t *default_value; + ngx_uint_t hostnames; /* unsigned hostnames:1 */ +} ngx_stream_map_ctx_t; + + +static int ngx_libc_cdecl ngx_stream_map_cmp_dns_wildcards(const void *one, + const void *two); +static void *ngx_stream_map_create_conf(ngx_conf_t *cf); +static char *ngx_stream_map_block(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_stream_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf); + + +static ngx_command_t ngx_stream_map_commands[] = { + + { ngx_string("map"), + NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2, + ngx_stream_map_block, + NGX_STREAM_MAIN_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("map_hash_max_size"), + NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_MAIN_CONF_OFFSET, + offsetof(ngx_stream_map_conf_t, hash_max_size), + NULL }, + + { ngx_string("map_hash_bucket_size"), + NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_MAIN_CONF_OFFSET, + offsetof(ngx_stream_map_conf_t, hash_bucket_size), + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_map_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + ngx_stream_map_create_conf, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_map_module = { + NGX_MODULE_V1, + &ngx_stream_map_module_ctx, /* module context */ + ngx_stream_map_commands, /* module directives */ + NGX_STREAM_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_stream_map_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, + uintptr_t data) +{ + ngx_stream_map_ctx_t *map = (ngx_stream_map_ctx_t *) data; + + ngx_str_t val, str; + ngx_stream_complex_value_t *cv; + ngx_stream_variable_value_t *value; + + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream map started"); + + if (ngx_stream_complex_value(s, &map->value, &val) != NGX_OK) { + return NGX_ERROR; + } + + if (map->hostnames && val.len > 0 && val.data[val.len - 1] == '.') { + val.len--; + } + + value = ngx_stream_map_find(s, &map->map, &val); + + if (value == NULL) { + value = map->default_value; + } + + if (!value->valid) { + cv = (ngx_stream_complex_value_t *) value->data; + + if (ngx_stream_complex_value(s, cv, &str) != NGX_OK) { + return NGX_ERROR; + } + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->len = str.len; + v->data = str.data; + + } else { + *v = *value; + } + + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream map: \"%V\" \"%v\"", &val, v); + + return NGX_OK; +} + + +static void * +ngx_stream_map_create_conf(ngx_conf_t *cf) +{ + ngx_stream_map_conf_t *mcf; + + mcf = ngx_palloc(cf->pool, sizeof(ngx_stream_map_conf_t)); + if (mcf == NULL) { + return NULL; + } + + mcf->hash_max_size = NGX_CONF_UNSET_UINT; + mcf->hash_bucket_size = NGX_CONF_UNSET_UINT; + + return mcf; +} + + +static char * +ngx_stream_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_map_conf_t *mcf = conf; + + char *rv; + ngx_str_t *value, name; + ngx_conf_t save; + ngx_pool_t *pool; + ngx_hash_init_t hash; + ngx_stream_map_ctx_t *map; + ngx_stream_variable_t *var; + ngx_stream_map_conf_ctx_t ctx; + ngx_stream_compile_complex_value_t ccv; + + if (mcf->hash_max_size == NGX_CONF_UNSET_UINT) { + mcf->hash_max_size = 2048; + } + + if (mcf->hash_bucket_size == NGX_CONF_UNSET_UINT) { + mcf->hash_bucket_size = ngx_cacheline_size; + + } else { + mcf->hash_bucket_size = ngx_align(mcf->hash_bucket_size, + ngx_cacheline_size); + } + + map = ngx_pcalloc(cf->pool, sizeof(ngx_stream_map_ctx_t)); + if (map == NULL) { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + + ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[1]; + ccv.complex_value = &map->value; + + if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + name = value[2]; + + if (name.data[0] != '$') { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid variable name \"%V\"", &name); + return NGX_CONF_ERROR; + } + + name.len--; + name.data++; + + var = ngx_stream_add_variable(cf, &name, NGX_STREAM_VAR_CHANGEABLE); + if (var == NULL) { + return NGX_CONF_ERROR; + } + + var->get_handler = ngx_stream_map_variable; + var->data = (uintptr_t) map; + + pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); + if (pool == NULL) { + return NGX_CONF_ERROR; + } + + ctx.keys.pool = cf->pool; + ctx.keys.temp_pool = pool; + + if (ngx_hash_keys_array_init(&ctx.keys, NGX_HASH_LARGE) != NGX_OK) { + ngx_destroy_pool(pool); + return NGX_CONF_ERROR; + } + + ctx.values_hash = ngx_pcalloc(pool, sizeof(ngx_array_t) * ctx.keys.hsize); + if (ctx.values_hash == NULL) { + ngx_destroy_pool(pool); + return NGX_CONF_ERROR; + } + +#if (NGX_PCRE) + if (ngx_array_init(&ctx.regexes, cf->pool, 2, + sizeof(ngx_stream_map_regex_t)) + != NGX_OK) + { + ngx_destroy_pool(pool); + return NGX_CONF_ERROR; + } +#endif + + ctx.default_value = NULL; + ctx.cf = &save; + ctx.hostnames = 0; + ctx.no_cacheable = 0; + + save = *cf; + cf->pool = pool; + cf->ctx = &ctx; + cf->handler = ngx_stream_map; + cf->handler_conf = conf; + + rv = ngx_conf_parse(cf, NULL); + + *cf = save; + + if (rv != NGX_CONF_OK) { + ngx_destroy_pool(pool); + return rv; + } + + if (ctx.no_cacheable) { + var->flags |= NGX_STREAM_VAR_NOCACHEABLE; + } + + map->default_value = ctx.default_value ? ctx.default_value: + &ngx_stream_variable_null_value; + + map->hostnames = ctx.hostnames; + + hash.key = ngx_hash_key_lc; + hash.max_size = mcf->hash_max_size; + hash.bucket_size = mcf->hash_bucket_size; + hash.name = "map_hash"; + hash.pool = cf->pool; + + if (ctx.keys.keys.nelts) { + hash.hash = &map->map.hash.hash; + hash.temp_pool = NULL; + + if (ngx_hash_init(&hash, ctx.keys.keys.elts, ctx.keys.keys.nelts) + != NGX_OK) + { + ngx_destroy_pool(pool); + return NGX_CONF_ERROR; + } + } + + if (ctx.keys.dns_wc_head.nelts) { + + ngx_qsort(ctx.keys.dns_wc_head.elts, + (size_t) ctx.keys.dns_wc_head.nelts, + sizeof(ngx_hash_key_t), ngx_stream_map_cmp_dns_wildcards); + + hash.hash = NULL; + hash.temp_pool = pool; + + if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_head.elts, + ctx.keys.dns_wc_head.nelts) + != NGX_OK) + { + ngx_destroy_pool(pool); + return NGX_CONF_ERROR; + } + + map->map.hash.wc_head = (ngx_hash_wildcard_t *) hash.hash; + } + + if (ctx.keys.dns_wc_tail.nelts) { + + ngx_qsort(ctx.keys.dns_wc_tail.elts, + (size_t) ctx.keys.dns_wc_tail.nelts, + sizeof(ngx_hash_key_t), ngx_stream_map_cmp_dns_wildcards); + + hash.hash = NULL; + hash.temp_pool = pool; + + if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_tail.elts, + ctx.keys.dns_wc_tail.nelts) + != NGX_OK) + { + ngx_destroy_pool(pool); + return NGX_CONF_ERROR; + } + + map->map.hash.wc_tail = (ngx_hash_wildcard_t *) hash.hash; + } + +#if (NGX_PCRE) + + if (ctx.regexes.nelts) { + map->map.regex = ctx.regexes.elts; + map->map.nregex = ctx.regexes.nelts; + } + +#endif + + ngx_destroy_pool(pool); + + return rv; +} + + +static int ngx_libc_cdecl +ngx_stream_map_cmp_dns_wildcards(const void *one, const void *two) +{ + ngx_hash_key_t *first, *second; + + first = (ngx_hash_key_t *) one; + second = (ngx_hash_key_t *) two; + + return ngx_dns_strcmp(first->key.data, second->key.data); +} + + +static char * +ngx_stream_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) +{ + u_char *data; + size_t len; + ngx_int_t rv; + ngx_str_t *value, v; + ngx_uint_t i, key; + ngx_stream_map_conf_ctx_t *ctx; + ngx_stream_complex_value_t cv, *cvp; + ngx_stream_variable_value_t *var, **vp; + ngx_stream_compile_complex_value_t ccv; + + ctx = cf->ctx; + + value = cf->args->elts; + + if (cf->args->nelts == 1 + && ngx_strcmp(value[0].data, "hostnames") == 0) + { + ctx->hostnames = 1; + return NGX_CONF_OK; + } + + if (cf->args->nelts == 1 + && ngx_strcmp(value[0].data, "volatile") == 0) + { + ctx->no_cacheable = 1; + return NGX_CONF_OK; + } + + if (cf->args->nelts != 2) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid number of the map parameters"); + return NGX_CONF_ERROR; + } + + if (ngx_strcmp(value[0].data, "include") == 0) { + return ngx_conf_include(cf, dummy, conf); + } + + key = 0; + + for (i = 0; i < value[1].len; i++) { + key = ngx_hash(key, value[1].data[i]); + } + + key %= ctx->keys.hsize; + + vp = ctx->values_hash[key].elts; + + if (vp) { + for (i = 0; i < ctx->values_hash[key].nelts; i++) { + + if (vp[i]->valid) { + data = vp[i]->data; + len = vp[i]->len; + + } else { + cvp = (ngx_stream_complex_value_t *) vp[i]->data; + data = cvp->value.data; + len = cvp->value.len; + } + + if (value[1].len != len) { + continue; + } + + if (ngx_strncmp(value[1].data, data, len) == 0) { + var = vp[i]; + goto found; + } + } + + } else { + if (ngx_array_init(&ctx->values_hash[key], cf->pool, 4, + sizeof(ngx_stream_variable_value_t *)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + } + + var = ngx_palloc(ctx->keys.pool, sizeof(ngx_stream_variable_value_t)); + if (var == NULL) { + return NGX_CONF_ERROR; + } + + v.len = value[1].len; + v.data = ngx_pstrdup(ctx->keys.pool, &value[1]); + if (v.data == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t)); + + ccv.cf = ctx->cf; + ccv.value = &v; + ccv.complex_value = &cv; + + if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (cv.lengths != NULL) { + cvp = ngx_palloc(ctx->keys.pool, sizeof(ngx_stream_complex_value_t)); + if (cvp == NULL) { + return NGX_CONF_ERROR; + } + + *cvp = cv; + + var->len = 0; + var->data = (u_char *) cvp; + var->valid = 0; + + } else { + var->len = v.len; + var->data = v.data; + var->valid = 1; + } + + var->no_cacheable = 0; + var->not_found = 0; + + vp = ngx_array_push(&ctx->values_hash[key]); + if (vp == NULL) { + return NGX_CONF_ERROR; + } + + *vp = var; + +found: + + if (ngx_strcmp(value[0].data, "default") == 0) { + + if (ctx->default_value) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "duplicate default map parameter"); + return NGX_CONF_ERROR; + } + + ctx->default_value = var; + + return NGX_CONF_OK; + } + +#if (NGX_PCRE) + + if (value[0].len && value[0].data[0] == '~') { + ngx_regex_compile_t rc; + ngx_stream_map_regex_t *regex; + u_char errstr[NGX_MAX_CONF_ERRSTR]; + + regex = ngx_array_push(&ctx->regexes); + if (regex == NULL) { + return NGX_CONF_ERROR; + } + + value[0].len--; + value[0].data++; + + ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); + + if (value[0].data[0] == '*') { + value[0].len--; + value[0].data++; + rc.options = NGX_REGEX_CASELESS; + } + + rc.pattern = value[0]; + rc.err.len = NGX_MAX_CONF_ERRSTR; + rc.err.data = errstr; + + regex->regex = ngx_stream_regex_compile(ctx->cf, &rc); + if (regex->regex == NULL) { + return NGX_CONF_ERROR; + } + + regex->value = var; + + return NGX_CONF_OK; + } + +#endif + + if (value[0].len && value[0].data[0] == '\\') { + value[0].len--; + value[0].data++; + } + + rv = ngx_hash_add_key(&ctx->keys, &value[0], var, + (ctx->hostnames) ? NGX_HASH_WILDCARD_KEY : 0); + + if (rv == NGX_OK) { + return NGX_CONF_OK; + } + + if (rv == NGX_DECLINED) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid hostname or wildcard \"%V\"", &value[0]); + } + + if (rv == NGX_BUSY) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "conflicting parameter \"%V\"", &value[0]); + } + + return NGX_CONF_ERROR; +} diff --git a/app/nginx/src/stream/ngx_stream_proxy_module.c b/app/nginx/src/stream/ngx_stream_proxy_module.c new file mode 100644 index 0000000..81a0891 --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_proxy_module.c @@ -0,0 +1,2170 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef struct { + ngx_addr_t *addr; + ngx_stream_complex_value_t *value; +#if (NGX_HAVE_TRANSPARENT_PROXY) + ngx_uint_t transparent; /* unsigned transparent:1; */ +#endif +} ngx_stream_upstream_local_t; + + +typedef struct { + ngx_msec_t connect_timeout; + ngx_msec_t timeout; + ngx_msec_t next_upstream_timeout; + size_t buffer_size; + size_t upload_rate; + size_t download_rate; + ngx_uint_t responses; + ngx_uint_t next_upstream_tries; + ngx_flag_t next_upstream; + ngx_flag_t proxy_protocol; + ngx_stream_upstream_local_t *local; + +#if (NGX_STREAM_SSL) + ngx_flag_t ssl_enable; + ngx_flag_t ssl_session_reuse; + ngx_uint_t ssl_protocols; + ngx_str_t ssl_ciphers; + ngx_stream_complex_value_t *ssl_name; + ngx_flag_t ssl_server_name; + + ngx_flag_t ssl_verify; + ngx_uint_t ssl_verify_depth; + ngx_str_t ssl_trusted_certificate; + ngx_str_t ssl_crl; + ngx_str_t ssl_certificate; + ngx_str_t ssl_certificate_key; + ngx_array_t *ssl_passwords; + + ngx_ssl_t *ssl; +#endif + + ngx_stream_upstream_srv_conf_t *upstream; + ngx_stream_complex_value_t *upstream_value; +} ngx_stream_proxy_srv_conf_t; + + +static void ngx_stream_proxy_handler(ngx_stream_session_t *s); +static ngx_int_t ngx_stream_proxy_eval(ngx_stream_session_t *s, + ngx_stream_proxy_srv_conf_t *pscf); +static ngx_int_t ngx_stream_proxy_set_local(ngx_stream_session_t *s, + ngx_stream_upstream_t *u, ngx_stream_upstream_local_t *local); +static void ngx_stream_proxy_connect(ngx_stream_session_t *s); +static void ngx_stream_proxy_init_upstream(ngx_stream_session_t *s); +static void ngx_stream_proxy_resolve_handler(ngx_resolver_ctx_t *ctx); +static void ngx_stream_proxy_upstream_handler(ngx_event_t *ev); +static void ngx_stream_proxy_downstream_handler(ngx_event_t *ev); +static void ngx_stream_proxy_process_connection(ngx_event_t *ev, + ngx_uint_t from_upstream); +static void ngx_stream_proxy_connect_handler(ngx_event_t *ev); +static ngx_int_t ngx_stream_proxy_test_connect(ngx_connection_t *c); +static void ngx_stream_proxy_process(ngx_stream_session_t *s, + ngx_uint_t from_upstream, ngx_uint_t do_write); +static void ngx_stream_proxy_next_upstream(ngx_stream_session_t *s); +static void ngx_stream_proxy_finalize(ngx_stream_session_t *s, ngx_uint_t rc); +static u_char *ngx_stream_proxy_log_error(ngx_log_t *log, u_char *buf, + size_t len); + +static void *ngx_stream_proxy_create_srv_conf(ngx_conf_t *cf); +static char *ngx_stream_proxy_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); +static char *ngx_stream_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_stream_proxy_bind(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +#if (NGX_STREAM_SSL) + +static ngx_int_t ngx_stream_proxy_send_proxy_protocol(ngx_stream_session_t *s); +static char *ngx_stream_proxy_ssl_password_file(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); +static void ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s); +static void ngx_stream_proxy_ssl_handshake(ngx_connection_t *pc); +static ngx_int_t ngx_stream_proxy_ssl_name(ngx_stream_session_t *s); +static ngx_int_t ngx_stream_proxy_set_ssl(ngx_conf_t *cf, + ngx_stream_proxy_srv_conf_t *pscf); + + +static ngx_conf_bitmask_t ngx_stream_proxy_ssl_protocols[] = { + { ngx_string("SSLv2"), NGX_SSL_SSLv2 }, + { ngx_string("SSLv3"), NGX_SSL_SSLv3 }, + { ngx_string("TLSv1"), NGX_SSL_TLSv1 }, + { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 }, + { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 }, + { ngx_null_string, 0 } +}; + +#endif + + +static ngx_conf_deprecated_t ngx_conf_deprecated_proxy_downstream_buffer = { + ngx_conf_deprecated, "proxy_downstream_buffer", "proxy_buffer_size" +}; + +static ngx_conf_deprecated_t ngx_conf_deprecated_proxy_upstream_buffer = { + ngx_conf_deprecated, "proxy_upstream_buffer", "proxy_buffer_size" +}; + + +static ngx_command_t ngx_stream_proxy_commands[] = { + + { ngx_string("proxy_pass"), + NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_stream_proxy_pass, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("proxy_bind"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE12, + ngx_stream_proxy_bind, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("proxy_connect_timeout"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, connect_timeout), + NULL }, + + { ngx_string("proxy_timeout"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, timeout), + NULL }, + + { ngx_string("proxy_buffer_size"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, buffer_size), + NULL }, + + { ngx_string("proxy_downstream_buffer"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, buffer_size), + &ngx_conf_deprecated_proxy_downstream_buffer }, + + { ngx_string("proxy_upstream_buffer"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, buffer_size), + &ngx_conf_deprecated_proxy_upstream_buffer }, + + { ngx_string("proxy_upload_rate"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, upload_rate), + NULL }, + + { ngx_string("proxy_download_rate"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, download_rate), + NULL }, + + { ngx_string("proxy_responses"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, responses), + NULL }, + + { ngx_string("proxy_next_upstream"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, next_upstream), + NULL }, + + { ngx_string("proxy_next_upstream_tries"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, next_upstream_tries), + NULL }, + + { ngx_string("proxy_next_upstream_timeout"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, next_upstream_timeout), + NULL }, + + { ngx_string("proxy_protocol"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, proxy_protocol), + NULL }, + +#if (NGX_STREAM_SSL) + + { ngx_string("proxy_ssl"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, ssl_enable), + NULL }, + + { ngx_string("proxy_ssl_session_reuse"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, ssl_session_reuse), + NULL }, + + { ngx_string("proxy_ssl_protocols"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, + ngx_conf_set_bitmask_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, ssl_protocols), + &ngx_stream_proxy_ssl_protocols }, + + { ngx_string("proxy_ssl_ciphers"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, ssl_ciphers), + NULL }, + + { ngx_string("proxy_ssl_name"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_stream_set_complex_value_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, ssl_name), + NULL }, + + { ngx_string("proxy_ssl_server_name"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, ssl_server_name), + NULL }, + + { ngx_string("proxy_ssl_verify"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, ssl_verify), + NULL }, + + { ngx_string("proxy_ssl_verify_depth"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, ssl_verify_depth), + NULL }, + + { ngx_string("proxy_ssl_trusted_certificate"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, ssl_trusted_certificate), + NULL }, + + { ngx_string("proxy_ssl_crl"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, ssl_crl), + NULL }, + + { ngx_string("proxy_ssl_certificate"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, ssl_certificate), + NULL }, + + { ngx_string("proxy_ssl_certificate_key"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, ssl_certificate_key), + NULL }, + + { ngx_string("proxy_ssl_password_file"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_stream_proxy_ssl_password_file, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + +#endif + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_proxy_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_stream_proxy_create_srv_conf, /* create server configuration */ + ngx_stream_proxy_merge_srv_conf /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_proxy_module = { + NGX_MODULE_V1, + &ngx_stream_proxy_module_ctx, /* module context */ + ngx_stream_proxy_commands, /* module directives */ + NGX_STREAM_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 void +ngx_stream_proxy_handler(ngx_stream_session_t *s) +{ + u_char *p; + ngx_str_t *host; + ngx_uint_t i; + ngx_connection_t *c; + ngx_resolver_ctx_t *ctx, temp; + ngx_stream_upstream_t *u; + ngx_stream_core_srv_conf_t *cscf; + ngx_stream_proxy_srv_conf_t *pscf; + ngx_stream_upstream_srv_conf_t *uscf, **uscfp; + ngx_stream_upstream_main_conf_t *umcf; + + c = s->connection; + + pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module); + + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, + "proxy connection handler"); + + u = ngx_pcalloc(c->pool, sizeof(ngx_stream_upstream_t)); + if (u == NULL) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + s->upstream = u; + + s->log_handler = ngx_stream_proxy_log_error; + + u->peer.log = c->log; + u->peer.log_error = NGX_ERROR_ERR; + + if (ngx_stream_proxy_set_local(s, u, pscf->local) != NGX_OK) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + u->peer.type = c->type; + u->start_sec = ngx_time(); + + c->write->handler = ngx_stream_proxy_downstream_handler; + c->read->handler = ngx_stream_proxy_downstream_handler; + + s->upstream_states = ngx_array_create(c->pool, 1, + sizeof(ngx_stream_upstream_state_t)); + if (s->upstream_states == NULL) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + if (c->type == SOCK_STREAM) { + p = ngx_pnalloc(c->pool, pscf->buffer_size); + if (p == NULL) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + u->downstream_buf.start = p; + u->downstream_buf.end = p + pscf->buffer_size; + u->downstream_buf.pos = p; + u->downstream_buf.last = p; + + if (c->read->ready) { + ngx_post_event(c->read, &ngx_posted_events); + } + } + + if (pscf->upstream_value) { + if (ngx_stream_proxy_eval(s, pscf) != NGX_OK) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + } + + if (u->resolved == NULL) { + + uscf = pscf->upstream; + + } else { + +#if (NGX_STREAM_SSL) + u->ssl_name = u->resolved->host; +#endif + + host = &u->resolved->host; + + umcf = ngx_stream_get_module_main_conf(s, ngx_stream_upstream_module); + + uscfp = umcf->upstreams.elts; + + for (i = 0; i < umcf->upstreams.nelts; i++) { + + uscf = uscfp[i]; + + if (uscf->host.len == host->len + && ((uscf->port == 0 && u->resolved->no_port) + || uscf->port == u->resolved->port) + && ngx_strncasecmp(uscf->host.data, host->data, host->len) == 0) + { + goto found; + } + } + + if (u->resolved->sockaddr) { + + if (u->resolved->port == 0 + && u->resolved->sockaddr->sa_family != AF_UNIX) + { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "no port in upstream \"%V\"", host); + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + if (ngx_stream_upstream_create_round_robin_peer(s, u->resolved) + != NGX_OK) + { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + ngx_stream_proxy_connect(s); + + return; + } + + if (u->resolved->port == 0) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "no port in upstream \"%V\"", host); + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + temp.name = *host; + + cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module); + + ctx = ngx_resolve_start(cscf->resolver, &temp); + if (ctx == NULL) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + if (ctx == NGX_NO_RESOLVER) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "no resolver defined to resolve %V", host); + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + ctx->name = *host; + ctx->handler = ngx_stream_proxy_resolve_handler; + ctx->data = s; + ctx->timeout = cscf->resolver_timeout; + + u->resolved->ctx = ctx; + + if (ngx_resolve_name(ctx) != NGX_OK) { + u->resolved->ctx = NULL; + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + return; + } + +found: + + if (uscf == NULL) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, "no upstream configuration"); + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + u->upstream = uscf; + +#if (NGX_STREAM_SSL) + u->ssl_name = uscf->host; +#endif + + if (uscf->peer.init(s, uscf) != NGX_OK) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + u->peer.start_time = ngx_current_msec; + + if (pscf->next_upstream_tries + && u->peer.tries > pscf->next_upstream_tries) + { + u->peer.tries = pscf->next_upstream_tries; + } + + ngx_stream_proxy_connect(s); +} + + +static ngx_int_t +ngx_stream_proxy_eval(ngx_stream_session_t *s, + ngx_stream_proxy_srv_conf_t *pscf) +{ + ngx_str_t host; + ngx_url_t url; + ngx_stream_upstream_t *u; + + if (ngx_stream_complex_value(s, pscf->upstream_value, &host) != NGX_OK) { + return NGX_ERROR; + } + + ngx_memzero(&url, sizeof(ngx_url_t)); + + url.url = host; + url.no_resolve = 1; + + if (ngx_parse_url(s->connection->pool, &url) != NGX_OK) { + if (url.err) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "%s in upstream \"%V\"", url.err, &url.url); + } + + return NGX_ERROR; + } + + u = s->upstream; + + u->resolved = ngx_pcalloc(s->connection->pool, + sizeof(ngx_stream_upstream_resolved_t)); + if (u->resolved == NULL) { + return NGX_ERROR; + } + + if (url.addrs) { + u->resolved->sockaddr = url.addrs[0].sockaddr; + u->resolved->socklen = url.addrs[0].socklen; + u->resolved->name = url.addrs[0].name; + u->resolved->naddrs = 1; + } + + u->resolved->host = url.host; + u->resolved->port = url.port; + u->resolved->no_port = url.no_port; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_proxy_set_local(ngx_stream_session_t *s, ngx_stream_upstream_t *u, + ngx_stream_upstream_local_t *local) +{ + ngx_int_t rc; + ngx_str_t val; + ngx_addr_t *addr; + + if (local == NULL) { + u->peer.local = NULL; + return NGX_OK; + } + +#if (NGX_HAVE_TRANSPARENT_PROXY) + u->peer.transparent = local->transparent; +#endif + + if (local->value == NULL) { + u->peer.local = local->addr; + return NGX_OK; + } + + if (ngx_stream_complex_value(s, local->value, &val) != NGX_OK) { + return NGX_ERROR; + } + + if (val.len == 0) { + return NGX_OK; + } + + addr = ngx_palloc(s->connection->pool, sizeof(ngx_addr_t)); + if (addr == NULL) { + return NGX_ERROR; + } + + rc = ngx_parse_addr_port(s->connection->pool, addr, val.data, val.len); + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "invalid local address \"%V\"", &val); + return NGX_OK; + } + + addr->name = val; + u->peer.local = addr; + + return NGX_OK; +} + + +static void +ngx_stream_proxy_connect(ngx_stream_session_t *s) +{ + ngx_int_t rc; + ngx_connection_t *c, *pc; + ngx_stream_upstream_t *u; + ngx_stream_proxy_srv_conf_t *pscf; + + c = s->connection; + + c->log->action = "connecting to upstream"; + + pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module); + + u = s->upstream; + + u->connected = 0; + u->proxy_protocol = pscf->proxy_protocol; + + if (u->state) { + u->state->response_time = ngx_current_msec - u->state->response_time; + } + + u->state = ngx_array_push(s->upstream_states); + if (u->state == NULL) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + ngx_memzero(u->state, sizeof(ngx_stream_upstream_state_t)); + + u->state->connect_time = (ngx_msec_t) -1; + u->state->first_byte_time = (ngx_msec_t) -1; + u->state->response_time = ngx_current_msec; + + rc = ngx_event_connect_peer(&u->peer); + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "proxy connect: %i", rc); + + if (rc == NGX_ERROR) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + u->state->peer = u->peer.name; + + if (rc == NGX_BUSY) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "no live upstreams"); + ngx_stream_proxy_finalize(s, NGX_STREAM_BAD_GATEWAY); + return; + } + + if (rc == NGX_DECLINED) { + ngx_stream_proxy_next_upstream(s); + return; + } + + /* rc == NGX_OK || rc == NGX_AGAIN || rc == NGX_DONE */ + + pc = u->peer.connection; + + pc->data = s; + pc->log = c->log; + pc->pool = c->pool; + pc->read->log = c->log; + pc->write->log = c->log; + + if (rc != NGX_AGAIN) { + ngx_stream_proxy_init_upstream(s); + return; + } + + pc->read->handler = ngx_stream_proxy_connect_handler; + pc->write->handler = ngx_stream_proxy_connect_handler; + + ngx_add_timer(pc->write, pscf->connect_timeout); +} + + +static void +ngx_stream_proxy_init_upstream(ngx_stream_session_t *s) +{ + int tcp_nodelay; + u_char *p; + ngx_chain_t *cl; + ngx_connection_t *c, *pc; + ngx_log_handler_pt handler; + ngx_stream_upstream_t *u; + ngx_stream_core_srv_conf_t *cscf; + ngx_stream_proxy_srv_conf_t *pscf; + + u = s->upstream; + pc = u->peer.connection; + + cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module); + + if (pc->type == SOCK_STREAM + && cscf->tcp_nodelay + && pc->tcp_nodelay == NGX_TCP_NODELAY_UNSET) + { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, pc->log, 0, "tcp_nodelay"); + + tcp_nodelay = 1; + + if (setsockopt(pc->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) == -1) + { + ngx_connection_error(pc, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + ngx_stream_proxy_next_upstream(s); + return; + } + + pc->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + + pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module); + +#if (NGX_STREAM_SSL) + + if (pc->type == SOCK_STREAM && pscf->ssl) { + + if (u->proxy_protocol) { + if (ngx_stream_proxy_send_proxy_protocol(s) != NGX_OK) { + return; + } + + u->proxy_protocol = 0; + } + + if (pc->ssl == NULL) { + ngx_stream_proxy_ssl_init_connection(s); + return; + } + } + +#endif + + c = s->connection; + + if (c->log->log_level >= NGX_LOG_INFO) { + ngx_str_t str; + u_char addr[NGX_SOCKADDR_STRLEN]; + + str.len = NGX_SOCKADDR_STRLEN; + str.data = addr; + + if (ngx_connection_local_sockaddr(pc, &str, 1) == NGX_OK) { + handler = c->log->handler; + c->log->handler = NULL; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "%sproxy %V connected to %V", + pc->type == SOCK_DGRAM ? "udp " : "", + &str, u->peer.name); + + c->log->handler = handler; + } + } + + u->state->connect_time = ngx_current_msec - u->state->response_time; + + if (u->peer.notify) { + u->peer.notify(&u->peer, u->peer.data, + NGX_STREAM_UPSTREAM_NOTIFY_CONNECT); + } + + c->log->action = "proxying connection"; + + if (u->upstream_buf.start == NULL) { + p = ngx_pnalloc(c->pool, pscf->buffer_size); + if (p == NULL) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + u->upstream_buf.start = p; + u->upstream_buf.end = p + pscf->buffer_size; + u->upstream_buf.pos = p; + u->upstream_buf.last = p; + } + + if (c->buffer && c->buffer->pos < c->buffer->last) { + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "stream proxy add preread buffer: %uz", + c->buffer->last - c->buffer->pos); + + cl = ngx_chain_get_free_buf(c->pool, &u->free); + if (cl == NULL) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + *cl->buf = *c->buffer; + + cl->buf->tag = (ngx_buf_tag_t) &ngx_stream_proxy_module; + cl->buf->flush = 1; + cl->buf->last_buf = (c->type == SOCK_DGRAM); + + cl->next = u->upstream_out; + u->upstream_out = cl; + } + + if (u->proxy_protocol) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, + "stream proxy add PROXY protocol header"); + + cl = ngx_chain_get_free_buf(c->pool, &u->free); + if (cl == NULL) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + p = ngx_pnalloc(c->pool, NGX_PROXY_PROTOCOL_MAX_HEADER); + if (p == NULL) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + cl->buf->pos = p; + + p = ngx_proxy_protocol_write(c, p, p + NGX_PROXY_PROTOCOL_MAX_HEADER); + if (p == NULL) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + cl->buf->last = p; + cl->buf->temporary = 1; + cl->buf->flush = 0; + cl->buf->last_buf = 0; + cl->buf->tag = (ngx_buf_tag_t) &ngx_stream_proxy_module; + + cl->next = u->upstream_out; + u->upstream_out = cl; + + u->proxy_protocol = 0; + } + + if (c->type == SOCK_DGRAM && pscf->responses == 0) { + pc->read->ready = 0; + pc->read->eof = 1; + } + + u->connected = 1; + + pc->read->handler = ngx_stream_proxy_upstream_handler; + pc->write->handler = ngx_stream_proxy_upstream_handler; + + if (pc->read->ready || pc->read->eof) { + ngx_post_event(pc->read, &ngx_posted_events); + } + + ngx_stream_proxy_process(s, 0, 1); +} + + +#if (NGX_STREAM_SSL) + +static ngx_int_t +ngx_stream_proxy_send_proxy_protocol(ngx_stream_session_t *s) +{ + u_char *p; + ssize_t n, size; + ngx_connection_t *c, *pc; + ngx_stream_upstream_t *u; + ngx_stream_proxy_srv_conf_t *pscf; + u_char buf[NGX_PROXY_PROTOCOL_MAX_HEADER]; + + c = s->connection; + + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, + "stream proxy send PROXY protocol header"); + + p = ngx_proxy_protocol_write(c, buf, buf + NGX_PROXY_PROTOCOL_MAX_HEADER); + if (p == NULL) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + u = s->upstream; + + pc = u->peer.connection; + + size = p - buf; + + n = pc->send(pc, buf, size); + + if (n == NGX_AGAIN) { + if (ngx_handle_write_event(pc->write, 0) != NGX_OK) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module); + + ngx_add_timer(pc->write, pscf->timeout); + + pc->write->handler = ngx_stream_proxy_connect_handler; + + return NGX_AGAIN; + } + + if (n == NGX_ERROR) { + ngx_stream_proxy_finalize(s, NGX_STREAM_OK); + return NGX_ERROR; + } + + if (n != size) { + + /* + * PROXY protocol specification: + * The sender must always ensure that the header + * is sent at once, so that the transport layer + * maintains atomicity along the path to the receiver. + */ + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "could not send PROXY protocol header at once"); + + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + + return NGX_ERROR; + } + + return NGX_OK; +} + + +static char * +ngx_stream_proxy_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + ngx_stream_proxy_srv_conf_t *pscf = conf; + + ngx_str_t *value; + + if (pscf->ssl_passwords != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + value = cf->args->elts; + + pscf->ssl_passwords = ngx_ssl_read_password_file(cf, &value[1]); + + if (pscf->ssl_passwords == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static void +ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s) +{ + ngx_int_t rc; + ngx_connection_t *pc; + ngx_stream_upstream_t *u; + ngx_stream_proxy_srv_conf_t *pscf; + + u = s->upstream; + + pc = u->peer.connection; + + pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module); + + if (ngx_ssl_create_connection(pscf->ssl, pc, NGX_SSL_BUFFER|NGX_SSL_CLIENT) + != NGX_OK) + { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + if (pscf->ssl_server_name || pscf->ssl_verify) { + if (ngx_stream_proxy_ssl_name(s) != NGX_OK) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + } + + if (pscf->ssl_session_reuse) { + if (u->peer.set_session(&u->peer, u->peer.data) != NGX_OK) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + } + + s->connection->log->action = "SSL handshaking to upstream"; + + rc = ngx_ssl_handshake(pc); + + if (rc == NGX_AGAIN) { + + if (!pc->write->timer_set) { + ngx_add_timer(pc->write, pscf->connect_timeout); + } + + pc->ssl->handler = ngx_stream_proxy_ssl_handshake; + return; + } + + ngx_stream_proxy_ssl_handshake(pc); +} + + +static void +ngx_stream_proxy_ssl_handshake(ngx_connection_t *pc) +{ + long rc; + ngx_stream_session_t *s; + ngx_stream_upstream_t *u; + ngx_stream_proxy_srv_conf_t *pscf; + + s = pc->data; + + pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module); + + if (pc->ssl->handshaked) { + + if (pscf->ssl_verify) { + rc = SSL_get_verify_result(pc->ssl->connection); + + if (rc != X509_V_OK) { + ngx_log_error(NGX_LOG_ERR, pc->log, 0, + "upstream SSL certificate verify error: (%l:%s)", + rc, X509_verify_cert_error_string(rc)); + goto failed; + } + + u = s->upstream; + + if (ngx_ssl_check_host(pc, &u->ssl_name) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, pc->log, 0, + "upstream SSL certificate does not match \"%V\"", + &u->ssl_name); + goto failed; + } + } + + if (pscf->ssl_session_reuse) { + u = s->upstream; + u->peer.save_session(&u->peer, u->peer.data); + } + + if (pc->write->timer_set) { + ngx_del_timer(pc->write); + } + + ngx_stream_proxy_init_upstream(s); + + return; + } + +failed: + + ngx_stream_proxy_next_upstream(s); +} + + +static ngx_int_t +ngx_stream_proxy_ssl_name(ngx_stream_session_t *s) +{ + u_char *p, *last; + ngx_str_t name; + ngx_stream_upstream_t *u; + ngx_stream_proxy_srv_conf_t *pscf; + + pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module); + + u = s->upstream; + + if (pscf->ssl_name) { + if (ngx_stream_complex_value(s, pscf->ssl_name, &name) != NGX_OK) { + return NGX_ERROR; + } + + } else { + name = u->ssl_name; + } + + if (name.len == 0) { + goto done; + } + + /* + * ssl name here may contain port, strip it for compatibility + * with the http module + */ + + p = name.data; + last = name.data + name.len; + + if (*p == '[') { + p = ngx_strlchr(p, last, ']'); + + if (p == NULL) { + p = name.data; + } + } + + p = ngx_strlchr(p, last, ':'); + + if (p != NULL) { + name.len = p - name.data; + } + + if (!pscf->ssl_server_name) { + goto done; + } + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + + /* as per RFC 6066, literal IPv4 and IPv6 addresses are not permitted */ + + if (name.len == 0 || *name.data == '[') { + goto done; + } + + if (ngx_inet_addr(name.data, name.len) != INADDR_NONE) { + goto done; + } + + /* + * SSL_set_tlsext_host_name() needs a null-terminated string, + * hence we explicitly null-terminate name here + */ + + p = ngx_pnalloc(s->connection->pool, name.len + 1); + if (p == NULL) { + return NGX_ERROR; + } + + (void) ngx_cpystrn(p, name.data, name.len + 1); + + name.data = p; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "upstream SSL server name: \"%s\"", name.data); + + if (SSL_set_tlsext_host_name(u->peer.connection->ssl->connection, + (char *) name.data) + == 0) + { + ngx_ssl_error(NGX_LOG_ERR, s->connection->log, 0, + "SSL_set_tlsext_host_name(\"%s\") failed", name.data); + return NGX_ERROR; + } + +#endif + +done: + + u->ssl_name = name; + + return NGX_OK; +} + +#endif + + +static void +ngx_stream_proxy_downstream_handler(ngx_event_t *ev) +{ + ngx_stream_proxy_process_connection(ev, ev->write); +} + + +static void +ngx_stream_proxy_resolve_handler(ngx_resolver_ctx_t *ctx) +{ + ngx_stream_session_t *s; + ngx_stream_upstream_t *u; + ngx_stream_proxy_srv_conf_t *pscf; + ngx_stream_upstream_resolved_t *ur; + + s = ctx->data; + + u = s->upstream; + ur = u->resolved; + + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream upstream resolve"); + + if (ctx->state) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "%V could not be resolved (%i: %s)", + &ctx->name, ctx->state, + ngx_resolver_strerror(ctx->state)); + + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + ur->naddrs = ctx->naddrs; + ur->addrs = ctx->addrs; + +#if (NGX_DEBUG) + { + u_char text[NGX_SOCKADDR_STRLEN]; + ngx_str_t addr; + ngx_uint_t i; + + addr.data = text; + + for (i = 0; i < ctx->naddrs; i++) { + addr.len = ngx_sock_ntop(ur->addrs[i].sockaddr, ur->addrs[i].socklen, + text, NGX_SOCKADDR_STRLEN, 0); + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "name was resolved to %V", &addr); + } + } +#endif + + if (ngx_stream_upstream_create_round_robin_peer(s, ur) != NGX_OK) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + ngx_resolve_name_done(ctx); + ur->ctx = NULL; + + u->peer.start_time = ngx_current_msec; + + pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module); + + if (pscf->next_upstream_tries + && u->peer.tries > pscf->next_upstream_tries) + { + u->peer.tries = pscf->next_upstream_tries; + } + + ngx_stream_proxy_connect(s); +} + + +static void +ngx_stream_proxy_upstream_handler(ngx_event_t *ev) +{ + ngx_stream_proxy_process_connection(ev, !ev->write); +} + + +static void +ngx_stream_proxy_process_connection(ngx_event_t *ev, ngx_uint_t from_upstream) +{ + ngx_connection_t *c, *pc; + ngx_stream_session_t *s; + ngx_stream_upstream_t *u; + ngx_stream_proxy_srv_conf_t *pscf; + + c = ev->data; + s = c->data; + u = s->upstream; + + c = s->connection; + pc = u->peer.connection; + + pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module); + + if (ev->timedout) { + ev->timedout = 0; + + if (ev->delayed) { + ev->delayed = 0; + + if (!ev->ready) { + if (ngx_handle_read_event(ev, 0) != NGX_OK) { + ngx_stream_proxy_finalize(s, + NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + if (u->connected && !c->read->delayed && !pc->read->delayed) { + ngx_add_timer(c->write, pscf->timeout); + } + + return; + } + + } else { + if (s->connection->type == SOCK_DGRAM) { + if (pscf->responses == NGX_MAX_INT32_VALUE) { + + /* + * successfully terminate timed out UDP session + * with unspecified number of responses + */ + + pc->read->ready = 0; + pc->read->eof = 1; + + ngx_stream_proxy_process(s, 1, 0); + return; + } + + if (u->received == 0) { + ngx_stream_proxy_next_upstream(s); + return; + } + } + + ngx_connection_error(c, NGX_ETIMEDOUT, "connection timed out"); + ngx_stream_proxy_finalize(s, NGX_STREAM_OK); + return; + } + + } else if (ev->delayed) { + + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, + "stream connection delayed"); + + if (ngx_handle_read_event(ev, 0) != NGX_OK) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + } + + return; + } + + if (from_upstream && !u->connected) { + return; + } + + ngx_stream_proxy_process(s, from_upstream, ev->write); +} + + +static void +ngx_stream_proxy_connect_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_stream_session_t *s; + + c = ev->data; + s = c->data; + + if (ev->timedout) { + ngx_log_error(NGX_LOG_ERR, c->log, NGX_ETIMEDOUT, "upstream timed out"); + ngx_stream_proxy_next_upstream(s); + return; + } + + ngx_del_timer(c->write); + + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, + "stream proxy connect upstream"); + + if (ngx_stream_proxy_test_connect(c) != NGX_OK) { + ngx_stream_proxy_next_upstream(s); + return; + } + + ngx_stream_proxy_init_upstream(s); +} + + +static ngx_int_t +ngx_stream_proxy_test_connect(ngx_connection_t *c) +{ + int err; + socklen_t len; + +#if (NGX_HAVE_KQUEUE) + + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { + err = c->write->kq_errno ? c->write->kq_errno : c->read->kq_errno; + + if (err) { + (void) ngx_connection_error(c, err, + "kevent() reported that connect() failed"); + return NGX_ERROR; + } + + } else +#endif + { + err = 0; + len = sizeof(int); + + /* + * BSDs and Linux return 0 and set a pending error in err + * Solaris returns -1 and sets errno + */ + + if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len) + == -1) + { + err = ngx_socket_errno; + } + + if (err) { + (void) ngx_connection_error(c, err, "connect() failed"); + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static void +ngx_stream_proxy_process(ngx_stream_session_t *s, ngx_uint_t from_upstream, + ngx_uint_t do_write) +{ + off_t *received, limit; + size_t size, limit_rate; + ssize_t n; + ngx_buf_t *b; + ngx_int_t rc; + ngx_uint_t flags; + ngx_msec_t delay; + ngx_chain_t *cl, **ll, **out, **busy; + ngx_connection_t *c, *pc, *src, *dst; + ngx_log_handler_pt handler; + ngx_stream_upstream_t *u; + ngx_stream_proxy_srv_conf_t *pscf; + + u = s->upstream; + + c = s->connection; + pc = u->connected ? u->peer.connection : NULL; + + if (c->type == SOCK_DGRAM && (ngx_terminate || ngx_exiting)) { + + /* socket is already closed on worker shutdown */ + + handler = c->log->handler; + c->log->handler = NULL; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, "disconnected on shutdown"); + + c->log->handler = handler; + + ngx_stream_proxy_finalize(s, NGX_STREAM_OK); + return; + } + + pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module); + + if (from_upstream) { + src = pc; + dst = c; + b = &u->upstream_buf; + limit_rate = pscf->download_rate; + received = &u->received; + out = &u->downstream_out; + busy = &u->downstream_busy; + + } else { + src = c; + dst = pc; + b = &u->downstream_buf; + limit_rate = pscf->upload_rate; + received = &s->received; + out = &u->upstream_out; + busy = &u->upstream_busy; + } + + for ( ;; ) { + + if (do_write && dst) { + + if (*out || *busy || dst->buffered) { + rc = ngx_stream_top_filter(s, *out, from_upstream); + + if (rc == NGX_ERROR) { + if (c->type == SOCK_DGRAM && !from_upstream) { + ngx_stream_proxy_next_upstream(s); + return; + } + + ngx_stream_proxy_finalize(s, NGX_STREAM_OK); + return; + } + + ngx_chain_update_chains(c->pool, &u->free, busy, out, + (ngx_buf_tag_t) &ngx_stream_proxy_module); + + if (*busy == NULL) { + b->pos = b->start; + b->last = b->start; + } + } + } + + size = b->end - b->last; + + if (size && src->read->ready && !src->read->delayed + && !src->read->error) + { + if (limit_rate) { + limit = (off_t) limit_rate * (ngx_time() - u->start_sec + 1) + - *received; + + if (limit <= 0) { + src->read->delayed = 1; + delay = (ngx_msec_t) (- limit * 1000 / limit_rate + 1); + ngx_add_timer(src->read, delay); + break; + } + + if ((off_t) size > limit) { + size = (size_t) limit; + } + } + + n = src->recv(src, b->last, size); + + if (n == NGX_AGAIN) { + break; + } + + if (n == NGX_ERROR) { + if (c->type == SOCK_DGRAM && u->received == 0) { + ngx_stream_proxy_next_upstream(s); + return; + } + + src->read->eof = 1; + n = 0; + } + + if (n >= 0) { + if (limit_rate) { + delay = (ngx_msec_t) (n * 1000 / limit_rate); + + if (delay > 0) { + src->read->delayed = 1; + ngx_add_timer(src->read, delay); + } + } + + if (from_upstream) { + if (u->state->first_byte_time == (ngx_msec_t) -1) { + u->state->first_byte_time = ngx_current_msec + - u->state->response_time; + } + } + + if (c->type == SOCK_DGRAM && ++u->responses == pscf->responses) + { + src->read->ready = 0; + src->read->eof = 1; + } + + for (ll = out; *ll; ll = &(*ll)->next) { /* void */ } + + cl = ngx_chain_get_free_buf(c->pool, &u->free); + if (cl == NULL) { + ngx_stream_proxy_finalize(s, + NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + *ll = cl; + + cl->buf->pos = b->last; + cl->buf->last = b->last + n; + cl->buf->tag = (ngx_buf_tag_t) &ngx_stream_proxy_module; + + cl->buf->temporary = (n ? 1 : 0); + cl->buf->last_buf = src->read->eof; + cl->buf->flush = 1; + + *received += n; + b->last += n; + do_write = 1; + + continue; + } + } + + break; + } + + if (src->read->eof && dst && (dst->read->eof || !dst->buffered)) { + handler = c->log->handler; + c->log->handler = NULL; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "%s%s disconnected" + ", bytes from/to client:%O/%O" + ", bytes from/to upstream:%O/%O", + src->type == SOCK_DGRAM ? "udp " : "", + from_upstream ? "upstream" : "client", + s->received, c->sent, u->received, pc ? pc->sent : 0); + + c->log->handler = handler; + + ngx_stream_proxy_finalize(s, NGX_STREAM_OK); + return; + } + + flags = src->read->eof ? NGX_CLOSE_EVENT : 0; + + if (!src->shared && ngx_handle_read_event(src->read, flags) != NGX_OK) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + if (dst) { + if (!dst->shared && ngx_handle_write_event(dst->write, 0) != NGX_OK) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + if (!c->read->delayed && !pc->read->delayed) { + ngx_add_timer(c->write, pscf->timeout); + + } else if (c->write->timer_set) { + ngx_del_timer(c->write); + } + } +} + + +static void +ngx_stream_proxy_next_upstream(ngx_stream_session_t *s) +{ + ngx_msec_t timeout; + ngx_connection_t *pc; + ngx_stream_upstream_t *u; + ngx_stream_proxy_srv_conf_t *pscf; + + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream proxy next upstream"); + + u = s->upstream; + pc = u->peer.connection; + + if (u->upstream_out || u->upstream_busy || (pc && pc->buffered)) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "pending buffers on next upstream"); + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + if (u->peer.sockaddr) { + u->peer.free(&u->peer, u->peer.data, NGX_PEER_FAILED); + u->peer.sockaddr = NULL; + } + + pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module); + + timeout = pscf->next_upstream_timeout; + + if (u->peer.tries == 0 + || !pscf->next_upstream + || (timeout && ngx_current_msec - u->peer.start_time >= timeout)) + { + ngx_stream_proxy_finalize(s, NGX_STREAM_BAD_GATEWAY); + return; + } + + if (pc) { + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "close proxy upstream connection: %d", pc->fd); + +#if (NGX_STREAM_SSL) + if (pc->ssl) { + pc->ssl->no_wait_shutdown = 1; + pc->ssl->no_send_shutdown = 1; + + (void) ngx_ssl_shutdown(pc); + } +#endif + + u->state->bytes_received = u->received; + u->state->bytes_sent = pc->sent; + + ngx_close_connection(pc); + u->peer.connection = NULL; + } + + ngx_stream_proxy_connect(s); +} + + +static void +ngx_stream_proxy_finalize(ngx_stream_session_t *s, ngx_uint_t rc) +{ + ngx_connection_t *pc; + ngx_stream_upstream_t *u; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "finalize stream proxy: %i", rc); + + u = s->upstream; + + if (u == NULL) { + goto noupstream; + } + + if (u->resolved && u->resolved->ctx) { + ngx_resolve_name_done(u->resolved->ctx); + u->resolved->ctx = NULL; + } + + pc = u->peer.connection; + + if (u->state) { + u->state->response_time = ngx_current_msec - u->state->response_time; + + if (pc) { + u->state->bytes_received = u->received; + u->state->bytes_sent = pc->sent; + } + } + + if (u->peer.free && u->peer.sockaddr) { + u->peer.free(&u->peer, u->peer.data, 0); + u->peer.sockaddr = NULL; + } + + if (pc) { + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "close stream proxy upstream connection: %d", pc->fd); + +#if (NGX_STREAM_SSL) + if (pc->ssl) { + pc->ssl->no_wait_shutdown = 1; + (void) ngx_ssl_shutdown(pc); + } +#endif + + ngx_close_connection(pc); + u->peer.connection = NULL; + } + +noupstream: + + ngx_stream_finalize_session(s, rc); +} + + +static u_char * +ngx_stream_proxy_log_error(ngx_log_t *log, u_char *buf, size_t len) +{ + u_char *p; + ngx_connection_t *pc; + ngx_stream_session_t *s; + ngx_stream_upstream_t *u; + + s = log->data; + + u = s->upstream; + + p = buf; + + if (u->peer.name) { + p = ngx_snprintf(p, len, ", upstream: \"%V\"", u->peer.name); + len -= p - buf; + } + + pc = u->peer.connection; + + p = ngx_snprintf(p, len, + ", bytes from/to client:%O/%O" + ", bytes from/to upstream:%O/%O", + s->received, s->connection->sent, + u->received, pc ? pc->sent : 0); + + return p; +} + + +static void * +ngx_stream_proxy_create_srv_conf(ngx_conf_t *cf) +{ + ngx_stream_proxy_srv_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_proxy_srv_conf_t)); + if (conf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * conf->ssl_protocols = 0; + * conf->ssl_ciphers = { 0, NULL }; + * conf->ssl_name = NULL; + * conf->ssl_trusted_certificate = { 0, NULL }; + * conf->ssl_crl = { 0, NULL }; + * conf->ssl_certificate = { 0, NULL }; + * conf->ssl_certificate_key = { 0, NULL }; + * + * conf->ssl = NULL; + * conf->upstream = NULL; + * conf->upstream_value = NULL; + */ + + conf->connect_timeout = NGX_CONF_UNSET_MSEC; + conf->timeout = NGX_CONF_UNSET_MSEC; + conf->next_upstream_timeout = NGX_CONF_UNSET_MSEC; + conf->buffer_size = NGX_CONF_UNSET_SIZE; + conf->upload_rate = NGX_CONF_UNSET_SIZE; + conf->download_rate = NGX_CONF_UNSET_SIZE; + conf->responses = NGX_CONF_UNSET_UINT; + conf->next_upstream_tries = NGX_CONF_UNSET_UINT; + conf->next_upstream = NGX_CONF_UNSET; + conf->proxy_protocol = NGX_CONF_UNSET; + conf->local = NGX_CONF_UNSET_PTR; + +#if (NGX_STREAM_SSL) + conf->ssl_enable = NGX_CONF_UNSET; + conf->ssl_session_reuse = NGX_CONF_UNSET; + conf->ssl_server_name = NGX_CONF_UNSET; + conf->ssl_verify = NGX_CONF_UNSET; + conf->ssl_verify_depth = NGX_CONF_UNSET_UINT; + conf->ssl_passwords = NGX_CONF_UNSET_PTR; +#endif + + return conf; +} + + +static char * +ngx_stream_proxy_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_stream_proxy_srv_conf_t *prev = parent; + ngx_stream_proxy_srv_conf_t *conf = child; + + ngx_conf_merge_msec_value(conf->connect_timeout, + prev->connect_timeout, 60000); + + ngx_conf_merge_msec_value(conf->timeout, + prev->timeout, 10 * 60000); + + ngx_conf_merge_msec_value(conf->next_upstream_timeout, + prev->next_upstream_timeout, 0); + + ngx_conf_merge_size_value(conf->buffer_size, + prev->buffer_size, 16384); + + ngx_conf_merge_size_value(conf->upload_rate, + prev->upload_rate, 0); + + ngx_conf_merge_size_value(conf->download_rate, + prev->download_rate, 0); + + ngx_conf_merge_uint_value(conf->responses, + prev->responses, NGX_MAX_INT32_VALUE); + + ngx_conf_merge_uint_value(conf->next_upstream_tries, + prev->next_upstream_tries, 0); + + ngx_conf_merge_value(conf->next_upstream, prev->next_upstream, 1); + + ngx_conf_merge_value(conf->proxy_protocol, prev->proxy_protocol, 0); + + ngx_conf_merge_ptr_value(conf->local, prev->local, NULL); + +#if (NGX_STREAM_SSL) + + ngx_conf_merge_value(conf->ssl_enable, prev->ssl_enable, 0); + + ngx_conf_merge_value(conf->ssl_session_reuse, + prev->ssl_session_reuse, 1); + + ngx_conf_merge_bitmask_value(conf->ssl_protocols, prev->ssl_protocols, + (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1 + |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)); + + ngx_conf_merge_str_value(conf->ssl_ciphers, prev->ssl_ciphers, "DEFAULT"); + + if (conf->ssl_name == NULL) { + conf->ssl_name = prev->ssl_name; + } + + ngx_conf_merge_value(conf->ssl_server_name, prev->ssl_server_name, 0); + + ngx_conf_merge_value(conf->ssl_verify, prev->ssl_verify, 0); + + ngx_conf_merge_uint_value(conf->ssl_verify_depth, + prev->ssl_verify_depth, 1); + + ngx_conf_merge_str_value(conf->ssl_trusted_certificate, + prev->ssl_trusted_certificate, ""); + + ngx_conf_merge_str_value(conf->ssl_crl, prev->ssl_crl, ""); + + ngx_conf_merge_str_value(conf->ssl_certificate, + prev->ssl_certificate, ""); + + ngx_conf_merge_str_value(conf->ssl_certificate_key, + prev->ssl_certificate_key, ""); + + ngx_conf_merge_ptr_value(conf->ssl_passwords, prev->ssl_passwords, NULL); + + if (conf->ssl_enable && ngx_stream_proxy_set_ssl(cf, conf) != NGX_OK) { + return NGX_CONF_ERROR; + } + +#endif + + return NGX_CONF_OK; +} + + +#if (NGX_STREAM_SSL) + +static ngx_int_t +ngx_stream_proxy_set_ssl(ngx_conf_t *cf, ngx_stream_proxy_srv_conf_t *pscf) +{ + ngx_pool_cleanup_t *cln; + + pscf->ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t)); + if (pscf->ssl == NULL) { + return NGX_ERROR; + } + + pscf->ssl->log = cf->log; + + if (ngx_ssl_create(pscf->ssl, pscf->ssl_protocols, NULL) != NGX_OK) { + return NGX_ERROR; + } + + cln = ngx_pool_cleanup_add(cf->pool, 0); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_ssl_cleanup_ctx; + cln->data = pscf->ssl; + + if (pscf->ssl_certificate.len) { + + if (pscf->ssl_certificate_key.len == 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"proxy_ssl_certificate_key\" is defined " + "for certificate \"%V\"", &pscf->ssl_certificate); + return NGX_ERROR; + } + + if (ngx_ssl_certificate(cf, pscf->ssl, &pscf->ssl_certificate, + &pscf->ssl_certificate_key, pscf->ssl_passwords) + != NGX_OK) + { + return NGX_ERROR; + } + } + + if (ngx_ssl_ciphers(cf, pscf->ssl, &pscf->ssl_ciphers, 0) != NGX_OK) { + return NGX_ERROR; + } + + if (pscf->ssl_verify) { + if (pscf->ssl_trusted_certificate.len == 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no proxy_ssl_trusted_certificate for proxy_ssl_verify"); + return NGX_ERROR; + } + + if (ngx_ssl_trusted_certificate(cf, pscf->ssl, + &pscf->ssl_trusted_certificate, + pscf->ssl_verify_depth) + != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_ssl_crl(cf, pscf->ssl, &pscf->ssl_crl) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + +#endif + + +static char * +ngx_stream_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_proxy_srv_conf_t *pscf = conf; + + ngx_url_t u; + ngx_str_t *value, *url; + ngx_stream_complex_value_t cv; + ngx_stream_core_srv_conf_t *cscf; + ngx_stream_compile_complex_value_t ccv; + + if (pscf->upstream || pscf->upstream_value) { + return "is duplicate"; + } + + cscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_core_module); + + cscf->handler = ngx_stream_proxy_handler; + + value = cf->args->elts; + + url = &value[1]; + + ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = url; + ccv.complex_value = &cv; + + if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (cv.lengths) { + pscf->upstream_value = ngx_palloc(cf->pool, + sizeof(ngx_stream_complex_value_t)); + if (pscf->upstream_value == NULL) { + return NGX_CONF_ERROR; + } + + *pscf->upstream_value = cv; + + return NGX_CONF_OK; + } + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = *url; + u.no_resolve = 1; + + pscf->upstream = ngx_stream_upstream_add(cf, &u, 0); + if (pscf->upstream == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_stream_proxy_bind(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_proxy_srv_conf_t *pscf = conf; + + ngx_int_t rc; + ngx_str_t *value; + ngx_stream_complex_value_t cv; + ngx_stream_upstream_local_t *local; + ngx_stream_compile_complex_value_t ccv; + + if (pscf->local != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + value = cf->args->elts; + + if (cf->args->nelts == 2 && ngx_strcmp(value[1].data, "off") == 0) { + pscf->local = NULL; + return NGX_CONF_OK; + } + + ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[1]; + ccv.complex_value = &cv; + + if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + local = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upstream_local_t)); + if (local == NULL) { + return NGX_CONF_ERROR; + } + + pscf->local = local; + + if (cv.lengths) { + local->value = ngx_palloc(cf->pool, sizeof(ngx_stream_complex_value_t)); + if (local->value == NULL) { + return NGX_CONF_ERROR; + } + + *local->value = cv; + + } else { + local->addr = ngx_palloc(cf->pool, sizeof(ngx_addr_t)); + if (local->addr == NULL) { + return NGX_CONF_ERROR; + } + + rc = ngx_parse_addr_port(cf->pool, local->addr, value[1].data, + value[1].len); + + switch (rc) { + case NGX_OK: + local->addr->name = value[1]; + break; + + case NGX_DECLINED: + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid address \"%V\"", &value[1]); + /* fall through */ + + default: + return NGX_CONF_ERROR; + } + } + + if (cf->args->nelts > 2) { + if (ngx_strcmp(value[2].data, "transparent") == 0) { +#if (NGX_HAVE_TRANSPARENT_PROXY) + local->transparent = 1; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "transparent proxying is not supported " + "on this platform, ignored"); +#endif + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[2]); + return NGX_CONF_ERROR; + } + } + + return NGX_CONF_OK; +} diff --git a/app/nginx/src/stream/ngx_stream_realip_module.c b/app/nginx/src/stream/ngx_stream_realip_module.c new file mode 100644 index 0000000..0740431 --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_realip_module.c @@ -0,0 +1,348 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef struct { + ngx_array_t *from; /* array of ngx_cidr_t */ +} ngx_stream_realip_srv_conf_t; + + +typedef struct { + struct sockaddr *sockaddr; + socklen_t socklen; + ngx_str_t addr_text; +} ngx_stream_realip_ctx_t; + + +static ngx_int_t ngx_stream_realip_handler(ngx_stream_session_t *s); +static ngx_int_t ngx_stream_realip_set_addr(ngx_stream_session_t *s, + ngx_addr_t *addr); +static char *ngx_stream_realip_from(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static void *ngx_stream_realip_create_srv_conf(ngx_conf_t *cf); +static char *ngx_stream_realip_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); +static ngx_int_t ngx_stream_realip_add_variables(ngx_conf_t *cf); +static ngx_int_t ngx_stream_realip_init(ngx_conf_t *cf); + + +static ngx_int_t ngx_stream_realip_remote_addr_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_realip_remote_port_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); + + +static ngx_command_t ngx_stream_realip_commands[] = { + + { ngx_string("set_real_ip_from"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_stream_realip_from, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_realip_module_ctx = { + ngx_stream_realip_add_variables, /* preconfiguration */ + ngx_stream_realip_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_stream_realip_create_srv_conf, /* create server configuration */ + ngx_stream_realip_merge_srv_conf /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_realip_module = { + NGX_MODULE_V1, + &ngx_stream_realip_module_ctx, /* module context */ + ngx_stream_realip_commands, /* module directives */ + NGX_STREAM_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_stream_variable_t ngx_stream_realip_vars[] = { + + { ngx_string("realip_remote_addr"), NULL, + ngx_stream_realip_remote_addr_variable, 0, 0, 0 }, + + { ngx_string("realip_remote_port"), NULL, + ngx_stream_realip_remote_port_variable, 0, 0, 0 }, + + { ngx_null_string, NULL, NULL, 0, 0, 0 } +}; + + +static ngx_int_t +ngx_stream_realip_handler(ngx_stream_session_t *s) +{ + ngx_addr_t addr; + ngx_connection_t *c; + ngx_stream_realip_srv_conf_t *rscf; + + rscf = ngx_stream_get_module_srv_conf(s, ngx_stream_realip_module); + + if (rscf->from == NULL) { + return NGX_DECLINED; + } + + c = s->connection; + + if (c->proxy_protocol_addr.len == 0) { + return NGX_DECLINED; + } + + if (ngx_cidr_match(c->sockaddr, rscf->from) != NGX_OK) { + return NGX_DECLINED; + } + + if (ngx_parse_addr(c->pool, &addr, c->proxy_protocol_addr.data, + c->proxy_protocol_addr.len) + != NGX_OK) + { + return NGX_DECLINED; + } + + ngx_inet_set_port(addr.sockaddr, c->proxy_protocol_port); + + return ngx_stream_realip_set_addr(s, &addr); +} + + +static ngx_int_t +ngx_stream_realip_set_addr(ngx_stream_session_t *s, ngx_addr_t *addr) +{ + size_t len; + u_char *p; + u_char text[NGX_SOCKADDR_STRLEN]; + ngx_connection_t *c; + ngx_stream_realip_ctx_t *ctx; + + c = s->connection; + + ctx = ngx_palloc(c->pool, sizeof(ngx_stream_realip_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + + len = ngx_sock_ntop(addr->sockaddr, addr->socklen, text, + NGX_SOCKADDR_STRLEN, 0); + if (len == 0) { + return NGX_ERROR; + } + + p = ngx_pnalloc(c->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, text, len); + + ngx_stream_set_ctx(s, ctx, ngx_stream_realip_module); + + ctx->sockaddr = c->sockaddr; + ctx->socklen = c->socklen; + ctx->addr_text = c->addr_text; + + c->sockaddr = addr->sockaddr; + c->socklen = addr->socklen; + c->addr_text.len = len; + c->addr_text.data = p; + + return NGX_DECLINED; +} + + +static char * +ngx_stream_realip_from(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_realip_srv_conf_t *rscf = conf; + + ngx_int_t rc; + ngx_str_t *value; + ngx_cidr_t *cidr; + + value = cf->args->elts; + + if (rscf->from == NULL) { + rscf->from = ngx_array_create(cf->pool, 2, + sizeof(ngx_cidr_t)); + if (rscf->from == NULL) { + return NGX_CONF_ERROR; + } + } + + cidr = ngx_array_push(rscf->from); + if (cidr == NULL) { + return NGX_CONF_ERROR; + } + +#if (NGX_HAVE_UNIX_DOMAIN) + + if (ngx_strcmp(value[1].data, "unix:") == 0) { + cidr->family = AF_UNIX; + return NGX_CONF_OK; + } + +#endif + + rc = ngx_ptocidr(&value[1], cidr); + + if (rc == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", + &value[1]); + return NGX_CONF_ERROR; + } + + if (rc == NGX_DONE) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "low address bits of %V are meaningless", &value[1]); + } + + return NGX_CONF_OK; +} + + +static void * +ngx_stream_realip_create_srv_conf(ngx_conf_t *cf) +{ + ngx_stream_realip_srv_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_realip_srv_conf_t)); + if (conf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * conf->from = NULL; + */ + + return conf; +} + + +static char * +ngx_stream_realip_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_stream_realip_srv_conf_t *prev = parent; + ngx_stream_realip_srv_conf_t *conf = child; + + if (conf->from == NULL) { + conf->from = prev->from; + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_stream_realip_add_variables(ngx_conf_t *cf) +{ + ngx_stream_variable_t *var, *v; + + for (v = ngx_stream_realip_vars; v->name.len; v++) { + var = ngx_stream_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_realip_init(ngx_conf_t *cf) +{ + ngx_stream_handler_pt *h; + ngx_stream_core_main_conf_t *cmcf; + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + + h = ngx_array_push(&cmcf->phases[NGX_STREAM_POST_ACCEPT_PHASE].handlers); + if (h == NULL) { + return NGX_ERROR; + } + + *h = ngx_stream_realip_handler; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_realip_remote_addr_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + ngx_str_t *addr_text; + ngx_stream_realip_ctx_t *ctx; + + ctx = ngx_stream_get_module_ctx(s, ngx_stream_realip_module); + + addr_text = ctx ? &ctx->addr_text : &s->connection->addr_text; + + v->len = addr_text->len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = addr_text->data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_realip_remote_port_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + ngx_uint_t port; + struct sockaddr *sa; + ngx_stream_realip_ctx_t *ctx; + + ctx = ngx_stream_get_module_ctx(s, ngx_stream_realip_module); + + sa = ctx ? ctx->sockaddr : s->connection->sockaddr; + + v->len = 0; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + v->data = ngx_pnalloc(s->connection->pool, sizeof("65535") - 1); + if (v->data == NULL) { + return NGX_ERROR; + } + + port = ngx_inet_get_port(sa); + + if (port > 0 && port < 65536) { + v->len = ngx_sprintf(v->data, "%ui", port) - v->data; + } + + return NGX_OK; +} diff --git a/app/nginx/src/stream/ngx_stream_return_module.c b/app/nginx/src/stream/ngx_stream_return_module.c new file mode 100644 index 0000000..9301b02 --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_return_module.c @@ -0,0 +1,218 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef struct { + ngx_stream_complex_value_t text; +} ngx_stream_return_srv_conf_t; + + +typedef struct { + ngx_chain_t *out; +} ngx_stream_return_ctx_t; + + +static void ngx_stream_return_handler(ngx_stream_session_t *s); +static void ngx_stream_return_write_handler(ngx_event_t *ev); + +static void *ngx_stream_return_create_srv_conf(ngx_conf_t *cf); +static char *ngx_stream_return(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + + +static ngx_command_t ngx_stream_return_commands[] = { + + { ngx_string("return"), + NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_stream_return, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_return_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_stream_return_create_srv_conf, /* create server configuration */ + NULL /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_return_module = { + NGX_MODULE_V1, + &ngx_stream_return_module_ctx, /* module context */ + ngx_stream_return_commands, /* module directives */ + NGX_STREAM_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 void +ngx_stream_return_handler(ngx_stream_session_t *s) +{ + ngx_str_t text; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_stream_return_ctx_t *ctx; + ngx_stream_return_srv_conf_t *rscf; + + c = s->connection; + + c->log->action = "returning text"; + + rscf = ngx_stream_get_module_srv_conf(s, ngx_stream_return_module); + + if (ngx_stream_complex_value(s, &rscf->text, &text) != NGX_OK) { + ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "stream return text: \"%V\"", &text); + + if (text.len == 0) { + ngx_stream_finalize_session(s, NGX_STREAM_OK); + return; + } + + ctx = ngx_pcalloc(c->pool, sizeof(ngx_stream_return_ctx_t)); + if (ctx == NULL) { + ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + ngx_stream_set_ctx(s, ctx, ngx_stream_return_module); + + b = ngx_calloc_buf(c->pool); + if (b == NULL) { + ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + b->memory = 1; + b->pos = text.data; + b->last = text.data + text.len; + b->last_buf = 1; + + ctx->out = ngx_alloc_chain_link(c->pool); + if (ctx->out == NULL) { + ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + ctx->out->buf = b; + ctx->out->next = NULL; + + c->write->handler = ngx_stream_return_write_handler; + + ngx_stream_return_write_handler(c->write); +} + + +static void +ngx_stream_return_write_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_stream_session_t *s; + ngx_stream_return_ctx_t *ctx; + + c = ev->data; + s = c->data; + + if (ev->timedout) { + ngx_connection_error(c, NGX_ETIMEDOUT, "connection timed out"); + ngx_stream_finalize_session(s, NGX_STREAM_OK); + return; + } + + ctx = ngx_stream_get_module_ctx(s, ngx_stream_return_module); + + if (ngx_stream_top_filter(s, ctx->out, 1) == NGX_ERROR) { + ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + ctx->out = NULL; + + if (!c->buffered) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, + "stream return done sending"); + ngx_stream_finalize_session(s, NGX_STREAM_OK); + return; + } + + if (ngx_handle_write_event(ev, 0) != NGX_OK) { + ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + ngx_add_timer(ev, 5000); +} + + +static void * +ngx_stream_return_create_srv_conf(ngx_conf_t *cf) +{ + ngx_stream_return_srv_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_return_srv_conf_t)); + if (conf == NULL) { + return NULL; + } + + return conf; +} + + +static char * +ngx_stream_return(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_return_srv_conf_t *rscf = conf; + + ngx_str_t *value; + ngx_stream_core_srv_conf_t *cscf; + ngx_stream_compile_complex_value_t ccv; + + if (rscf->text.value.data) { + return "is duplicate"; + } + + value = cf->args->elts; + + ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[1]; + ccv.complex_value = &rscf->text; + + if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + cscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_core_module); + + cscf->handler = ngx_stream_return_handler; + + return NGX_CONF_OK; +} diff --git a/app/nginx/src/stream/ngx_stream_script.c b/app/nginx/src/stream/ngx_stream_script.c new file mode 100644 index 0000000..aa555ca --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_script.c @@ -0,0 +1,921 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +static ngx_int_t ngx_stream_script_init_arrays( + ngx_stream_script_compile_t *sc); +static ngx_int_t ngx_stream_script_done(ngx_stream_script_compile_t *sc); +static ngx_int_t ngx_stream_script_add_copy_code( + ngx_stream_script_compile_t *sc, ngx_str_t *value, ngx_uint_t last); +static ngx_int_t ngx_stream_script_add_var_code( + ngx_stream_script_compile_t *sc, ngx_str_t *name); +#if (NGX_PCRE) +static ngx_int_t ngx_stream_script_add_capture_code( + ngx_stream_script_compile_t *sc, ngx_uint_t n); +#endif +static ngx_int_t ngx_stream_script_add_full_name_code( + ngx_stream_script_compile_t *sc); +static size_t ngx_stream_script_full_name_len_code( + ngx_stream_script_engine_t *e); +static void ngx_stream_script_full_name_code(ngx_stream_script_engine_t *e); + + +#define ngx_stream_script_exit (u_char *) &ngx_stream_script_exit_code + +static uintptr_t ngx_stream_script_exit_code = (uintptr_t) NULL; + + +void +ngx_stream_script_flush_complex_value(ngx_stream_session_t *s, + ngx_stream_complex_value_t *val) +{ + ngx_uint_t *index; + + index = val->flushes; + + if (index) { + while (*index != (ngx_uint_t) -1) { + + if (s->variables[*index].no_cacheable) { + s->variables[*index].valid = 0; + s->variables[*index].not_found = 0; + } + + index++; + } + } +} + + +ngx_int_t +ngx_stream_complex_value(ngx_stream_session_t *s, + ngx_stream_complex_value_t *val, ngx_str_t *value) +{ + size_t len; + ngx_stream_script_code_pt code; + ngx_stream_script_engine_t e; + ngx_stream_script_len_code_pt lcode; + + if (val->lengths == NULL) { + *value = val->value; + return NGX_OK; + } + + ngx_stream_script_flush_complex_value(s, val); + + ngx_memzero(&e, sizeof(ngx_stream_script_engine_t)); + + e.ip = val->lengths; + e.session = s; + e.flushed = 1; + + len = 0; + + while (*(uintptr_t *) e.ip) { + lcode = *(ngx_stream_script_len_code_pt *) e.ip; + len += lcode(&e); + } + + value->len = len; + value->data = ngx_pnalloc(s->connection->pool, len); + if (value->data == NULL) { + return NGX_ERROR; + } + + e.ip = val->values; + e.pos = value->data; + e.buf = *value; + + while (*(uintptr_t *) e.ip) { + code = *(ngx_stream_script_code_pt *) e.ip; + code((ngx_stream_script_engine_t *) &e); + } + + *value = e.buf; + + return NGX_OK; +} + + +ngx_int_t +ngx_stream_compile_complex_value(ngx_stream_compile_complex_value_t *ccv) +{ + ngx_str_t *v; + ngx_uint_t i, n, nv, nc; + ngx_array_t flushes, lengths, values, *pf, *pl, *pv; + ngx_stream_script_compile_t sc; + + v = ccv->value; + + nv = 0; + nc = 0; + + for (i = 0; i < v->len; i++) { + if (v->data[i] == '$') { + if (v->data[i + 1] >= '1' && v->data[i + 1] <= '9') { + nc++; + + } else { + nv++; + } + } + } + + if ((v->len == 0 || v->data[0] != '$') + && (ccv->conf_prefix || ccv->root_prefix)) + { + if (ngx_conf_full_name(ccv->cf->cycle, v, ccv->conf_prefix) != NGX_OK) { + return NGX_ERROR; + } + + ccv->conf_prefix = 0; + ccv->root_prefix = 0; + } + + ccv->complex_value->value = *v; + ccv->complex_value->flushes = NULL; + ccv->complex_value->lengths = NULL; + ccv->complex_value->values = NULL; + + if (nv == 0 && nc == 0) { + return NGX_OK; + } + + n = nv + 1; + + if (ngx_array_init(&flushes, ccv->cf->pool, n, sizeof(ngx_uint_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + n = nv * (2 * sizeof(ngx_stream_script_copy_code_t) + + sizeof(ngx_stream_script_var_code_t)) + + sizeof(uintptr_t); + + if (ngx_array_init(&lengths, ccv->cf->pool, n, 1) != NGX_OK) { + return NGX_ERROR; + } + + n = (nv * (2 * sizeof(ngx_stream_script_copy_code_t) + + sizeof(ngx_stream_script_var_code_t)) + + sizeof(uintptr_t) + + v->len + + sizeof(uintptr_t) - 1) + & ~(sizeof(uintptr_t) - 1); + + if (ngx_array_init(&values, ccv->cf->pool, n, 1) != NGX_OK) { + return NGX_ERROR; + } + + pf = &flushes; + pl = &lengths; + pv = &values; + + ngx_memzero(&sc, sizeof(ngx_stream_script_compile_t)); + + sc.cf = ccv->cf; + sc.source = v; + sc.flushes = &pf; + sc.lengths = &pl; + sc.values = &pv; + sc.complete_lengths = 1; + sc.complete_values = 1; + sc.zero = ccv->zero; + sc.conf_prefix = ccv->conf_prefix; + sc.root_prefix = ccv->root_prefix; + + if (ngx_stream_script_compile(&sc) != NGX_OK) { + return NGX_ERROR; + } + + if (flushes.nelts) { + ccv->complex_value->flushes = flushes.elts; + ccv->complex_value->flushes[flushes.nelts] = (ngx_uint_t) -1; + } + + ccv->complex_value->lengths = lengths.elts; + ccv->complex_value->values = values.elts; + + return NGX_OK; +} + + +char * +ngx_stream_set_complex_value_slot(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + char *p = conf; + + ngx_str_t *value; + ngx_stream_complex_value_t **cv; + ngx_stream_compile_complex_value_t ccv; + + cv = (ngx_stream_complex_value_t **) (p + cmd->offset); + + if (*cv != NULL) { + return "is duplicate"; + } + + *cv = ngx_palloc(cf->pool, sizeof(ngx_stream_complex_value_t)); + if (*cv == NULL) { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + + ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[1]; + ccv.complex_value = *cv; + + if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +ngx_uint_t +ngx_stream_script_variables_count(ngx_str_t *value) +{ + ngx_uint_t i, n; + + for (n = 0, i = 0; i < value->len; i++) { + if (value->data[i] == '$') { + n++; + } + } + + return n; +} + + +ngx_int_t +ngx_stream_script_compile(ngx_stream_script_compile_t *sc) +{ + u_char ch; + ngx_str_t name; + ngx_uint_t i, bracket; + + if (ngx_stream_script_init_arrays(sc) != NGX_OK) { + return NGX_ERROR; + } + + for (i = 0; i < sc->source->len; /* void */ ) { + + name.len = 0; + + if (sc->source->data[i] == '$') { + + if (++i == sc->source->len) { + goto invalid_variable; + } + + if (sc->source->data[i] >= '1' && sc->source->data[i] <= '9') { +#if (NGX_PCRE) + ngx_uint_t n; + + n = sc->source->data[i] - '0'; + + if (ngx_stream_script_add_capture_code(sc, n) != NGX_OK) { + return NGX_ERROR; + } + + i++; + + continue; +#else + ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0, + "using variable \"$%c\" requires " + "PCRE library", sc->source->data[i]); + return NGX_ERROR; +#endif + } + + if (sc->source->data[i] == '{') { + bracket = 1; + + if (++i == sc->source->len) { + goto invalid_variable; + } + + name.data = &sc->source->data[i]; + + } else { + bracket = 0; + name.data = &sc->source->data[i]; + } + + for ( /* void */ ; i < sc->source->len; i++, name.len++) { + ch = sc->source->data[i]; + + if (ch == '}' && bracket) { + i++; + bracket = 0; + break; + } + + if ((ch >= 'A' && ch <= 'Z') + || (ch >= 'a' && ch <= 'z') + || (ch >= '0' && ch <= '9') + || ch == '_') + { + continue; + } + + break; + } + + if (bracket) { + ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0, + "the closing bracket in \"%V\" " + "variable is missing", &name); + return NGX_ERROR; + } + + if (name.len == 0) { + goto invalid_variable; + } + + sc->variables++; + + if (ngx_stream_script_add_var_code(sc, &name) != NGX_OK) { + return NGX_ERROR; + } + + continue; + } + + name.data = &sc->source->data[i]; + + while (i < sc->source->len) { + + if (sc->source->data[i] == '$') { + break; + } + + i++; + name.len++; + } + + sc->size += name.len; + + if (ngx_stream_script_add_copy_code(sc, &name, (i == sc->source->len)) + != NGX_OK) + { + return NGX_ERROR; + } + } + + return ngx_stream_script_done(sc); + +invalid_variable: + + ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0, "invalid variable name"); + + return NGX_ERROR; +} + + +u_char * +ngx_stream_script_run(ngx_stream_session_t *s, ngx_str_t *value, + void *code_lengths, size_t len, void *code_values) +{ + ngx_uint_t i; + ngx_stream_script_code_pt code; + ngx_stream_script_engine_t e; + ngx_stream_core_main_conf_t *cmcf; + ngx_stream_script_len_code_pt lcode; + + cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module); + + for (i = 0; i < cmcf->variables.nelts; i++) { + if (s->variables[i].no_cacheable) { + s->variables[i].valid = 0; + s->variables[i].not_found = 0; + } + } + + ngx_memzero(&e, sizeof(ngx_stream_script_engine_t)); + + e.ip = code_lengths; + e.session = s; + e.flushed = 1; + + while (*(uintptr_t *) e.ip) { + lcode = *(ngx_stream_script_len_code_pt *) e.ip; + len += lcode(&e); + } + + + value->len = len; + value->data = ngx_pnalloc(s->connection->pool, len); + if (value->data == NULL) { + return NULL; + } + + e.ip = code_values; + e.pos = value->data; + + while (*(uintptr_t *) e.ip) { + code = *(ngx_stream_script_code_pt *) e.ip; + code((ngx_stream_script_engine_t *) &e); + } + + return e.pos; +} + + +void +ngx_stream_script_flush_no_cacheable_variables(ngx_stream_session_t *s, + ngx_array_t *indices) +{ + ngx_uint_t n, *index; + + if (indices) { + index = indices->elts; + for (n = 0; n < indices->nelts; n++) { + if (s->variables[index[n]].no_cacheable) { + s->variables[index[n]].valid = 0; + s->variables[index[n]].not_found = 0; + } + } + } +} + + +static ngx_int_t +ngx_stream_script_init_arrays(ngx_stream_script_compile_t *sc) +{ + ngx_uint_t n; + + if (sc->flushes && *sc->flushes == NULL) { + n = sc->variables ? sc->variables : 1; + *sc->flushes = ngx_array_create(sc->cf->pool, n, sizeof(ngx_uint_t)); + if (*sc->flushes == NULL) { + return NGX_ERROR; + } + } + + if (*sc->lengths == NULL) { + n = sc->variables * (2 * sizeof(ngx_stream_script_copy_code_t) + + sizeof(ngx_stream_script_var_code_t)) + + sizeof(uintptr_t); + + *sc->lengths = ngx_array_create(sc->cf->pool, n, 1); + if (*sc->lengths == NULL) { + return NGX_ERROR; + } + } + + if (*sc->values == NULL) { + n = (sc->variables * (2 * sizeof(ngx_stream_script_copy_code_t) + + sizeof(ngx_stream_script_var_code_t)) + + sizeof(uintptr_t) + + sc->source->len + + sizeof(uintptr_t) - 1) + & ~(sizeof(uintptr_t) - 1); + + *sc->values = ngx_array_create(sc->cf->pool, n, 1); + if (*sc->values == NULL) { + return NGX_ERROR; + } + } + + sc->variables = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_script_done(ngx_stream_script_compile_t *sc) +{ + ngx_str_t zero; + uintptr_t *code; + + if (sc->zero) { + + zero.len = 1; + zero.data = (u_char *) "\0"; + + if (ngx_stream_script_add_copy_code(sc, &zero, 0) != NGX_OK) { + return NGX_ERROR; + } + } + + if (sc->conf_prefix || sc->root_prefix) { + if (ngx_stream_script_add_full_name_code(sc) != NGX_OK) { + return NGX_ERROR; + } + } + + if (sc->complete_lengths) { + code = ngx_stream_script_add_code(*sc->lengths, sizeof(uintptr_t), + NULL); + if (code == NULL) { + return NGX_ERROR; + } + + *code = (uintptr_t) NULL; + } + + if (sc->complete_values) { + code = ngx_stream_script_add_code(*sc->values, sizeof(uintptr_t), + &sc->main); + if (code == NULL) { + return NGX_ERROR; + } + + *code = (uintptr_t) NULL; + } + + return NGX_OK; +} + + +void * +ngx_stream_script_add_code(ngx_array_t *codes, size_t size, void *code) +{ + u_char *elts, **p; + void *new; + + elts = codes->elts; + + new = ngx_array_push_n(codes, size); + if (new == NULL) { + return NULL; + } + + if (code) { + if (elts != codes->elts) { + p = code; + *p += (u_char *) codes->elts - elts; + } + } + + return new; +} + + +static ngx_int_t +ngx_stream_script_add_copy_code(ngx_stream_script_compile_t *sc, + ngx_str_t *value, ngx_uint_t last) +{ + u_char *p; + size_t size, len, zero; + ngx_stream_script_copy_code_t *code; + + zero = (sc->zero && last); + len = value->len + zero; + + code = ngx_stream_script_add_code(*sc->lengths, + sizeof(ngx_stream_script_copy_code_t), + NULL); + if (code == NULL) { + return NGX_ERROR; + } + + code->code = (ngx_stream_script_code_pt) ngx_stream_script_copy_len_code; + code->len = len; + + size = (sizeof(ngx_stream_script_copy_code_t) + len + sizeof(uintptr_t) - 1) + & ~(sizeof(uintptr_t) - 1); + + code = ngx_stream_script_add_code(*sc->values, size, &sc->main); + if (code == NULL) { + return NGX_ERROR; + } + + code->code = ngx_stream_script_copy_code; + code->len = len; + + p = ngx_cpymem((u_char *) code + sizeof(ngx_stream_script_copy_code_t), + value->data, value->len); + + if (zero) { + *p = '\0'; + sc->zero = 0; + } + + return NGX_OK; +} + + +size_t +ngx_stream_script_copy_len_code(ngx_stream_script_engine_t *e) +{ + ngx_stream_script_copy_code_t *code; + + code = (ngx_stream_script_copy_code_t *) e->ip; + + e->ip += sizeof(ngx_stream_script_copy_code_t); + + return code->len; +} + + +void +ngx_stream_script_copy_code(ngx_stream_script_engine_t *e) +{ + u_char *p; + ngx_stream_script_copy_code_t *code; + + code = (ngx_stream_script_copy_code_t *) e->ip; + + p = e->pos; + + if (!e->skip) { + e->pos = ngx_copy(p, e->ip + sizeof(ngx_stream_script_copy_code_t), + code->len); + } + + e->ip += sizeof(ngx_stream_script_copy_code_t) + + ((code->len + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1)); + + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, e->session->connection->log, 0, + "stream script copy: \"%*s\"", e->pos - p, p); +} + + +static ngx_int_t +ngx_stream_script_add_var_code(ngx_stream_script_compile_t *sc, ngx_str_t *name) +{ + ngx_int_t index, *p; + ngx_stream_script_var_code_t *code; + + index = ngx_stream_get_variable_index(sc->cf, name); + + if (index == NGX_ERROR) { + return NGX_ERROR; + } + + if (sc->flushes) { + p = ngx_array_push(*sc->flushes); + if (p == NULL) { + return NGX_ERROR; + } + + *p = index; + } + + code = ngx_stream_script_add_code(*sc->lengths, + sizeof(ngx_stream_script_var_code_t), + NULL); + if (code == NULL) { + return NGX_ERROR; + } + + code->code = (ngx_stream_script_code_pt) + ngx_stream_script_copy_var_len_code; + code->index = (uintptr_t) index; + + code = ngx_stream_script_add_code(*sc->values, + sizeof(ngx_stream_script_var_code_t), + &sc->main); + if (code == NULL) { + return NGX_ERROR; + } + + code->code = ngx_stream_script_copy_var_code; + code->index = (uintptr_t) index; + + return NGX_OK; +} + + +size_t +ngx_stream_script_copy_var_len_code(ngx_stream_script_engine_t *e) +{ + ngx_stream_variable_value_t *value; + ngx_stream_script_var_code_t *code; + + code = (ngx_stream_script_var_code_t *) e->ip; + + e->ip += sizeof(ngx_stream_script_var_code_t); + + if (e->flushed) { + value = ngx_stream_get_indexed_variable(e->session, code->index); + + } else { + value = ngx_stream_get_flushed_variable(e->session, code->index); + } + + if (value && !value->not_found) { + return value->len; + } + + return 0; +} + + +void +ngx_stream_script_copy_var_code(ngx_stream_script_engine_t *e) +{ + u_char *p; + ngx_stream_variable_value_t *value; + ngx_stream_script_var_code_t *code; + + code = (ngx_stream_script_var_code_t *) e->ip; + + e->ip += sizeof(ngx_stream_script_var_code_t); + + if (!e->skip) { + + if (e->flushed) { + value = ngx_stream_get_indexed_variable(e->session, code->index); + + } else { + value = ngx_stream_get_flushed_variable(e->session, code->index); + } + + if (value && !value->not_found) { + p = e->pos; + e->pos = ngx_copy(p, value->data, value->len); + + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, + e->session->connection->log, 0, + "stream script var: \"%*s\"", e->pos - p, p); + } + } +} + + +#if (NGX_PCRE) + +static ngx_int_t +ngx_stream_script_add_capture_code(ngx_stream_script_compile_t *sc, + ngx_uint_t n) +{ + ngx_stream_script_copy_capture_code_t *code; + + code = ngx_stream_script_add_code(*sc->lengths, + sizeof(ngx_stream_script_copy_capture_code_t), + NULL); + if (code == NULL) { + return NGX_ERROR; + } + + code->code = (ngx_stream_script_code_pt) + ngx_stream_script_copy_capture_len_code; + code->n = 2 * n; + + + code = ngx_stream_script_add_code(*sc->values, + sizeof(ngx_stream_script_copy_capture_code_t), + &sc->main); + if (code == NULL) { + return NGX_ERROR; + } + + code->code = ngx_stream_script_copy_capture_code; + code->n = 2 * n; + + if (sc->ncaptures < n) { + sc->ncaptures = n; + } + + return NGX_OK; +} + + +size_t +ngx_stream_script_copy_capture_len_code(ngx_stream_script_engine_t *e) +{ + int *cap; + ngx_uint_t n; + ngx_stream_session_t *s; + ngx_stream_script_copy_capture_code_t *code; + + s = e->session; + + code = (ngx_stream_script_copy_capture_code_t *) e->ip; + + e->ip += sizeof(ngx_stream_script_copy_capture_code_t); + + n = code->n; + + if (n < s->ncaptures) { + cap = s->captures; + return cap[n + 1] - cap[n]; + } + + return 0; +} + + +void +ngx_stream_script_copy_capture_code(ngx_stream_script_engine_t *e) +{ + int *cap; + u_char *p, *pos; + ngx_uint_t n; + ngx_stream_session_t *s; + ngx_stream_script_copy_capture_code_t *code; + + s = e->session; + + code = (ngx_stream_script_copy_capture_code_t *) e->ip; + + e->ip += sizeof(ngx_stream_script_copy_capture_code_t); + + n = code->n; + + pos = e->pos; + + if (n < s->ncaptures) { + cap = s->captures; + p = s->captures_data; + e->pos = ngx_copy(pos, &p[cap[n]], cap[n + 1] - cap[n]); + } + + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, e->session->connection->log, 0, + "stream script capture: \"%*s\"", e->pos - pos, pos); +} + +#endif + + +static ngx_int_t +ngx_stream_script_add_full_name_code(ngx_stream_script_compile_t *sc) +{ + ngx_stream_script_full_name_code_t *code; + + code = ngx_stream_script_add_code(*sc->lengths, + sizeof(ngx_stream_script_full_name_code_t), + NULL); + if (code == NULL) { + return NGX_ERROR; + } + + code->code = (ngx_stream_script_code_pt) + ngx_stream_script_full_name_len_code; + code->conf_prefix = sc->conf_prefix; + + code = ngx_stream_script_add_code(*sc->values, + sizeof(ngx_stream_script_full_name_code_t), &sc->main); + if (code == NULL) { + return NGX_ERROR; + } + + code->code = ngx_stream_script_full_name_code; + code->conf_prefix = sc->conf_prefix; + + return NGX_OK; +} + + +static size_t +ngx_stream_script_full_name_len_code(ngx_stream_script_engine_t *e) +{ + ngx_stream_script_full_name_code_t *code; + + code = (ngx_stream_script_full_name_code_t *) e->ip; + + e->ip += sizeof(ngx_stream_script_full_name_code_t); + + return code->conf_prefix ? ngx_cycle->conf_prefix.len: + ngx_cycle->prefix.len; +} + + +static void +ngx_stream_script_full_name_code(ngx_stream_script_engine_t *e) +{ + ngx_stream_script_full_name_code_t *code; + + ngx_str_t value, *prefix; + + code = (ngx_stream_script_full_name_code_t *) e->ip; + + value.data = e->buf.data; + value.len = e->pos - e->buf.data; + + prefix = code->conf_prefix ? (ngx_str_t *) &ngx_cycle->conf_prefix: + (ngx_str_t *) &ngx_cycle->prefix; + + if (ngx_get_full_name(e->session->connection->pool, prefix, &value) + != NGX_OK) + { + e->ip = ngx_stream_script_exit; + return; + } + + e->buf = value; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, e->session->connection->log, 0, + "stream script fullname: \"%V\"", &value); + + e->ip += sizeof(ngx_stream_script_full_name_code_t); +} diff --git a/app/nginx/src/stream/ngx_stream_script.h b/app/nginx/src/stream/ngx_stream_script.h new file mode 100644 index 0000000..25a450d --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_script.h @@ -0,0 +1,127 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_STREAM_SCRIPT_H_INCLUDED_ +#define _NGX_STREAM_SCRIPT_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct { + u_char *ip; + u_char *pos; + ngx_stream_variable_value_t *sp; + + ngx_str_t buf; + ngx_str_t line; + + unsigned flushed:1; + unsigned skip:1; + + ngx_stream_session_t *session; +} ngx_stream_script_engine_t; + + +typedef struct { + ngx_conf_t *cf; + ngx_str_t *source; + + ngx_array_t **flushes; + ngx_array_t **lengths; + ngx_array_t **values; + + ngx_uint_t variables; + ngx_uint_t ncaptures; + ngx_uint_t size; + + void *main; + + unsigned complete_lengths:1; + unsigned complete_values:1; + unsigned zero:1; + unsigned conf_prefix:1; + unsigned root_prefix:1; +} ngx_stream_script_compile_t; + + +typedef struct { + ngx_str_t value; + ngx_uint_t *flushes; + void *lengths; + void *values; +} ngx_stream_complex_value_t; + + +typedef struct { + ngx_conf_t *cf; + ngx_str_t *value; + ngx_stream_complex_value_t *complex_value; + + unsigned zero:1; + unsigned conf_prefix:1; + unsigned root_prefix:1; +} ngx_stream_compile_complex_value_t; + + +typedef void (*ngx_stream_script_code_pt) (ngx_stream_script_engine_t *e); +typedef size_t (*ngx_stream_script_len_code_pt) (ngx_stream_script_engine_t *e); + + +typedef struct { + ngx_stream_script_code_pt code; + uintptr_t len; +} ngx_stream_script_copy_code_t; + + +typedef struct { + ngx_stream_script_code_pt code; + uintptr_t index; +} ngx_stream_script_var_code_t; + + +typedef struct { + ngx_stream_script_code_pt code; + uintptr_t n; +} ngx_stream_script_copy_capture_code_t; + + +typedef struct { + ngx_stream_script_code_pt code; + uintptr_t conf_prefix; +} ngx_stream_script_full_name_code_t; + + +void ngx_stream_script_flush_complex_value(ngx_stream_session_t *s, + ngx_stream_complex_value_t *val); +ngx_int_t ngx_stream_complex_value(ngx_stream_session_t *s, + ngx_stream_complex_value_t *val, ngx_str_t *value); +ngx_int_t ngx_stream_compile_complex_value( + ngx_stream_compile_complex_value_t *ccv); +char *ngx_stream_set_complex_value_slot(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +ngx_uint_t ngx_stream_script_variables_count(ngx_str_t *value); +ngx_int_t ngx_stream_script_compile(ngx_stream_script_compile_t *sc); +u_char *ngx_stream_script_run(ngx_stream_session_t *s, ngx_str_t *value, + void *code_lengths, size_t reserved, void *code_values); +void ngx_stream_script_flush_no_cacheable_variables(ngx_stream_session_t *s, + ngx_array_t *indices); + +void *ngx_stream_script_add_code(ngx_array_t *codes, size_t size, void *code); + +size_t ngx_stream_script_copy_len_code(ngx_stream_script_engine_t *e); +void ngx_stream_script_copy_code(ngx_stream_script_engine_t *e); +size_t ngx_stream_script_copy_var_len_code(ngx_stream_script_engine_t *e); +void ngx_stream_script_copy_var_code(ngx_stream_script_engine_t *e); +size_t ngx_stream_script_copy_capture_len_code(ngx_stream_script_engine_t *e); +void ngx_stream_script_copy_capture_code(ngx_stream_script_engine_t *e); + +#endif /* _NGX_STREAM_SCRIPT_H_INCLUDED_ */ diff --git a/app/nginx/src/stream/ngx_stream_split_clients_module.c b/app/nginx/src/stream/ngx_stream_split_clients_module.c new file mode 100644 index 0000000..af6c8a1 --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_split_clients_module.c @@ -0,0 +1,244 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef struct { + uint32_t percent; + ngx_stream_variable_value_t value; +} ngx_stream_split_clients_part_t; + + +typedef struct { + ngx_stream_complex_value_t value; + ngx_array_t parts; +} ngx_stream_split_clients_ctx_t; + + +static char *ngx_conf_split_clients_block(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_stream_split_clients(ngx_conf_t *cf, ngx_command_t *dummy, + void *conf); + +static ngx_command_t ngx_stream_split_clients_commands[] = { + + { ngx_string("split_clients"), + NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2, + ngx_conf_split_clients_block, + NGX_STREAM_MAIN_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_split_clients_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_split_clients_module = { + NGX_MODULE_V1, + &ngx_stream_split_clients_module_ctx, /* module context */ + ngx_stream_split_clients_commands, /* module directives */ + NGX_STREAM_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_stream_split_clients_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + ngx_stream_split_clients_ctx_t *ctx = + (ngx_stream_split_clients_ctx_t *) data; + + uint32_t hash; + ngx_str_t val; + ngx_uint_t i; + ngx_stream_split_clients_part_t *part; + + *v = ngx_stream_variable_null_value; + + if (ngx_stream_complex_value(s, &ctx->value, &val) != NGX_OK) { + return NGX_OK; + } + + hash = ngx_murmur_hash2(val.data, val.len); + + part = ctx->parts.elts; + + for (i = 0; i < ctx->parts.nelts; i++) { + + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream split: %uD %uD", hash, part[i].percent); + + if (hash < part[i].percent || part[i].percent == 0) { + *v = part[i].value; + return NGX_OK; + } + } + + return NGX_OK; +} + + +static char * +ngx_conf_split_clients_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *rv; + uint32_t sum, last; + ngx_str_t *value, name; + ngx_uint_t i; + ngx_conf_t save; + ngx_stream_variable_t *var; + ngx_stream_split_clients_ctx_t *ctx; + ngx_stream_split_clients_part_t *part; + ngx_stream_compile_complex_value_t ccv; + + ctx = ngx_pcalloc(cf->pool, sizeof(ngx_stream_split_clients_ctx_t)); + if (ctx == NULL) { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + + ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[1]; + ccv.complex_value = &ctx->value; + + if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + name = value[2]; + + if (name.data[0] != '$') { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid variable name \"%V\"", &name); + return NGX_CONF_ERROR; + } + + name.len--; + name.data++; + + var = ngx_stream_add_variable(cf, &name, NGX_STREAM_VAR_CHANGEABLE); + if (var == NULL) { + return NGX_CONF_ERROR; + } + + var->get_handler = ngx_stream_split_clients_variable; + var->data = (uintptr_t) ctx; + + if (ngx_array_init(&ctx->parts, cf->pool, 2, + sizeof(ngx_stream_split_clients_part_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + save = *cf; + cf->ctx = ctx; + cf->handler = ngx_stream_split_clients; + cf->handler_conf = conf; + + rv = ngx_conf_parse(cf, NULL); + + *cf = save; + + if (rv != NGX_CONF_OK) { + return rv; + } + + sum = 0; + last = 0; + part = ctx->parts.elts; + + for (i = 0; i < ctx->parts.nelts; i++) { + sum = part[i].percent ? sum + part[i].percent : 10000; + if (sum > 10000) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "percent total is greater than 100%%"); + return NGX_CONF_ERROR; + } + + if (part[i].percent) { + last += part[i].percent * (uint64_t) 0xffffffff / 10000; + part[i].percent = last; + } + } + + return rv; +} + + +static char * +ngx_stream_split_clients(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) +{ + ngx_int_t n; + ngx_str_t *value; + ngx_stream_split_clients_ctx_t *ctx; + ngx_stream_split_clients_part_t *part; + + ctx = cf->ctx; + value = cf->args->elts; + + part = ngx_array_push(&ctx->parts); + if (part == NULL) { + return NGX_CONF_ERROR; + } + + if (value[0].len == 1 && value[0].data[0] == '*') { + part->percent = 0; + + } else { + if (value[0].len == 0 || value[0].data[value[0].len - 1] != '%') { + goto invalid; + } + + n = ngx_atofp(value[0].data, value[0].len - 1, 2); + if (n == NGX_ERROR || n == 0) { + goto invalid; + } + + part->percent = (uint32_t) n; + } + + part->value.len = value[1].len; + part->value.valid = 1; + part->value.no_cacheable = 0; + part->value.not_found = 0; + part->value.data = value[1].data; + + return NGX_CONF_OK; + +invalid: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid percent value \"%V\"", &value[0]); + return NGX_CONF_ERROR; +} diff --git a/app/nginx/src/stream/ngx_stream_ssl_module.c b/app/nginx/src/stream/ngx_stream_ssl_module.c new file mode 100644 index 0000000..2f242b6 --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_ssl_module.c @@ -0,0 +1,839 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c, + ngx_pool_t *pool, ngx_str_t *s); + + +#define NGX_DEFAULT_CIPHERS "HIGH:!aNULL:!MD5" +#define NGX_DEFAULT_ECDH_CURVE "auto" + + +static ngx_int_t ngx_stream_ssl_handler(ngx_stream_session_t *s); +static ngx_int_t ngx_stream_ssl_init_connection(ngx_ssl_t *ssl, + ngx_connection_t *c); +static void ngx_stream_ssl_handshake_handler(ngx_connection_t *c); +static ngx_int_t ngx_stream_ssl_static_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_ssl_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); + +static ngx_int_t ngx_stream_ssl_add_variables(ngx_conf_t *cf); +static void *ngx_stream_ssl_create_conf(ngx_conf_t *cf); +static char *ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, + void *child); + +static char *ngx_stream_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static ngx_int_t ngx_stream_ssl_init(ngx_conf_t *cf); + + +static ngx_conf_bitmask_t ngx_stream_ssl_protocols[] = { + { ngx_string("SSLv2"), NGX_SSL_SSLv2 }, + { ngx_string("SSLv3"), NGX_SSL_SSLv3 }, + { ngx_string("TLSv1"), NGX_SSL_TLSv1 }, + { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 }, + { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 }, + { ngx_null_string, 0 } +}; + + +static ngx_conf_enum_t ngx_stream_ssl_verify[] = { + { ngx_string("off"), 0 }, + { ngx_string("on"), 1 }, + { ngx_string("optional"), 2 }, + { ngx_string("optional_no_ca"), 3 }, + { ngx_null_string, 0 } +}; + + +static ngx_command_t ngx_stream_ssl_commands[] = { + + { ngx_string("ssl_handshake_timeout"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, handshake_timeout), + NULL }, + + { ngx_string("ssl_certificate"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_array_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, certificates), + NULL }, + + { ngx_string("ssl_certificate_key"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_array_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, certificate_keys), + NULL }, + + { ngx_string("ssl_password_file"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_stream_ssl_password_file, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("ssl_dhparam"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, dhparam), + NULL }, + + { ngx_string("ssl_ecdh_curve"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, ecdh_curve), + NULL }, + + { ngx_string("ssl_protocols"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, + ngx_conf_set_bitmask_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, protocols), + &ngx_stream_ssl_protocols }, + + { ngx_string("ssl_ciphers"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, ciphers), + NULL }, + + { ngx_string("ssl_verify_client"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_enum_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, verify), + &ngx_stream_ssl_verify }, + + { ngx_string("ssl_verify_depth"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, verify_depth), + NULL }, + + { ngx_string("ssl_client_certificate"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, client_certificate), + NULL }, + + { ngx_string("ssl_trusted_certificate"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, trusted_certificate), + NULL }, + + { ngx_string("ssl_prefer_server_ciphers"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, prefer_server_ciphers), + NULL }, + + { ngx_string("ssl_session_cache"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE12, + ngx_stream_ssl_session_cache, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("ssl_session_tickets"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, session_tickets), + NULL }, + + { ngx_string("ssl_session_ticket_key"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_array_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, session_ticket_keys), + NULL }, + + { ngx_string("ssl_session_timeout"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_sec_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, session_timeout), + NULL }, + + { ngx_string("ssl_crl"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, crl), + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_ssl_module_ctx = { + ngx_stream_ssl_add_variables, /* preconfiguration */ + ngx_stream_ssl_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_stream_ssl_create_conf, /* create server configuration */ + ngx_stream_ssl_merge_conf /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_ssl_module = { + NGX_MODULE_V1, + &ngx_stream_ssl_module_ctx, /* module context */ + ngx_stream_ssl_commands, /* module directives */ + NGX_STREAM_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_stream_variable_t ngx_stream_ssl_vars[] = { + + { ngx_string("ssl_protocol"), NULL, ngx_stream_ssl_static_variable, + (uintptr_t) ngx_ssl_get_protocol, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_cipher"), NULL, ngx_stream_ssl_static_variable, + (uintptr_t) ngx_ssl_get_cipher_name, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_ciphers"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_ciphers, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_curves"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_curves, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_session_id"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_session_id, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_session_reused"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_session_reused, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_server_name"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_server_name, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_client_cert"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_certificate, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_client_raw_cert"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_raw_certificate, + NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_client_s_dn"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_subject_dn, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_client_i_dn"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_issuer_dn, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_client_serial"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_serial_number, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_client_fingerprint"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_fingerprint, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_client_verify"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_client_verify, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_client_v_start"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_client_v_start, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_client_v_end"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_client_v_end, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_client_v_remain"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_client_v_remain, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_null_string, NULL, NULL, 0, 0, 0 } +}; + + +static ngx_str_t ngx_stream_ssl_sess_id_ctx = ngx_string("STREAM"); + + +static ngx_int_t +ngx_stream_ssl_handler(ngx_stream_session_t *s) +{ + long rc; + X509 *cert; + ngx_int_t rv; + ngx_connection_t *c; + ngx_stream_ssl_conf_t *sslcf; + + if (!s->ssl) { + return NGX_OK; + } + + c = s->connection; + + sslcf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_module); + + if (c->ssl == NULL) { + c->log->action = "SSL handshaking"; + + if (sslcf->ssl.ctx == NULL) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "no \"ssl_certificate\" is defined " + "in server listening on SSL port"); + return NGX_ERROR; + } + + rv = ngx_stream_ssl_init_connection(&sslcf->ssl, c); + + if (rv != NGX_OK) { + return rv; + } + } + + if (sslcf->verify) { + rc = SSL_get_verify_result(c->ssl->connection); + + if (rc != X509_V_OK + && (sslcf->verify != 3 || !ngx_ssl_verify_error_optional(rc))) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client SSL certificate verify error: (%l:%s)", + rc, X509_verify_cert_error_string(rc)); + + ngx_ssl_remove_cached_session(sslcf->ssl.ctx, + (SSL_get0_session(c->ssl->connection))); + return NGX_ERROR; + } + + if (sslcf->verify == 1) { + cert = SSL_get_peer_certificate(c->ssl->connection); + + if (cert == NULL) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent no required SSL certificate"); + + ngx_ssl_remove_cached_session(sslcf->ssl.ctx, + (SSL_get0_session(c->ssl->connection))); + return NGX_ERROR; + } + + X509_free(cert); + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c) +{ + ngx_int_t rc; + ngx_stream_session_t *s; + ngx_stream_ssl_conf_t *sslcf; + + s = c->data; + + if (ngx_ssl_create_connection(ssl, c, 0) == NGX_ERROR) { + return NGX_ERROR; + } + + rc = ngx_ssl_handshake(c); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_AGAIN) { + sslcf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_module); + + ngx_add_timer(c->read, sslcf->handshake_timeout); + + c->ssl->handler = ngx_stream_ssl_handshake_handler; + + return NGX_AGAIN; + } + + /* rc == NGX_OK */ + + return NGX_OK; +} + + +static void +ngx_stream_ssl_handshake_handler(ngx_connection_t *c) +{ + ngx_stream_session_t *s; + + s = c->data; + + if (!c->ssl->handshaked) { + ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + ngx_stream_core_run_phases(s); +} + + +static ngx_int_t +ngx_stream_ssl_static_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + ngx_ssl_variable_handler_pt handler = (ngx_ssl_variable_handler_pt) data; + + size_t len; + ngx_str_t str; + + if (s->connection->ssl) { + + (void) handler(s->connection, NULL, &str); + + v->data = str.data; + + for (len = 0; v->data[len]; len++) { /* void */ } + + v->len = len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; + } + + v->not_found = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_ssl_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + ngx_ssl_variable_handler_pt handler = (ngx_ssl_variable_handler_pt) data; + + ngx_str_t str; + + if (s->connection->ssl) { + + if (handler(s->connection, s->connection->pool, &str) != NGX_OK) { + return NGX_ERROR; + } + + v->len = str.len; + v->data = str.data; + + if (v->len) { + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; + } + } + + v->not_found = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_ssl_add_variables(ngx_conf_t *cf) +{ + ngx_stream_variable_t *var, *v; + + for (v = ngx_stream_ssl_vars; v->name.len; v++) { + var = ngx_stream_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static void * +ngx_stream_ssl_create_conf(ngx_conf_t *cf) +{ + ngx_stream_ssl_conf_t *scf; + + scf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_ssl_conf_t)); + if (scf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * scf->protocols = 0; + * scf->dhparam = { 0, NULL }; + * scf->ecdh_curve = { 0, NULL }; + * scf->client_certificate = { 0, NULL }; + * scf->trusted_certificate = { 0, NULL }; + * scf->crl = { 0, NULL }; + * scf->ciphers = { 0, NULL }; + * scf->shm_zone = NULL; + */ + + scf->handshake_timeout = NGX_CONF_UNSET_MSEC; + scf->certificates = NGX_CONF_UNSET_PTR; + scf->certificate_keys = NGX_CONF_UNSET_PTR; + scf->passwords = NGX_CONF_UNSET_PTR; + scf->prefer_server_ciphers = NGX_CONF_UNSET; + scf->verify = NGX_CONF_UNSET_UINT; + scf->verify_depth = NGX_CONF_UNSET_UINT; + scf->builtin_session_cache = NGX_CONF_UNSET; + scf->session_timeout = NGX_CONF_UNSET; + scf->session_tickets = NGX_CONF_UNSET; + scf->session_ticket_keys = NGX_CONF_UNSET_PTR; + + return scf; +} + + +static char * +ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_stream_ssl_conf_t *prev = parent; + ngx_stream_ssl_conf_t *conf = child; + + ngx_pool_cleanup_t *cln; + + ngx_conf_merge_msec_value(conf->handshake_timeout, + prev->handshake_timeout, 60000); + + ngx_conf_merge_value(conf->session_timeout, + prev->session_timeout, 300); + + ngx_conf_merge_value(conf->prefer_server_ciphers, + prev->prefer_server_ciphers, 0); + + ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols, + (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1 + |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)); + + ngx_conf_merge_uint_value(conf->verify, prev->verify, 0); + ngx_conf_merge_uint_value(conf->verify_depth, prev->verify_depth, 1); + + ngx_conf_merge_ptr_value(conf->certificates, prev->certificates, NULL); + ngx_conf_merge_ptr_value(conf->certificate_keys, prev->certificate_keys, + NULL); + + ngx_conf_merge_ptr_value(conf->passwords, prev->passwords, NULL); + + ngx_conf_merge_str_value(conf->dhparam, prev->dhparam, ""); + + ngx_conf_merge_str_value(conf->client_certificate, prev->client_certificate, + ""); + ngx_conf_merge_str_value(conf->trusted_certificate, + prev->trusted_certificate, ""); + ngx_conf_merge_str_value(conf->crl, prev->crl, ""); + + ngx_conf_merge_str_value(conf->ecdh_curve, prev->ecdh_curve, + NGX_DEFAULT_ECDH_CURVE); + + ngx_conf_merge_str_value(conf->ciphers, prev->ciphers, NGX_DEFAULT_CIPHERS); + + + conf->ssl.log = cf->log; + + if (conf->certificates == NULL) { + return NGX_CONF_OK; + } + + if (conf->certificate_keys == NULL + || conf->certificate_keys->nelts < conf->certificates->nelts) + { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"ssl_certificate_key\" is defined " + "for certificate \"%V\"", + ((ngx_str_t *) conf->certificates->elts) + + conf->certificates->nelts - 1); + return NGX_CONF_ERROR; + } + + if (ngx_ssl_create(&conf->ssl, conf->protocols, NULL) != NGX_OK) { + return NGX_CONF_ERROR; + } + + cln = ngx_pool_cleanup_add(cf->pool, 0); + if (cln == NULL) { + return NGX_CONF_ERROR; + } + + cln->handler = ngx_ssl_cleanup_ctx; + cln->data = &conf->ssl; + + if (ngx_ssl_certificates(cf, &conf->ssl, conf->certificates, + conf->certificate_keys, conf->passwords) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + if (ngx_ssl_ciphers(cf, &conf->ssl, &conf->ciphers, + conf->prefer_server_ciphers) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + if (conf->verify) { + + if (conf->client_certificate.len == 0 && conf->verify != 3) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate for ssl_client_verify"); + return NGX_CONF_ERROR; + } + + if (ngx_ssl_client_certificate(cf, &conf->ssl, + &conf->client_certificate, + conf->verify_depth) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + if (ngx_ssl_trusted_certificate(cf, &conf->ssl, + &conf->trusted_certificate, + conf->verify_depth) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + if (ngx_ssl_crl(cf, &conf->ssl, &conf->crl) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + + if (ngx_ssl_dhparam(cf, &conf->ssl, &conf->dhparam) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (ngx_ssl_ecdh_curve(cf, &conf->ssl, &conf->ecdh_curve) != NGX_OK) { + return NGX_CONF_ERROR; + } + + ngx_conf_merge_value(conf->builtin_session_cache, + prev->builtin_session_cache, NGX_SSL_NONE_SCACHE); + + if (conf->shm_zone == NULL) { + conf->shm_zone = prev->shm_zone; + } + + if (ngx_ssl_session_cache(&conf->ssl, &ngx_stream_ssl_sess_id_ctx, + conf->builtin_session_cache, + conf->shm_zone, conf->session_timeout) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + ngx_conf_merge_value(conf->session_tickets, + prev->session_tickets, 1); + +#ifdef SSL_OP_NO_TICKET + if (!conf->session_tickets) { + SSL_CTX_set_options(conf->ssl.ctx, SSL_OP_NO_TICKET); + } +#endif + + ngx_conf_merge_ptr_value(conf->session_ticket_keys, + prev->session_ticket_keys, NULL); + + if (ngx_ssl_session_ticket_keys(cf, &conf->ssl, conf->session_ticket_keys) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_stream_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_ssl_conf_t *scf = conf; + + ngx_str_t *value; + + if (scf->passwords != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + value = cf->args->elts; + + scf->passwords = ngx_ssl_read_password_file(cf, &value[1]); + + if (scf->passwords == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_ssl_conf_t *scf = conf; + + size_t len; + ngx_str_t *value, name, size; + ngx_int_t n; + ngx_uint_t i, j; + + value = cf->args->elts; + + for (i = 1; i < cf->args->nelts; i++) { + + if (ngx_strcmp(value[i].data, "off") == 0) { + scf->builtin_session_cache = NGX_SSL_NO_SCACHE; + continue; + } + + if (ngx_strcmp(value[i].data, "none") == 0) { + scf->builtin_session_cache = NGX_SSL_NONE_SCACHE; + continue; + } + + if (ngx_strcmp(value[i].data, "builtin") == 0) { + scf->builtin_session_cache = NGX_SSL_DFLT_BUILTIN_SCACHE; + continue; + } + + if (value[i].len > sizeof("builtin:") - 1 + && ngx_strncmp(value[i].data, "builtin:", sizeof("builtin:") - 1) + == 0) + { + n = ngx_atoi(value[i].data + sizeof("builtin:") - 1, + value[i].len - (sizeof("builtin:") - 1)); + + if (n == NGX_ERROR) { + goto invalid; + } + + scf->builtin_session_cache = n; + + continue; + } + + if (value[i].len > sizeof("shared:") - 1 + && ngx_strncmp(value[i].data, "shared:", sizeof("shared:") - 1) + == 0) + { + len = 0; + + for (j = sizeof("shared:") - 1; j < value[i].len; j++) { + if (value[i].data[j] == ':') { + break; + } + + len++; + } + + if (len == 0) { + goto invalid; + } + + name.len = len; + name.data = value[i].data + sizeof("shared:") - 1; + + size.len = value[i].len - j - 1; + size.data = name.data + len + 1; + + n = ngx_parse_size(&size); + + if (n == NGX_ERROR) { + goto invalid; + } + + if (n < (ngx_int_t) (8 * ngx_pagesize)) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "session cache \"%V\" is too small", + &value[i]); + + return NGX_CONF_ERROR; + } + + scf->shm_zone = ngx_shared_memory_add(cf, &name, n, + &ngx_stream_ssl_module); + if (scf->shm_zone == NULL) { + return NGX_CONF_ERROR; + } + + scf->shm_zone->init = ngx_ssl_session_cache_init; + + continue; + } + + goto invalid; + } + + if (scf->shm_zone && scf->builtin_session_cache == NGX_CONF_UNSET) { + scf->builtin_session_cache = NGX_SSL_NO_BUILTIN_SCACHE; + } + + return NGX_CONF_OK; + +invalid: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid session cache \"%V\"", &value[i]); + + return NGX_CONF_ERROR; +} + + +static ngx_int_t +ngx_stream_ssl_init(ngx_conf_t *cf) +{ + ngx_stream_handler_pt *h; + ngx_stream_core_main_conf_t *cmcf; + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + + h = ngx_array_push(&cmcf->phases[NGX_STREAM_SSL_PHASE].handlers); + if (h == NULL) { + return NGX_ERROR; + } + + *h = ngx_stream_ssl_handler; + + return NGX_OK; +} diff --git a/app/nginx/src/stream/ngx_stream_ssl_module.h b/app/nginx/src/stream/ngx_stream_ssl_module.h new file mode 100644 index 0000000..65f5d45 --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_ssl_module.h @@ -0,0 +1,56 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_STREAM_SSL_H_INCLUDED_ +#define _NGX_STREAM_SSL_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct { + ngx_msec_t handshake_timeout; + + ngx_flag_t prefer_server_ciphers; + + ngx_ssl_t ssl; + + ngx_uint_t protocols; + + ngx_uint_t verify; + ngx_uint_t verify_depth; + + ssize_t builtin_session_cache; + + time_t session_timeout; + + ngx_array_t *certificates; + ngx_array_t *certificate_keys; + + ngx_str_t dhparam; + ngx_str_t ecdh_curve; + ngx_str_t client_certificate; + ngx_str_t trusted_certificate; + ngx_str_t crl; + + ngx_str_t ciphers; + + ngx_array_t *passwords; + + ngx_shm_zone_t *shm_zone; + + ngx_flag_t session_tickets; + ngx_array_t *session_ticket_keys; +} ngx_stream_ssl_conf_t; + + +extern ngx_module_t ngx_stream_ssl_module; + + +#endif /* _NGX_STREAM_SSL_H_INCLUDED_ */ diff --git a/app/nginx/src/stream/ngx_stream_ssl_preread_module.c b/app/nginx/src/stream/ngx_stream_ssl_preread_module.c new file mode 100644 index 0000000..2040b4f --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_ssl_preread_module.c @@ -0,0 +1,449 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef struct { + ngx_flag_t enabled; +} ngx_stream_ssl_preread_srv_conf_t; + + +typedef struct { + size_t left; + size_t size; + u_char *pos; + u_char *dst; + u_char buf[4]; + ngx_str_t host; + ngx_log_t *log; + ngx_pool_t *pool; + ngx_uint_t state; +} ngx_stream_ssl_preread_ctx_t; + + +static ngx_int_t ngx_stream_ssl_preread_handler(ngx_stream_session_t *s); +static ngx_int_t ngx_stream_ssl_preread_parse_record( + ngx_stream_ssl_preread_ctx_t *ctx, u_char *pos, u_char *last); +static ngx_int_t ngx_stream_ssl_preread_server_name_variable( + ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_ssl_preread_add_variables(ngx_conf_t *cf); +static void *ngx_stream_ssl_preread_create_srv_conf(ngx_conf_t *cf); +static char *ngx_stream_ssl_preread_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); +static ngx_int_t ngx_stream_ssl_preread_init(ngx_conf_t *cf); + + +static ngx_command_t ngx_stream_ssl_preread_commands[] = { + + { ngx_string("ssl_preread"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_preread_srv_conf_t, enabled), + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_ssl_preread_module_ctx = { + ngx_stream_ssl_preread_add_variables, /* preconfiguration */ + ngx_stream_ssl_preread_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_stream_ssl_preread_create_srv_conf, /* create server configuration */ + ngx_stream_ssl_preread_merge_srv_conf /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_ssl_preread_module = { + NGX_MODULE_V1, + &ngx_stream_ssl_preread_module_ctx, /* module context */ + ngx_stream_ssl_preread_commands, /* module directives */ + NGX_STREAM_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_stream_variable_t ngx_stream_ssl_preread_vars[] = { + + { ngx_string("ssl_preread_server_name"), NULL, + ngx_stream_ssl_preread_server_name_variable, 0, 0, 0 }, + + { ngx_null_string, NULL, NULL, 0, 0, 0 } +}; + + +static ngx_int_t +ngx_stream_ssl_preread_handler(ngx_stream_session_t *s) +{ + u_char *last, *p; + size_t len; + ngx_int_t rc; + ngx_connection_t *c; + ngx_stream_ssl_preread_ctx_t *ctx; + ngx_stream_ssl_preread_srv_conf_t *sscf; + + c = s->connection; + + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, "ssl preread handler"); + + sscf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_preread_module); + + if (!sscf->enabled) { + return NGX_DECLINED; + } + + if (c->type != SOCK_STREAM) { + return NGX_DECLINED; + } + + if (c->buffer == NULL) { + return NGX_AGAIN; + } + + ctx = ngx_stream_get_module_ctx(s, ngx_stream_ssl_preread_module); + if (ctx == NULL) { + ctx = ngx_pcalloc(c->pool, sizeof(ngx_stream_ssl_preread_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_stream_set_ctx(s, ctx, ngx_stream_ssl_preread_module); + + ctx->pool = c->pool; + ctx->log = c->log; + ctx->pos = c->buffer->pos; + } + + p = ctx->pos; + last = c->buffer->last; + + while (last - p >= 5) { + + if (p[0] != 0x16) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0, + "ssl preread: not a handshake"); + return NGX_DECLINED; + } + + if (p[1] != 3) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0, + "ssl preread: unsupported SSL version"); + return NGX_DECLINED; + } + + len = (p[3] << 8) + p[4]; + + /* read the whole record before parsing */ + if ((size_t) (last - p) < len + 5) { + break; + } + + p += 5; + + rc = ngx_stream_ssl_preread_parse_record(ctx, p, p + len); + if (rc != NGX_AGAIN) { + return rc; + } + + p += len; + } + + ctx->pos = p; + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_stream_ssl_preread_parse_record(ngx_stream_ssl_preread_ctx_t *ctx, + u_char *pos, u_char *last) +{ + size_t left, n, size; + u_char *dst, *p; + + enum { + sw_start = 0, + sw_header, /* handshake msg_type, length */ + sw_head_tail, /* version, random */ + sw_sid_len, /* session_id length */ + sw_sid, /* session_id */ + sw_cs_len, /* cipher_suites length */ + sw_cs, /* cipher_suites */ + sw_cm_len, /* compression_methods length */ + sw_cm, /* compression_methods */ + sw_ext, /* extension */ + sw_ext_header, /* extension_type, extension_data length */ + sw_sni_len, /* SNI length */ + sw_sni_host_head, /* SNI name_type, host_name length */ + sw_sni_host /* SNI host_name */ + } state; + + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, ctx->log, 0, + "ssl preread: state %ui left %z", ctx->state, ctx->left); + + state = ctx->state; + size = ctx->size; + left = ctx->left; + dst = ctx->dst; + p = ctx->buf; + + for ( ;; ) { + n = ngx_min((size_t) (last - pos), size); + + if (dst) { + dst = ngx_cpymem(dst, pos, n); + } + + pos += n; + size -= n; + left -= n; + + if (size != 0) { + break; + } + + switch (state) { + + case sw_start: + state = sw_header; + dst = p; + size = 4; + left = size; + break; + + case sw_header: + if (p[0] != 1) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0, + "ssl preread: not a client hello"); + return NGX_DECLINED; + } + + state = sw_head_tail; + dst = NULL; + size = 34; + left = (p[1] << 16) + (p[2] << 8) + p[3]; + break; + + case sw_head_tail: + state = sw_sid_len; + dst = p; + size = 1; + break; + + case sw_sid_len: + state = sw_sid; + dst = NULL; + size = p[0]; + break; + + case sw_sid: + state = sw_cs_len; + dst = p; + size = 2; + break; + + case sw_cs_len: + state = sw_cs; + dst = NULL; + size = (p[0] << 8) + p[1]; + break; + + case sw_cs: + state = sw_cm_len; + dst = p; + size = 1; + break; + + case sw_cm_len: + state = sw_cm; + dst = NULL; + size = p[0]; + break; + + case sw_cm: + if (left == 0) { + /* no extensions */ + return NGX_OK; + } + + state = sw_ext; + dst = p; + size = 2; + break; + + case sw_ext: + if (left == 0) { + return NGX_OK; + } + + state = sw_ext_header; + dst = p; + size = 4; + break; + + case sw_ext_header: + if (p[0] == 0 && p[1] == 0) { + /* SNI extension */ + state = sw_sni_len; + dst = NULL; + size = 2; + break; + } + + state = sw_ext; + dst = NULL; + size = (p[2] << 8) + p[3]; + break; + + case sw_sni_len: + state = sw_sni_host_head; + dst = p; + size = 3; + break; + + case sw_sni_host_head: + if (p[0] != 0) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0, + "ssl preread: SNI hostname type is not DNS"); + return NGX_DECLINED; + } + + state = sw_sni_host; + size = (p[1] << 8) + p[2]; + + ctx->host.data = ngx_pnalloc(ctx->pool, size); + if (ctx->host.data == NULL) { + return NGX_ERROR; + } + + dst = ctx->host.data; + break; + + case sw_sni_host: + ctx->host.len = (p[1] << 8) + p[2]; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, ctx->log, 0, + "ssl preread: SNI hostname \"%V\"", &ctx->host); + return NGX_OK; + } + + if (left < size) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0, + "ssl preread: failed to parse handshake"); + return NGX_DECLINED; + } + } + + ctx->state = state; + ctx->size = size; + ctx->left = left; + ctx->dst = dst; + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_stream_ssl_preread_server_name_variable(ngx_stream_session_t *s, + ngx_variable_value_t *v, uintptr_t data) +{ + ngx_stream_ssl_preread_ctx_t *ctx; + + ctx = ngx_stream_get_module_ctx(s, ngx_stream_ssl_preread_module); + + if (ctx == NULL) { + v->not_found = 1; + return NGX_OK; + } + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->len = ctx->host.len; + v->data = ctx->host.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_ssl_preread_add_variables(ngx_conf_t *cf) +{ + ngx_stream_variable_t *var, *v; + + for (v = ngx_stream_ssl_preread_vars; v->name.len; v++) { + var = ngx_stream_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static void * +ngx_stream_ssl_preread_create_srv_conf(ngx_conf_t *cf) +{ + ngx_stream_ssl_preread_srv_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_ssl_preread_srv_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->enabled = NGX_CONF_UNSET; + + return conf; +} + + +static char * +ngx_stream_ssl_preread_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_stream_ssl_preread_srv_conf_t *prev = parent; + ngx_stream_ssl_preread_srv_conf_t *conf = child; + + ngx_conf_merge_value(conf->enabled, prev->enabled, 0); + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_stream_ssl_preread_init(ngx_conf_t *cf) +{ + ngx_stream_handler_pt *h; + ngx_stream_core_main_conf_t *cmcf; + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + + h = ngx_array_push(&cmcf->phases[NGX_STREAM_PREREAD_PHASE].handlers); + if (h == NULL) { + return NGX_ERROR; + } + + *h = ngx_stream_ssl_preread_handler; + + return NGX_OK; +} diff --git a/app/nginx/src/stream/ngx_stream_upstream.c b/app/nginx/src/stream/ngx_stream_upstream.c new file mode 100644 index 0000000..c9e1784 --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_upstream.c @@ -0,0 +1,717 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +static ngx_int_t ngx_stream_upstream_add_variables(ngx_conf_t *cf); +static ngx_int_t ngx_stream_upstream_addr_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_upstream_response_time_variable( + ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_upstream_bytes_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); + +static char *ngx_stream_upstream(ngx_conf_t *cf, ngx_command_t *cmd, + void *dummy); +static char *ngx_stream_upstream_server(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static void *ngx_stream_upstream_create_main_conf(ngx_conf_t *cf); +static char *ngx_stream_upstream_init_main_conf(ngx_conf_t *cf, void *conf); + + +static ngx_command_t ngx_stream_upstream_commands[] = { + + { ngx_string("upstream"), + NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1, + ngx_stream_upstream, + 0, + 0, + NULL }, + + { ngx_string("server"), + NGX_STREAM_UPS_CONF|NGX_CONF_1MORE, + ngx_stream_upstream_server, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_upstream_module_ctx = { + ngx_stream_upstream_add_variables, /* preconfiguration */ + NULL, /* postconfiguration */ + + ngx_stream_upstream_create_main_conf, /* create main configuration */ + ngx_stream_upstream_init_main_conf, /* init main configuration */ + + NULL, /* create server configuration */ + NULL /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_upstream_module = { + NGX_MODULE_V1, + &ngx_stream_upstream_module_ctx, /* module context */ + ngx_stream_upstream_commands, /* module directives */ + NGX_STREAM_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_stream_variable_t ngx_stream_upstream_vars[] = { + + { ngx_string("upstream_addr"), NULL, + ngx_stream_upstream_addr_variable, 0, + NGX_STREAM_VAR_NOCACHEABLE, 0 }, + + { ngx_string("upstream_bytes_sent"), NULL, + ngx_stream_upstream_bytes_variable, 0, + NGX_STREAM_VAR_NOCACHEABLE, 0 }, + + { ngx_string("upstream_connect_time"), NULL, + ngx_stream_upstream_response_time_variable, 2, + NGX_STREAM_VAR_NOCACHEABLE, 0 }, + + { ngx_string("upstream_first_byte_time"), NULL, + ngx_stream_upstream_response_time_variable, 1, + NGX_STREAM_VAR_NOCACHEABLE, 0 }, + + { ngx_string("upstream_session_time"), NULL, + ngx_stream_upstream_response_time_variable, 0, + NGX_STREAM_VAR_NOCACHEABLE, 0 }, + + { ngx_string("upstream_bytes_received"), NULL, + ngx_stream_upstream_bytes_variable, 1, + NGX_STREAM_VAR_NOCACHEABLE, 0 }, + + { ngx_null_string, NULL, NULL, 0, 0, 0 } +}; + + +static ngx_int_t +ngx_stream_upstream_add_variables(ngx_conf_t *cf) +{ + ngx_stream_variable_t *var, *v; + + for (v = ngx_stream_upstream_vars; v->name.len; v++) { + var = ngx_stream_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_upstream_addr_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + u_char *p; + size_t len; + ngx_uint_t i; + ngx_stream_upstream_state_t *state; + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + if (s->upstream_states == NULL || s->upstream_states->nelts == 0) { + v->not_found = 1; + return NGX_OK; + } + + len = 0; + state = s->upstream_states->elts; + + for (i = 0; i < s->upstream_states->nelts; i++) { + if (state[i].peer) { + len += state[i].peer->len; + } + + len += 2; + } + + p = ngx_pnalloc(s->connection->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + v->data = p; + + i = 0; + + for ( ;; ) { + if (state[i].peer) { + p = ngx_cpymem(p, state[i].peer->data, state[i].peer->len); + } + + if (++i == s->upstream_states->nelts) { + break; + } + + *p++ = ','; + *p++ = ' '; + } + + v->len = p - v->data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_upstream_bytes_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + u_char *p; + size_t len; + ngx_uint_t i; + ngx_stream_upstream_state_t *state; + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + if (s->upstream_states == NULL || s->upstream_states->nelts == 0) { + v->not_found = 1; + return NGX_OK; + } + + len = s->upstream_states->nelts * (NGX_OFF_T_LEN + 2); + + p = ngx_pnalloc(s->connection->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + v->data = p; + + i = 0; + state = s->upstream_states->elts; + + for ( ;; ) { + + if (data == 1) { + p = ngx_sprintf(p, "%O", state[i].bytes_received); + + } else { + p = ngx_sprintf(p, "%O", state[i].bytes_sent); + } + + if (++i == s->upstream_states->nelts) { + break; + } + + *p++ = ','; + *p++ = ' '; + } + + v->len = p - v->data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_upstream_response_time_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + u_char *p; + size_t len; + ngx_uint_t i; + ngx_msec_int_t ms; + ngx_stream_upstream_state_t *state; + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + if (s->upstream_states == NULL || s->upstream_states->nelts == 0) { + v->not_found = 1; + return NGX_OK; + } + + len = s->upstream_states->nelts * (NGX_TIME_T_LEN + 4 + 2); + + p = ngx_pnalloc(s->connection->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + v->data = p; + + i = 0; + state = s->upstream_states->elts; + + for ( ;; ) { + + if (data == 1) { + if (state[i].first_byte_time == (ngx_msec_t) -1) { + *p++ = '-'; + goto next; + } + + ms = state[i].first_byte_time; + + } else if (data == 2 && state[i].connect_time != (ngx_msec_t) -1) { + ms = state[i].connect_time; + + } else { + ms = state[i].response_time; + } + + ms = ngx_max(ms, 0); + p = ngx_sprintf(p, "%T.%03M", (time_t) ms / 1000, ms % 1000); + + next: + + if (++i == s->upstream_states->nelts) { + break; + } + + *p++ = ','; + *p++ = ' '; + } + + v->len = p - v->data; + + return NGX_OK; +} + + +static char * +ngx_stream_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy) +{ + char *rv; + void *mconf; + ngx_str_t *value; + ngx_url_t u; + ngx_uint_t m; + ngx_conf_t pcf; + ngx_stream_module_t *module; + ngx_stream_conf_ctx_t *ctx, *stream_ctx; + ngx_stream_upstream_srv_conf_t *uscf; + + ngx_memzero(&u, sizeof(ngx_url_t)); + + value = cf->args->elts; + u.host = value[1]; + u.no_resolve = 1; + u.no_port = 1; + + uscf = ngx_stream_upstream_add(cf, &u, NGX_STREAM_UPSTREAM_CREATE + |NGX_STREAM_UPSTREAM_WEIGHT + |NGX_STREAM_UPSTREAM_MAX_CONNS + |NGX_STREAM_UPSTREAM_MAX_FAILS + |NGX_STREAM_UPSTREAM_FAIL_TIMEOUT + |NGX_STREAM_UPSTREAM_DOWN + |NGX_STREAM_UPSTREAM_BACKUP); + if (uscf == NULL) { + return NGX_CONF_ERROR; + } + + + ctx = ngx_pcalloc(cf->pool, sizeof(ngx_stream_conf_ctx_t)); + if (ctx == NULL) { + return NGX_CONF_ERROR; + } + + stream_ctx = cf->ctx; + ctx->main_conf = stream_ctx->main_conf; + + /* the upstream{}'s srv_conf */ + + ctx->srv_conf = ngx_pcalloc(cf->pool, + sizeof(void *) * ngx_stream_max_module); + if (ctx->srv_conf == NULL) { + return NGX_CONF_ERROR; + } + + ctx->srv_conf[ngx_stream_upstream_module.ctx_index] = uscf; + + uscf->srv_conf = ctx->srv_conf; + + for (m = 0; cf->cycle->modules[m]; m++) { + if (cf->cycle->modules[m]->type != NGX_STREAM_MODULE) { + continue; + } + + module = cf->cycle->modules[m]->ctx; + + if (module->create_srv_conf) { + mconf = module->create_srv_conf(cf); + if (mconf == NULL) { + return NGX_CONF_ERROR; + } + + ctx->srv_conf[cf->cycle->modules[m]->ctx_index] = mconf; + } + } + + uscf->servers = ngx_array_create(cf->pool, 4, + sizeof(ngx_stream_upstream_server_t)); + if (uscf->servers == NULL) { + return NGX_CONF_ERROR; + } + + + /* parse inside upstream{} */ + + pcf = *cf; + cf->ctx = ctx; + cf->cmd_type = NGX_STREAM_UPS_CONF; + + rv = ngx_conf_parse(cf, NULL); + + *cf = pcf; + + if (rv != NGX_CONF_OK) { + return rv; + } + + if (uscf->servers->nelts == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "no servers are inside upstream"); + return NGX_CONF_ERROR; + } + + return rv; +} + + +static char * +ngx_stream_upstream_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_upstream_srv_conf_t *uscf = conf; + + time_t fail_timeout; + ngx_str_t *value, s; + ngx_url_t u; + ngx_int_t weight, max_conns, max_fails; + ngx_uint_t i; + ngx_stream_upstream_server_t *us; + + us = ngx_array_push(uscf->servers); + if (us == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(us, sizeof(ngx_stream_upstream_server_t)); + + value = cf->args->elts; + + weight = 1; + max_conns = 0; + max_fails = 1; + fail_timeout = 10; + + for (i = 2; i < cf->args->nelts; i++) { + + if (ngx_strncmp(value[i].data, "weight=", 7) == 0) { + + if (!(uscf->flags & NGX_STREAM_UPSTREAM_WEIGHT)) { + goto not_supported; + } + + weight = ngx_atoi(&value[i].data[7], value[i].len - 7); + + if (weight == NGX_ERROR || weight == 0) { + goto invalid; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "max_conns=", 10) == 0) { + + if (!(uscf->flags & NGX_STREAM_UPSTREAM_MAX_CONNS)) { + goto not_supported; + } + + max_conns = ngx_atoi(&value[i].data[10], value[i].len - 10); + + if (max_conns == NGX_ERROR) { + goto invalid; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "max_fails=", 10) == 0) { + + if (!(uscf->flags & NGX_STREAM_UPSTREAM_MAX_FAILS)) { + goto not_supported; + } + + max_fails = ngx_atoi(&value[i].data[10], value[i].len - 10); + + if (max_fails == NGX_ERROR) { + goto invalid; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "fail_timeout=", 13) == 0) { + + if (!(uscf->flags & NGX_STREAM_UPSTREAM_FAIL_TIMEOUT)) { + goto not_supported; + } + + s.len = value[i].len - 13; + s.data = &value[i].data[13]; + + fail_timeout = ngx_parse_time(&s, 1); + + if (fail_timeout == (time_t) NGX_ERROR) { + goto invalid; + } + + continue; + } + + if (ngx_strcmp(value[i].data, "backup") == 0) { + + if (!(uscf->flags & NGX_STREAM_UPSTREAM_BACKUP)) { + goto not_supported; + } + + us->backup = 1; + + continue; + } + + if (ngx_strcmp(value[i].data, "down") == 0) { + + if (!(uscf->flags & NGX_STREAM_UPSTREAM_DOWN)) { + goto not_supported; + } + + us->down = 1; + + continue; + } + + goto invalid; + } + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = value[1]; + + if (ngx_parse_url(cf->pool, &u) != NGX_OK) { + if (u.err) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%s in upstream \"%V\"", u.err, &u.url); + } + + return NGX_CONF_ERROR; + } + + if (u.no_port) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "no port in upstream \"%V\"", &u.url); + return NGX_CONF_ERROR; + } + + us->name = u.url; + us->addrs = u.addrs; + us->naddrs = u.naddrs; + us->weight = weight; + us->max_conns = max_conns; + us->max_fails = max_fails; + us->fail_timeout = fail_timeout; + + return NGX_CONF_OK; + +invalid: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[i]); + + return NGX_CONF_ERROR; + +not_supported: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "balancing method does not support parameter \"%V\"", + &value[i]); + + return NGX_CONF_ERROR; +} + + +ngx_stream_upstream_srv_conf_t * +ngx_stream_upstream_add(ngx_conf_t *cf, ngx_url_t *u, ngx_uint_t flags) +{ + ngx_uint_t i; + ngx_stream_upstream_server_t *us; + ngx_stream_upstream_srv_conf_t *uscf, **uscfp; + ngx_stream_upstream_main_conf_t *umcf; + + if (!(flags & NGX_STREAM_UPSTREAM_CREATE)) { + + if (ngx_parse_url(cf->pool, u) != NGX_OK) { + if (u->err) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%s in upstream \"%V\"", u->err, &u->url); + } + + return NULL; + } + } + + umcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_upstream_module); + + uscfp = umcf->upstreams.elts; + + for (i = 0; i < umcf->upstreams.nelts; i++) { + + if (uscfp[i]->host.len != u->host.len + || ngx_strncasecmp(uscfp[i]->host.data, u->host.data, u->host.len) + != 0) + { + continue; + } + + if ((flags & NGX_STREAM_UPSTREAM_CREATE) + && (uscfp[i]->flags & NGX_STREAM_UPSTREAM_CREATE)) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "duplicate upstream \"%V\"", &u->host); + return NULL; + } + + if ((uscfp[i]->flags & NGX_STREAM_UPSTREAM_CREATE) && !u->no_port) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "upstream \"%V\" may not have port %d", + &u->host, u->port); + return NULL; + } + + if ((flags & NGX_STREAM_UPSTREAM_CREATE) && !uscfp[i]->no_port) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "upstream \"%V\" may not have port %d in %s:%ui", + &u->host, uscfp[i]->port, + uscfp[i]->file_name, uscfp[i]->line); + return NULL; + } + + if (uscfp[i]->port != u->port) { + continue; + } + + if (flags & NGX_STREAM_UPSTREAM_CREATE) { + uscfp[i]->flags = flags; + } + + return uscfp[i]; + } + + uscf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upstream_srv_conf_t)); + if (uscf == NULL) { + return NULL; + } + + uscf->flags = flags; + uscf->host = u->host; + uscf->file_name = cf->conf_file->file.name.data; + uscf->line = cf->conf_file->line; + uscf->port = u->port; + uscf->no_port = u->no_port; + + if (u->naddrs == 1 && (u->port || u->family == AF_UNIX)) { + uscf->servers = ngx_array_create(cf->pool, 1, + sizeof(ngx_stream_upstream_server_t)); + if (uscf->servers == NULL) { + return NULL; + } + + us = ngx_array_push(uscf->servers); + if (us == NULL) { + return NULL; + } + + ngx_memzero(us, sizeof(ngx_stream_upstream_server_t)); + + us->addrs = u->addrs; + us->naddrs = 1; + } + + uscfp = ngx_array_push(&umcf->upstreams); + if (uscfp == NULL) { + return NULL; + } + + *uscfp = uscf; + + return uscf; +} + + +static void * +ngx_stream_upstream_create_main_conf(ngx_conf_t *cf) +{ + ngx_stream_upstream_main_conf_t *umcf; + + umcf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upstream_main_conf_t)); + if (umcf == NULL) { + return NULL; + } + + if (ngx_array_init(&umcf->upstreams, cf->pool, 4, + sizeof(ngx_stream_upstream_srv_conf_t *)) + != NGX_OK) + { + return NULL; + } + + return umcf; +} + + +static char * +ngx_stream_upstream_init_main_conf(ngx_conf_t *cf, void *conf) +{ + ngx_stream_upstream_main_conf_t *umcf = conf; + + ngx_uint_t i; + ngx_stream_upstream_init_pt init; + ngx_stream_upstream_srv_conf_t **uscfp; + + uscfp = umcf->upstreams.elts; + + for (i = 0; i < umcf->upstreams.nelts; i++) { + + init = uscfp[i]->peer.init_upstream + ? uscfp[i]->peer.init_upstream + : ngx_stream_upstream_init_round_robin; + + if (init(cf, uscfp[i]) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + + return NGX_CONF_OK; +} diff --git a/app/nginx/src/stream/ngx_stream_upstream.h b/app/nginx/src/stream/ngx_stream_upstream.h new file mode 100644 index 0000000..90076e0 --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_upstream.h @@ -0,0 +1,154 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_STREAM_UPSTREAM_H_INCLUDED_ +#define _NGX_STREAM_UPSTREAM_H_INCLUDED_ + + +#include +#include +#include +#include + + +#define NGX_STREAM_UPSTREAM_CREATE 0x0001 +#define NGX_STREAM_UPSTREAM_WEIGHT 0x0002 +#define NGX_STREAM_UPSTREAM_MAX_FAILS 0x0004 +#define NGX_STREAM_UPSTREAM_FAIL_TIMEOUT 0x0008 +#define NGX_STREAM_UPSTREAM_DOWN 0x0010 +#define NGX_STREAM_UPSTREAM_BACKUP 0x0020 +#define NGX_STREAM_UPSTREAM_MAX_CONNS 0x0100 + + +#define NGX_STREAM_UPSTREAM_NOTIFY_CONNECT 0x1 + + +typedef struct { + ngx_array_t upstreams; + /* ngx_stream_upstream_srv_conf_t */ +} ngx_stream_upstream_main_conf_t; + + +typedef struct ngx_stream_upstream_srv_conf_s ngx_stream_upstream_srv_conf_t; + + +typedef ngx_int_t (*ngx_stream_upstream_init_pt)(ngx_conf_t *cf, + ngx_stream_upstream_srv_conf_t *us); +typedef ngx_int_t (*ngx_stream_upstream_init_peer_pt)(ngx_stream_session_t *s, + ngx_stream_upstream_srv_conf_t *us); + + +typedef struct { + ngx_stream_upstream_init_pt init_upstream; + ngx_stream_upstream_init_peer_pt init; + void *data; +} ngx_stream_upstream_peer_t; + + +typedef struct { + ngx_str_t name; + ngx_addr_t *addrs; + ngx_uint_t naddrs; + ngx_uint_t weight; + ngx_uint_t max_conns; + ngx_uint_t max_fails; + time_t fail_timeout; + ngx_msec_t slow_start; + + unsigned down:1; + unsigned backup:1; + + NGX_COMPAT_BEGIN(4) + NGX_COMPAT_END +} ngx_stream_upstream_server_t; + + +struct ngx_stream_upstream_srv_conf_s { + ngx_stream_upstream_peer_t peer; + void **srv_conf; + + ngx_array_t *servers; + /* ngx_stream_upstream_server_t */ + + ngx_uint_t flags; + ngx_str_t host; + u_char *file_name; + ngx_uint_t line; + in_port_t port; + ngx_uint_t no_port; /* unsigned no_port:1 */ + +#if (NGX_STREAM_UPSTREAM_ZONE) + ngx_shm_zone_t *shm_zone; +#endif +}; + + +typedef struct { + ngx_msec_t response_time; + ngx_msec_t connect_time; + ngx_msec_t first_byte_time; + off_t bytes_sent; + off_t bytes_received; + + ngx_str_t *peer; +} ngx_stream_upstream_state_t; + + +typedef struct { + ngx_str_t host; + in_port_t port; + ngx_uint_t no_port; /* unsigned no_port:1 */ + + ngx_uint_t naddrs; + ngx_resolver_addr_t *addrs; + + struct sockaddr *sockaddr; + socklen_t socklen; + ngx_str_t name; + + ngx_resolver_ctx_t *ctx; +} ngx_stream_upstream_resolved_t; + + +typedef struct { + ngx_peer_connection_t peer; + + ngx_buf_t downstream_buf; + ngx_buf_t upstream_buf; + + ngx_chain_t *free; + ngx_chain_t *upstream_out; + ngx_chain_t *upstream_busy; + ngx_chain_t *downstream_out; + ngx_chain_t *downstream_busy; + + off_t received; + time_t start_sec; + ngx_uint_t responses; + + ngx_str_t ssl_name; + + ngx_stream_upstream_srv_conf_t *upstream; + ngx_stream_upstream_resolved_t *resolved; + ngx_stream_upstream_state_t *state; + unsigned connected:1; + unsigned proxy_protocol:1; +} ngx_stream_upstream_t; + + +ngx_stream_upstream_srv_conf_t *ngx_stream_upstream_add(ngx_conf_t *cf, + ngx_url_t *u, ngx_uint_t flags); + + +#define ngx_stream_conf_upstream_srv_conf(uscf, module) \ + uscf->srv_conf[module.ctx_index] + + +extern ngx_module_t ngx_stream_upstream_module; + + +#endif /* _NGX_STREAM_UPSTREAM_H_INCLUDED_ */ diff --git a/app/nginx/src/stream/ngx_stream_upstream_hash_module.c b/app/nginx/src/stream/ngx_stream_upstream_hash_module.c new file mode 100644 index 0000000..cb44fcd --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_upstream_hash_module.c @@ -0,0 +1,675 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef struct { + uint32_t hash; + ngx_str_t *server; +} ngx_stream_upstream_chash_point_t; + + +typedef struct { + ngx_uint_t number; + ngx_stream_upstream_chash_point_t point[1]; +} ngx_stream_upstream_chash_points_t; + + +typedef struct { + ngx_stream_complex_value_t key; + ngx_stream_upstream_chash_points_t *points; +} ngx_stream_upstream_hash_srv_conf_t; + + +typedef struct { + /* the round robin data must be first */ + ngx_stream_upstream_rr_peer_data_t rrp; + ngx_stream_upstream_hash_srv_conf_t *conf; + ngx_str_t key; + ngx_uint_t tries; + ngx_uint_t rehash; + uint32_t hash; + ngx_event_get_peer_pt get_rr_peer; +} ngx_stream_upstream_hash_peer_data_t; + + +static ngx_int_t ngx_stream_upstream_init_hash(ngx_conf_t *cf, + ngx_stream_upstream_srv_conf_t *us); +static ngx_int_t ngx_stream_upstream_init_hash_peer(ngx_stream_session_t *s, + ngx_stream_upstream_srv_conf_t *us); +static ngx_int_t ngx_stream_upstream_get_hash_peer(ngx_peer_connection_t *pc, + void *data); + +static ngx_int_t ngx_stream_upstream_init_chash(ngx_conf_t *cf, + ngx_stream_upstream_srv_conf_t *us); +static int ngx_libc_cdecl + ngx_stream_upstream_chash_cmp_points(const void *one, const void *two); +static ngx_uint_t ngx_stream_upstream_find_chash_point( + ngx_stream_upstream_chash_points_t *points, uint32_t hash); +static ngx_int_t ngx_stream_upstream_init_chash_peer(ngx_stream_session_t *s, + ngx_stream_upstream_srv_conf_t *us); +static ngx_int_t ngx_stream_upstream_get_chash_peer(ngx_peer_connection_t *pc, + void *data); + +static void *ngx_stream_upstream_hash_create_conf(ngx_conf_t *cf); +static char *ngx_stream_upstream_hash(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +static ngx_command_t ngx_stream_upstream_hash_commands[] = { + + { ngx_string("hash"), + NGX_STREAM_UPS_CONF|NGX_CONF_TAKE12, + ngx_stream_upstream_hash, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_upstream_hash_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_stream_upstream_hash_create_conf, /* create server configuration */ + NULL /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_upstream_hash_module = { + NGX_MODULE_V1, + &ngx_stream_upstream_hash_module_ctx, /* module context */ + ngx_stream_upstream_hash_commands, /* module directives */ + NGX_STREAM_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_stream_upstream_init_hash(ngx_conf_t *cf, + ngx_stream_upstream_srv_conf_t *us) +{ + if (ngx_stream_upstream_init_round_robin(cf, us) != NGX_OK) { + return NGX_ERROR; + } + + us->peer.init = ngx_stream_upstream_init_hash_peer; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_upstream_init_hash_peer(ngx_stream_session_t *s, + ngx_stream_upstream_srv_conf_t *us) +{ + ngx_stream_upstream_hash_srv_conf_t *hcf; + ngx_stream_upstream_hash_peer_data_t *hp; + + hp = ngx_palloc(s->connection->pool, + sizeof(ngx_stream_upstream_hash_peer_data_t)); + if (hp == NULL) { + return NGX_ERROR; + } + + s->upstream->peer.data = &hp->rrp; + + if (ngx_stream_upstream_init_round_robin_peer(s, us) != NGX_OK) { + return NGX_ERROR; + } + + s->upstream->peer.get = ngx_stream_upstream_get_hash_peer; + + hcf = ngx_stream_conf_upstream_srv_conf(us, + ngx_stream_upstream_hash_module); + + if (ngx_stream_complex_value(s, &hcf->key, &hp->key) != NGX_OK) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "upstream hash key:\"%V\"", &hp->key); + + hp->conf = hcf; + hp->tries = 0; + hp->rehash = 0; + hp->hash = 0; + hp->get_rr_peer = ngx_stream_upstream_get_round_robin_peer; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_upstream_get_hash_peer(ngx_peer_connection_t *pc, void *data) +{ + ngx_stream_upstream_hash_peer_data_t *hp = data; + + time_t now; + u_char buf[NGX_INT_T_LEN]; + size_t size; + uint32_t hash; + ngx_int_t w; + uintptr_t m; + ngx_uint_t n, p; + ngx_stream_upstream_rr_peer_t *peer; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0, + "get hash peer, try: %ui", pc->tries); + + ngx_stream_upstream_rr_peers_wlock(hp->rrp.peers); + + if (hp->tries > 20 || hp->rrp.peers->single) { + ngx_stream_upstream_rr_peers_unlock(hp->rrp.peers); + return hp->get_rr_peer(pc, &hp->rrp); + } + + now = ngx_time(); + + pc->connection = NULL; + + for ( ;; ) { + + /* + * Hash expression is compatible with Cache::Memcached: + * ((crc32([REHASH] KEY) >> 16) & 0x7fff) + PREV_HASH + * with REHASH omitted at the first iteration. + */ + + ngx_crc32_init(hash); + + if (hp->rehash > 0) { + size = ngx_sprintf(buf, "%ui", hp->rehash) - buf; + ngx_crc32_update(&hash, buf, size); + } + + ngx_crc32_update(&hash, hp->key.data, hp->key.len); + ngx_crc32_final(hash); + + hash = (hash >> 16) & 0x7fff; + + hp->hash += hash; + hp->rehash++; + + w = hp->hash % hp->rrp.peers->total_weight; + peer = hp->rrp.peers->peer; + p = 0; + + while (w >= peer->weight) { + w -= peer->weight; + peer = peer->next; + p++; + } + + n = p / (8 * sizeof(uintptr_t)); + m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t)); + + if (hp->rrp.tried[n] & m) { + goto next; + } + + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, pc->log, 0, + "get hash peer, value:%uD, peer:%ui", hp->hash, p); + + if (peer->down) { + goto next; + } + + if (peer->max_fails + && peer->fails >= peer->max_fails + && now - peer->checked <= peer->fail_timeout) + { + goto next; + } + + if (peer->max_conns && peer->conns >= peer->max_conns) { + goto next; + } + + break; + + next: + + if (++hp->tries > 20) { + ngx_stream_upstream_rr_peers_unlock(hp->rrp.peers); + return hp->get_rr_peer(pc, &hp->rrp); + } + } + + hp->rrp.current = peer; + + pc->sockaddr = peer->sockaddr; + pc->socklen = peer->socklen; + pc->name = &peer->name; + + peer->conns++; + + if (now - peer->checked > peer->fail_timeout) { + peer->checked = now; + } + + ngx_stream_upstream_rr_peers_unlock(hp->rrp.peers); + + hp->rrp.tried[n] |= m; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_upstream_init_chash(ngx_conf_t *cf, + ngx_stream_upstream_srv_conf_t *us) +{ + u_char *host, *port, c; + size_t host_len, port_len, size; + uint32_t hash, base_hash; + ngx_str_t *server; + ngx_uint_t npoints, i, j; + ngx_stream_upstream_rr_peer_t *peer; + ngx_stream_upstream_rr_peers_t *peers; + ngx_stream_upstream_chash_points_t *points; + ngx_stream_upstream_hash_srv_conf_t *hcf; + union { + uint32_t value; + u_char byte[4]; + } prev_hash; + + if (ngx_stream_upstream_init_round_robin(cf, us) != NGX_OK) { + return NGX_ERROR; + } + + us->peer.init = ngx_stream_upstream_init_chash_peer; + + peers = us->peer.data; + npoints = peers->total_weight * 160; + + size = sizeof(ngx_stream_upstream_chash_points_t) + + sizeof(ngx_stream_upstream_chash_point_t) * (npoints - 1); + + points = ngx_palloc(cf->pool, size); + if (points == NULL) { + return NGX_ERROR; + } + + points->number = 0; + + for (peer = peers->peer; peer; peer = peer->next) { + server = &peer->server; + + /* + * Hash expression is compatible with Cache::Memcached::Fast: + * crc32(HOST \0 PORT PREV_HASH). + */ + + if (server->len >= 5 + && ngx_strncasecmp(server->data, (u_char *) "unix:", 5) == 0) + { + host = server->data + 5; + host_len = server->len - 5; + port = NULL; + port_len = 0; + goto done; + } + + for (j = 0; j < server->len; j++) { + c = server->data[server->len - j - 1]; + + if (c == ':') { + host = server->data; + host_len = server->len - j - 1; + port = server->data + server->len - j; + port_len = j; + goto done; + } + + if (c < '0' || c > '9') { + break; + } + } + + host = server->data; + host_len = server->len; + port = NULL; + port_len = 0; + + done: + + ngx_crc32_init(base_hash); + ngx_crc32_update(&base_hash, host, host_len); + ngx_crc32_update(&base_hash, (u_char *) "", 1); + ngx_crc32_update(&base_hash, port, port_len); + + prev_hash.value = 0; + npoints = peer->weight * 160; + + for (j = 0; j < npoints; j++) { + hash = base_hash; + + ngx_crc32_update(&hash, prev_hash.byte, 4); + ngx_crc32_final(hash); + + points->point[points->number].hash = hash; + points->point[points->number].server = server; + points->number++; + +#if (NGX_HAVE_LITTLE_ENDIAN) + prev_hash.value = hash; +#else + prev_hash.byte[0] = (u_char) (hash & 0xff); + prev_hash.byte[1] = (u_char) ((hash >> 8) & 0xff); + prev_hash.byte[2] = (u_char) ((hash >> 16) & 0xff); + prev_hash.byte[3] = (u_char) ((hash >> 24) & 0xff); +#endif + } + } + + ngx_qsort(points->point, + points->number, + sizeof(ngx_stream_upstream_chash_point_t), + ngx_stream_upstream_chash_cmp_points); + + for (i = 0, j = 1; j < points->number; j++) { + if (points->point[i].hash != points->point[j].hash) { + points->point[++i] = points->point[j]; + } + } + + points->number = i + 1; + + hcf = ngx_stream_conf_upstream_srv_conf(us, + ngx_stream_upstream_hash_module); + hcf->points = points; + + return NGX_OK; +} + + +static int ngx_libc_cdecl +ngx_stream_upstream_chash_cmp_points(const void *one, const void *two) +{ + ngx_stream_upstream_chash_point_t *first = + (ngx_stream_upstream_chash_point_t *) one; + ngx_stream_upstream_chash_point_t *second = + (ngx_stream_upstream_chash_point_t *) two; + + if (first->hash < second->hash) { + return -1; + + } else if (first->hash > second->hash) { + return 1; + + } else { + return 0; + } +} + + +static ngx_uint_t +ngx_stream_upstream_find_chash_point(ngx_stream_upstream_chash_points_t *points, + uint32_t hash) +{ + ngx_uint_t i, j, k; + ngx_stream_upstream_chash_point_t *point; + + /* find first point >= hash */ + + point = &points->point[0]; + + i = 0; + j = points->number; + + while (i < j) { + k = (i + j) / 2; + + if (hash > point[k].hash) { + i = k + 1; + + } else if (hash < point[k].hash) { + j = k; + + } else { + return k; + } + } + + return i; +} + + +static ngx_int_t +ngx_stream_upstream_init_chash_peer(ngx_stream_session_t *s, + ngx_stream_upstream_srv_conf_t *us) +{ + uint32_t hash; + ngx_stream_upstream_hash_srv_conf_t *hcf; + ngx_stream_upstream_hash_peer_data_t *hp; + + if (ngx_stream_upstream_init_hash_peer(s, us) != NGX_OK) { + return NGX_ERROR; + } + + s->upstream->peer.get = ngx_stream_upstream_get_chash_peer; + + hp = s->upstream->peer.data; + hcf = ngx_stream_conf_upstream_srv_conf(us, + ngx_stream_upstream_hash_module); + + hash = ngx_crc32_long(hp->key.data, hp->key.len); + + ngx_stream_upstream_rr_peers_rlock(hp->rrp.peers); + + hp->hash = ngx_stream_upstream_find_chash_point(hcf->points, hash); + + ngx_stream_upstream_rr_peers_unlock(hp->rrp.peers); + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_upstream_get_chash_peer(ngx_peer_connection_t *pc, void *data) +{ + ngx_stream_upstream_hash_peer_data_t *hp = data; + + time_t now; + intptr_t m; + ngx_str_t *server; + ngx_int_t total; + ngx_uint_t i, n, best_i; + ngx_stream_upstream_rr_peer_t *peer, *best; + ngx_stream_upstream_chash_point_t *point; + ngx_stream_upstream_chash_points_t *points; + ngx_stream_upstream_hash_srv_conf_t *hcf; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0, + "get consistent hash peer, try: %ui", pc->tries); + + ngx_stream_upstream_rr_peers_wlock(hp->rrp.peers); + + pc->connection = NULL; + + now = ngx_time(); + hcf = hp->conf; + + points = hcf->points; + point = &points->point[0]; + + for ( ;; ) { + server = point[hp->hash % points->number].server; + + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, pc->log, 0, + "consistent hash peer:%uD, server:\"%V\"", + hp->hash, server); + + best = NULL; + best_i = 0; + total = 0; + + for (peer = hp->rrp.peers->peer, i = 0; + peer; + peer = peer->next, i++) + { + n = i / (8 * sizeof(uintptr_t)); + m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t)); + + if (hp->rrp.tried[n] & m) { + continue; + } + + if (peer->down) { + continue; + } + + if (peer->server.len != server->len + || ngx_strncmp(peer->server.data, server->data, server->len) + != 0) + { + continue; + } + + if (peer->max_fails + && peer->fails >= peer->max_fails + && now - peer->checked <= peer->fail_timeout) + { + continue; + } + + if (peer->max_conns && peer->conns >= peer->max_conns) { + continue; + } + + peer->current_weight += peer->effective_weight; + total += peer->effective_weight; + + if (peer->effective_weight < peer->weight) { + peer->effective_weight++; + } + + if (best == NULL || peer->current_weight > best->current_weight) { + best = peer; + best_i = i; + } + } + + if (best) { + best->current_weight -= total; + break; + } + + hp->hash++; + hp->tries++; + + if (hp->tries >= points->number) { + pc->name = hp->rrp.peers->name; + ngx_stream_upstream_rr_peers_unlock(hp->rrp.peers); + return NGX_BUSY; + } + } + + hp->rrp.current = best; + + pc->sockaddr = best->sockaddr; + pc->socklen = best->socklen; + pc->name = &best->name; + + best->conns++; + + if (now - best->checked > best->fail_timeout) { + best->checked = now; + } + + ngx_stream_upstream_rr_peers_unlock(hp->rrp.peers); + + n = best_i / (8 * sizeof(uintptr_t)); + m = (uintptr_t) 1 << best_i % (8 * sizeof(uintptr_t)); + + hp->rrp.tried[n] |= m; + + return NGX_OK; +} + + +static void * +ngx_stream_upstream_hash_create_conf(ngx_conf_t *cf) +{ + ngx_stream_upstream_hash_srv_conf_t *conf; + + conf = ngx_palloc(cf->pool, sizeof(ngx_stream_upstream_hash_srv_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->points = NULL; + + return conf; +} + + +static char * +ngx_stream_upstream_hash(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_upstream_hash_srv_conf_t *hcf = conf; + + ngx_str_t *value; + ngx_stream_upstream_srv_conf_t *uscf; + ngx_stream_compile_complex_value_t ccv; + + value = cf->args->elts; + + ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[1]; + ccv.complex_value = &hcf->key; + + if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + uscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_upstream_module); + + if (uscf->peer.init_upstream) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "load balancing method redefined"); + } + + uscf->flags = NGX_STREAM_UPSTREAM_CREATE + |NGX_STREAM_UPSTREAM_WEIGHT + |NGX_STREAM_UPSTREAM_MAX_CONNS + |NGX_STREAM_UPSTREAM_MAX_FAILS + |NGX_STREAM_UPSTREAM_FAIL_TIMEOUT + |NGX_STREAM_UPSTREAM_DOWN; + + if (cf->args->nelts == 2) { + uscf->peer.init_upstream = ngx_stream_upstream_init_hash; + + } else if (ngx_strcmp(value[2].data, "consistent") == 0) { + uscf->peer.init_upstream = ngx_stream_upstream_init_chash; + + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[2]); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} diff --git a/app/nginx/src/stream/ngx_stream_upstream_least_conn_module.c b/app/nginx/src/stream/ngx_stream_upstream_least_conn_module.c new file mode 100644 index 0000000..739b20a --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_upstream_least_conn_module.c @@ -0,0 +1,310 @@ + +/* + * Copyright (C) Maxim Dounin + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +static ngx_int_t ngx_stream_upstream_init_least_conn_peer( + ngx_stream_session_t *s, ngx_stream_upstream_srv_conf_t *us); +static ngx_int_t ngx_stream_upstream_get_least_conn_peer( + ngx_peer_connection_t *pc, void *data); +static char *ngx_stream_upstream_least_conn(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +static ngx_command_t ngx_stream_upstream_least_conn_commands[] = { + + { ngx_string("least_conn"), + NGX_STREAM_UPS_CONF|NGX_CONF_NOARGS, + ngx_stream_upstream_least_conn, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_upstream_least_conn_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_upstream_least_conn_module = { + NGX_MODULE_V1, + &ngx_stream_upstream_least_conn_module_ctx, /* module context */ + ngx_stream_upstream_least_conn_commands, /* module directives */ + NGX_STREAM_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_stream_upstream_init_least_conn(ngx_conf_t *cf, + ngx_stream_upstream_srv_conf_t *us) +{ + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, cf->log, 0, + "init least conn"); + + if (ngx_stream_upstream_init_round_robin(cf, us) != NGX_OK) { + return NGX_ERROR; + } + + us->peer.init = ngx_stream_upstream_init_least_conn_peer; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_upstream_init_least_conn_peer(ngx_stream_session_t *s, + ngx_stream_upstream_srv_conf_t *us) +{ + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "init least conn peer"); + + if (ngx_stream_upstream_init_round_robin_peer(s, us) != NGX_OK) { + return NGX_ERROR; + } + + s->upstream->peer.get = ngx_stream_upstream_get_least_conn_peer; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_upstream_get_least_conn_peer(ngx_peer_connection_t *pc, void *data) +{ + ngx_stream_upstream_rr_peer_data_t *rrp = data; + + time_t now; + uintptr_t m; + ngx_int_t rc, total; + ngx_uint_t i, n, p, many; + ngx_stream_upstream_rr_peer_t *peer, *best; + ngx_stream_upstream_rr_peers_t *peers; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0, + "get least conn peer, try: %ui", pc->tries); + + if (rrp->peers->single) { + return ngx_stream_upstream_get_round_robin_peer(pc, rrp); + } + + pc->connection = NULL; + + now = ngx_time(); + + peers = rrp->peers; + + ngx_stream_upstream_rr_peers_wlock(peers); + + best = NULL; + total = 0; + +#if (NGX_SUPPRESS_WARN) + many = 0; + p = 0; +#endif + + for (peer = peers->peer, i = 0; + peer; + peer = peer->next, i++) + { + n = i / (8 * sizeof(uintptr_t)); + m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t)); + + if (rrp->tried[n] & m) { + continue; + } + + if (peer->down) { + continue; + } + + if (peer->max_fails + && peer->fails >= peer->max_fails + && now - peer->checked <= peer->fail_timeout) + { + continue; + } + + if (peer->max_conns && peer->conns >= peer->max_conns) { + continue; + } + + /* + * select peer with least number of connections; if there are + * multiple peers with the same number of connections, select + * based on round-robin + */ + + if (best == NULL + || peer->conns * best->weight < best->conns * peer->weight) + { + best = peer; + many = 0; + p = i; + + } else if (peer->conns * best->weight == best->conns * peer->weight) { + many = 1; + } + } + + if (best == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, pc->log, 0, + "get least conn peer, no peer found"); + + goto failed; + } + + if (many) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, pc->log, 0, + "get least conn peer, many"); + + for (peer = best, i = p; + peer; + peer = peer->next, i++) + { + n = i / (8 * sizeof(uintptr_t)); + m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t)); + + if (rrp->tried[n] & m) { + continue; + } + + if (peer->down) { + continue; + } + + if (peer->conns * best->weight != best->conns * peer->weight) { + continue; + } + + if (peer->max_fails + && peer->fails >= peer->max_fails + && now - peer->checked <= peer->fail_timeout) + { + continue; + } + + if (peer->max_conns && peer->conns >= peer->max_conns) { + continue; + } + + peer->current_weight += peer->effective_weight; + total += peer->effective_weight; + + if (peer->effective_weight < peer->weight) { + peer->effective_weight++; + } + + if (peer->current_weight > best->current_weight) { + best = peer; + p = i; + } + } + } + + best->current_weight -= total; + + if (now - best->checked > best->fail_timeout) { + best->checked = now; + } + + pc->sockaddr = best->sockaddr; + pc->socklen = best->socklen; + pc->name = &best->name; + + best->conns++; + + rrp->current = best; + + n = p / (8 * sizeof(uintptr_t)); + m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t)); + + rrp->tried[n] |= m; + + ngx_stream_upstream_rr_peers_unlock(peers); + + return NGX_OK; + +failed: + + if (peers->next) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, pc->log, 0, + "get least conn peer, backup servers"); + + rrp->peers = peers->next; + + n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1)) + / (8 * sizeof(uintptr_t)); + + for (i = 0; i < n; i++) { + rrp->tried[i] = 0; + } + + ngx_stream_upstream_rr_peers_unlock(peers); + + rc = ngx_stream_upstream_get_least_conn_peer(pc, rrp); + + if (rc != NGX_BUSY) { + return rc; + } + + ngx_stream_upstream_rr_peers_wlock(peers); + } + + ngx_stream_upstream_rr_peers_unlock(peers); + + pc->name = peers->name; + + return NGX_BUSY; +} + + +static char * +ngx_stream_upstream_least_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_upstream_srv_conf_t *uscf; + + uscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_upstream_module); + + if (uscf->peer.init_upstream) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "load balancing method redefined"); + } + + uscf->peer.init_upstream = ngx_stream_upstream_init_least_conn; + + uscf->flags = NGX_STREAM_UPSTREAM_CREATE + |NGX_STREAM_UPSTREAM_WEIGHT + |NGX_STREAM_UPSTREAM_MAX_CONNS + |NGX_STREAM_UPSTREAM_MAX_FAILS + |NGX_STREAM_UPSTREAM_FAIL_TIMEOUT + |NGX_STREAM_UPSTREAM_DOWN + |NGX_STREAM_UPSTREAM_BACKUP; + + return NGX_CONF_OK; +} diff --git a/app/nginx/src/stream/ngx_stream_upstream_round_robin.c b/app/nginx/src/stream/ngx_stream_upstream_round_robin.c new file mode 100644 index 0000000..526de3a --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_upstream_round_robin.c @@ -0,0 +1,874 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#define ngx_stream_upstream_tries(p) ((p)->number \ + + ((p)->next ? (p)->next->number : 0)) + + +static ngx_stream_upstream_rr_peer_t *ngx_stream_upstream_get_peer( + ngx_stream_upstream_rr_peer_data_t *rrp); +static void ngx_stream_upstream_notify_round_robin_peer( + ngx_peer_connection_t *pc, void *data, ngx_uint_t state); + +#if (NGX_STREAM_SSL) + +static ngx_int_t ngx_stream_upstream_set_round_robin_peer_session( + ngx_peer_connection_t *pc, void *data); +static void ngx_stream_upstream_save_round_robin_peer_session( + ngx_peer_connection_t *pc, void *data); +static ngx_int_t ngx_stream_upstream_empty_set_session( + ngx_peer_connection_t *pc, void *data); +static void ngx_stream_upstream_empty_save_session(ngx_peer_connection_t *pc, + void *data); + +#endif + + +ngx_int_t +ngx_stream_upstream_init_round_robin(ngx_conf_t *cf, + ngx_stream_upstream_srv_conf_t *us) +{ + ngx_url_t u; + ngx_uint_t i, j, n, w; + ngx_stream_upstream_server_t *server; + ngx_stream_upstream_rr_peer_t *peer, **peerp; + ngx_stream_upstream_rr_peers_t *peers, *backup; + + us->peer.init = ngx_stream_upstream_init_round_robin_peer; + + if (us->servers) { + server = us->servers->elts; + + n = 0; + w = 0; + + for (i = 0; i < us->servers->nelts; i++) { + if (server[i].backup) { + continue; + } + + n += server[i].naddrs; + w += server[i].naddrs * server[i].weight; + } + + if (n == 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no servers in upstream \"%V\" in %s:%ui", + &us->host, us->file_name, us->line); + return NGX_ERROR; + } + + peers = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upstream_rr_peers_t)); + if (peers == NULL) { + return NGX_ERROR; + } + + peer = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upstream_rr_peer_t) * n); + if (peer == NULL) { + return NGX_ERROR; + } + + peers->single = (n == 1); + peers->number = n; + peers->weighted = (w != n); + peers->total_weight = w; + peers->name = &us->host; + + n = 0; + peerp = &peers->peer; + + for (i = 0; i < us->servers->nelts; i++) { + if (server[i].backup) { + continue; + } + + for (j = 0; j < server[i].naddrs; j++) { + peer[n].sockaddr = server[i].addrs[j].sockaddr; + peer[n].socklen = server[i].addrs[j].socklen; + peer[n].name = server[i].addrs[j].name; + peer[n].weight = server[i].weight; + peer[n].effective_weight = server[i].weight; + peer[n].current_weight = 0; + peer[n].max_conns = server[i].max_conns; + peer[n].max_fails = server[i].max_fails; + peer[n].fail_timeout = server[i].fail_timeout; + peer[n].down = server[i].down; + peer[n].server = server[i].name; + + *peerp = &peer[n]; + peerp = &peer[n].next; + n++; + } + } + + us->peer.data = peers; + + /* backup servers */ + + n = 0; + w = 0; + + for (i = 0; i < us->servers->nelts; i++) { + if (!server[i].backup) { + continue; + } + + n += server[i].naddrs; + w += server[i].naddrs * server[i].weight; + } + + if (n == 0) { + return NGX_OK; + } + + backup = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upstream_rr_peers_t)); + if (backup == NULL) { + return NGX_ERROR; + } + + peer = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upstream_rr_peer_t) * n); + if (peer == NULL) { + return NGX_ERROR; + } + + peers->single = 0; + backup->single = 0; + backup->number = n; + backup->weighted = (w != n); + backup->total_weight = w; + backup->name = &us->host; + + n = 0; + peerp = &backup->peer; + + for (i = 0; i < us->servers->nelts; i++) { + if (!server[i].backup) { + continue; + } + + for (j = 0; j < server[i].naddrs; j++) { + peer[n].sockaddr = server[i].addrs[j].sockaddr; + peer[n].socklen = server[i].addrs[j].socklen; + peer[n].name = server[i].addrs[j].name; + peer[n].weight = server[i].weight; + peer[n].effective_weight = server[i].weight; + peer[n].current_weight = 0; + peer[n].max_conns = server[i].max_conns; + peer[n].max_fails = server[i].max_fails; + peer[n].fail_timeout = server[i].fail_timeout; + peer[n].down = server[i].down; + peer[n].server = server[i].name; + + *peerp = &peer[n]; + peerp = &peer[n].next; + n++; + } + } + + peers->next = backup; + + return NGX_OK; + } + + + /* an upstream implicitly defined by proxy_pass, etc. */ + + if (us->port == 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no port in upstream \"%V\" in %s:%ui", + &us->host, us->file_name, us->line); + return NGX_ERROR; + } + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.host = us->host; + u.port = us->port; + + if (ngx_inet_resolve_host(cf->pool, &u) != NGX_OK) { + if (u.err) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "%s in upstream \"%V\" in %s:%ui", + u.err, &us->host, us->file_name, us->line); + } + + return NGX_ERROR; + } + + n = u.naddrs; + + peers = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upstream_rr_peers_t)); + if (peers == NULL) { + return NGX_ERROR; + } + + peer = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upstream_rr_peer_t) * n); + if (peer == NULL) { + return NGX_ERROR; + } + + peers->single = (n == 1); + peers->number = n; + peers->weighted = 0; + peers->total_weight = n; + peers->name = &us->host; + + peerp = &peers->peer; + + for (i = 0; i < u.naddrs; i++) { + peer[i].sockaddr = u.addrs[i].sockaddr; + peer[i].socklen = u.addrs[i].socklen; + peer[i].name = u.addrs[i].name; + peer[i].weight = 1; + peer[i].effective_weight = 1; + peer[i].current_weight = 0; + peer[i].max_conns = 0; + peer[i].max_fails = 1; + peer[i].fail_timeout = 10; + *peerp = &peer[i]; + peerp = &peer[i].next; + } + + us->peer.data = peers; + + /* implicitly defined upstream has no backup servers */ + + return NGX_OK; +} + + +ngx_int_t +ngx_stream_upstream_init_round_robin_peer(ngx_stream_session_t *s, + ngx_stream_upstream_srv_conf_t *us) +{ + ngx_uint_t n; + ngx_stream_upstream_rr_peer_data_t *rrp; + + rrp = s->upstream->peer.data; + + if (rrp == NULL) { + rrp = ngx_palloc(s->connection->pool, + sizeof(ngx_stream_upstream_rr_peer_data_t)); + if (rrp == NULL) { + return NGX_ERROR; + } + + s->upstream->peer.data = rrp; + } + + rrp->peers = us->peer.data; + rrp->current = NULL; + rrp->config = 0; + + n = rrp->peers->number; + + if (rrp->peers->next && rrp->peers->next->number > n) { + n = rrp->peers->next->number; + } + + if (n <= 8 * sizeof(uintptr_t)) { + rrp->tried = &rrp->data; + rrp->data = 0; + + } else { + n = (n + (8 * sizeof(uintptr_t) - 1)) / (8 * sizeof(uintptr_t)); + + rrp->tried = ngx_pcalloc(s->connection->pool, n * sizeof(uintptr_t)); + if (rrp->tried == NULL) { + return NGX_ERROR; + } + } + + s->upstream->peer.get = ngx_stream_upstream_get_round_robin_peer; + s->upstream->peer.free = ngx_stream_upstream_free_round_robin_peer; + s->upstream->peer.notify = ngx_stream_upstream_notify_round_robin_peer; + s->upstream->peer.tries = ngx_stream_upstream_tries(rrp->peers); +#if (NGX_STREAM_SSL) + s->upstream->peer.set_session = + ngx_stream_upstream_set_round_robin_peer_session; + s->upstream->peer.save_session = + ngx_stream_upstream_save_round_robin_peer_session; +#endif + + return NGX_OK; +} + + +ngx_int_t +ngx_stream_upstream_create_round_robin_peer(ngx_stream_session_t *s, + ngx_stream_upstream_resolved_t *ur) +{ + u_char *p; + size_t len; + socklen_t socklen; + ngx_uint_t i, n; + struct sockaddr *sockaddr; + ngx_stream_upstream_rr_peer_t *peer, **peerp; + ngx_stream_upstream_rr_peers_t *peers; + ngx_stream_upstream_rr_peer_data_t *rrp; + + rrp = s->upstream->peer.data; + + if (rrp == NULL) { + rrp = ngx_palloc(s->connection->pool, + sizeof(ngx_stream_upstream_rr_peer_data_t)); + if (rrp == NULL) { + return NGX_ERROR; + } + + s->upstream->peer.data = rrp; + } + + peers = ngx_pcalloc(s->connection->pool, + sizeof(ngx_stream_upstream_rr_peers_t)); + if (peers == NULL) { + return NGX_ERROR; + } + + peer = ngx_pcalloc(s->connection->pool, + sizeof(ngx_stream_upstream_rr_peer_t) * ur->naddrs); + if (peer == NULL) { + return NGX_ERROR; + } + + peers->single = (ur->naddrs == 1); + peers->number = ur->naddrs; + peers->name = &ur->host; + + if (ur->sockaddr) { + peer[0].sockaddr = ur->sockaddr; + peer[0].socklen = ur->socklen; + peer[0].name = ur->name; + peer[0].weight = 1; + peer[0].effective_weight = 1; + peer[0].current_weight = 0; + peer[0].max_conns = 0; + peer[0].max_fails = 1; + peer[0].fail_timeout = 10; + peers->peer = peer; + + } else { + peerp = &peers->peer; + + for (i = 0; i < ur->naddrs; i++) { + + socklen = ur->addrs[i].socklen; + + sockaddr = ngx_palloc(s->connection->pool, socklen); + if (sockaddr == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(sockaddr, ur->addrs[i].sockaddr, socklen); + ngx_inet_set_port(sockaddr, ur->port); + + p = ngx_pnalloc(s->connection->pool, NGX_SOCKADDR_STRLEN); + if (p == NULL) { + return NGX_ERROR; + } + + len = ngx_sock_ntop(sockaddr, socklen, p, NGX_SOCKADDR_STRLEN, 1); + + peer[i].sockaddr = sockaddr; + peer[i].socklen = socklen; + peer[i].name.len = len; + peer[i].name.data = p; + peer[i].weight = 1; + peer[i].effective_weight = 1; + peer[i].current_weight = 0; + peer[i].max_conns = 0; + peer[i].max_fails = 1; + peer[i].fail_timeout = 10; + *peerp = &peer[i]; + peerp = &peer[i].next; + } + } + + rrp->peers = peers; + rrp->current = NULL; + rrp->config = 0; + + if (rrp->peers->number <= 8 * sizeof(uintptr_t)) { + rrp->tried = &rrp->data; + rrp->data = 0; + + } else { + n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1)) + / (8 * sizeof(uintptr_t)); + + rrp->tried = ngx_pcalloc(s->connection->pool, n * sizeof(uintptr_t)); + if (rrp->tried == NULL) { + return NGX_ERROR; + } + } + + s->upstream->peer.get = ngx_stream_upstream_get_round_robin_peer; + s->upstream->peer.free = ngx_stream_upstream_free_round_robin_peer; + s->upstream->peer.tries = ngx_stream_upstream_tries(rrp->peers); +#if (NGX_STREAM_SSL) + s->upstream->peer.set_session = ngx_stream_upstream_empty_set_session; + s->upstream->peer.save_session = ngx_stream_upstream_empty_save_session; +#endif + + return NGX_OK; +} + + +ngx_int_t +ngx_stream_upstream_get_round_robin_peer(ngx_peer_connection_t *pc, void *data) +{ + ngx_stream_upstream_rr_peer_data_t *rrp = data; + + ngx_int_t rc; + ngx_uint_t i, n; + ngx_stream_upstream_rr_peer_t *peer; + ngx_stream_upstream_rr_peers_t *peers; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0, + "get rr peer, try: %ui", pc->tries); + + pc->connection = NULL; + + peers = rrp->peers; + ngx_stream_upstream_rr_peers_wlock(peers); + + if (peers->single) { + peer = peers->peer; + + if (peer->down) { + goto failed; + } + + if (peer->max_conns && peer->conns >= peer->max_conns) { + goto failed; + } + + rrp->current = peer; + + } else { + + /* there are several peers */ + + peer = ngx_stream_upstream_get_peer(rrp); + + if (peer == NULL) { + goto failed; + } + + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, pc->log, 0, + "get rr peer, current: %p %i", + peer, peer->current_weight); + } + + pc->sockaddr = peer->sockaddr; + pc->socklen = peer->socklen; + pc->name = &peer->name; + + peer->conns++; + + ngx_stream_upstream_rr_peers_unlock(peers); + + return NGX_OK; + +failed: + + if (peers->next) { + + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, pc->log, 0, "backup servers"); + + rrp->peers = peers->next; + + n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1)) + / (8 * sizeof(uintptr_t)); + + for (i = 0; i < n; i++) { + rrp->tried[i] = 0; + } + + ngx_stream_upstream_rr_peers_unlock(peers); + + rc = ngx_stream_upstream_get_round_robin_peer(pc, rrp); + + if (rc != NGX_BUSY) { + return rc; + } + + ngx_stream_upstream_rr_peers_wlock(peers); + } + + ngx_stream_upstream_rr_peers_unlock(peers); + + pc->name = peers->name; + + return NGX_BUSY; +} + + +static ngx_stream_upstream_rr_peer_t * +ngx_stream_upstream_get_peer(ngx_stream_upstream_rr_peer_data_t *rrp) +{ + time_t now; + uintptr_t m; + ngx_int_t total; + ngx_uint_t i, n, p; + ngx_stream_upstream_rr_peer_t *peer, *best; + + now = ngx_time(); + + best = NULL; + total = 0; + +#if (NGX_SUPPRESS_WARN) + p = 0; +#endif + + for (peer = rrp->peers->peer, i = 0; + peer; + peer = peer->next, i++) + { + n = i / (8 * sizeof(uintptr_t)); + m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t)); + + if (rrp->tried[n] & m) { + continue; + } + + if (peer->down) { + continue; + } + + if (peer->max_fails + && peer->fails >= peer->max_fails + && now - peer->checked <= peer->fail_timeout) + { + continue; + } + + if (peer->max_conns && peer->conns >= peer->max_conns) { + continue; + } + + peer->current_weight += peer->effective_weight; + total += peer->effective_weight; + + if (peer->effective_weight < peer->weight) { + peer->effective_weight++; + } + + if (best == NULL || peer->current_weight > best->current_weight) { + best = peer; + p = i; + } + } + + if (best == NULL) { + return NULL; + } + + rrp->current = best; + + n = p / (8 * sizeof(uintptr_t)); + m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t)); + + rrp->tried[n] |= m; + + best->current_weight -= total; + + if (now - best->checked > best->fail_timeout) { + best->checked = now; + } + + return best; +} + + +void +ngx_stream_upstream_free_round_robin_peer(ngx_peer_connection_t *pc, void *data, + ngx_uint_t state) +{ + ngx_stream_upstream_rr_peer_data_t *rrp = data; + + time_t now; + ngx_stream_upstream_rr_peer_t *peer; + + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, pc->log, 0, + "free rr peer %ui %ui", pc->tries, state); + + peer = rrp->current; + + ngx_stream_upstream_rr_peers_rlock(rrp->peers); + ngx_stream_upstream_rr_peer_lock(rrp->peers, peer); + + if (rrp->peers->single) { + peer->conns--; + + ngx_stream_upstream_rr_peer_unlock(rrp->peers, peer); + ngx_stream_upstream_rr_peers_unlock(rrp->peers); + + pc->tries = 0; + return; + } + + if (state & NGX_PEER_FAILED) { + now = ngx_time(); + + peer->fails++; + peer->accessed = now; + peer->checked = now; + + if (peer->max_fails) { + peer->effective_weight -= peer->weight / peer->max_fails; + + if (peer->fails >= peer->max_fails) { + ngx_log_error(NGX_LOG_WARN, pc->log, 0, + "upstream server temporarily disabled"); + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, pc->log, 0, + "free rr peer failed: %p %i", + peer, peer->effective_weight); + + if (peer->effective_weight < 0) { + peer->effective_weight = 0; + } + + } else { + + /* mark peer live if check passed */ + + if (peer->accessed < peer->checked) { + peer->fails = 0; + } + } + + peer->conns--; + + ngx_stream_upstream_rr_peer_unlock(rrp->peers, peer); + ngx_stream_upstream_rr_peers_unlock(rrp->peers); + + if (pc->tries) { + pc->tries--; + } +} + + +static void +ngx_stream_upstream_notify_round_robin_peer(ngx_peer_connection_t *pc, + void *data, ngx_uint_t type) +{ + ngx_stream_upstream_rr_peer_data_t *rrp = data; + + ngx_stream_upstream_rr_peer_t *peer; + + peer = rrp->current; + + if (type == NGX_STREAM_UPSTREAM_NOTIFY_CONNECT + && pc->connection->type == SOCK_STREAM) + { + ngx_stream_upstream_rr_peers_rlock(rrp->peers); + ngx_stream_upstream_rr_peer_lock(rrp->peers, peer); + + if (peer->accessed < peer->checked) { + peer->fails = 0; + } + + ngx_stream_upstream_rr_peer_unlock(rrp->peers, peer); + ngx_stream_upstream_rr_peers_unlock(rrp->peers); + } +} + + +#if (NGX_STREAM_SSL) + +static ngx_int_t +ngx_stream_upstream_set_round_robin_peer_session(ngx_peer_connection_t *pc, + void *data) +{ + ngx_stream_upstream_rr_peer_data_t *rrp = data; + + ngx_int_t rc; + ngx_ssl_session_t *ssl_session; + ngx_stream_upstream_rr_peer_t *peer; +#if (NGX_STREAM_UPSTREAM_ZONE) + int len; +#if OPENSSL_VERSION_NUMBER >= 0x0090707fL + const +#endif + u_char *p; + ngx_stream_upstream_rr_peers_t *peers; + u_char buf[NGX_SSL_MAX_SESSION_SIZE]; +#endif + + peer = rrp->current; + +#if (NGX_STREAM_UPSTREAM_ZONE) + peers = rrp->peers; + + if (peers->shpool) { + ngx_stream_upstream_rr_peers_rlock(peers); + ngx_stream_upstream_rr_peer_lock(peers, peer); + + if (peer->ssl_session == NULL) { + ngx_stream_upstream_rr_peer_unlock(peers, peer); + ngx_stream_upstream_rr_peers_unlock(peers); + return NGX_OK; + } + + len = peer->ssl_session_len; + + ngx_memcpy(buf, peer->ssl_session, len); + + ngx_stream_upstream_rr_peer_unlock(peers, peer); + ngx_stream_upstream_rr_peers_unlock(peers); + + p = buf; + ssl_session = d2i_SSL_SESSION(NULL, &p, len); + + rc = ngx_ssl_set_session(pc->connection, ssl_session); + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0, + "set session: %p", ssl_session); + + ngx_ssl_free_session(ssl_session); + + return rc; + } +#endif + + ssl_session = peer->ssl_session; + + rc = ngx_ssl_set_session(pc->connection, ssl_session); + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0, + "set session: %p", ssl_session); + + return rc; +} + + +static void +ngx_stream_upstream_save_round_robin_peer_session(ngx_peer_connection_t *pc, + void *data) +{ + ngx_stream_upstream_rr_peer_data_t *rrp = data; + + ngx_ssl_session_t *old_ssl_session, *ssl_session; + ngx_stream_upstream_rr_peer_t *peer; +#if (NGX_STREAM_UPSTREAM_ZONE) + int len; + u_char *p; + ngx_stream_upstream_rr_peers_t *peers; + u_char buf[NGX_SSL_MAX_SESSION_SIZE]; +#endif + +#if (NGX_STREAM_UPSTREAM_ZONE) + peers = rrp->peers; + + if (peers->shpool) { + + ssl_session = SSL_get0_session(pc->connection->ssl->connection); + + if (ssl_session == NULL) { + return; + } + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0, + "save session: %p", ssl_session); + + len = i2d_SSL_SESSION(ssl_session, NULL); + + /* do not cache too big session */ + + if (len > NGX_SSL_MAX_SESSION_SIZE) { + return; + } + + p = buf; + (void) i2d_SSL_SESSION(ssl_session, &p); + + peer = rrp->current; + + ngx_stream_upstream_rr_peers_rlock(peers); + ngx_stream_upstream_rr_peer_lock(peers, peer); + + if (len > peer->ssl_session_len) { + ngx_shmtx_lock(&peers->shpool->mutex); + + if (peer->ssl_session) { + ngx_slab_free_locked(peers->shpool, peer->ssl_session); + } + + peer->ssl_session = ngx_slab_alloc_locked(peers->shpool, len); + + ngx_shmtx_unlock(&peers->shpool->mutex); + + if (peer->ssl_session == NULL) { + peer->ssl_session_len = 0; + + ngx_stream_upstream_rr_peer_unlock(peers, peer); + ngx_stream_upstream_rr_peers_unlock(peers); + return; + } + + peer->ssl_session_len = len; + } + + ngx_memcpy(peer->ssl_session, buf, len); + + ngx_stream_upstream_rr_peer_unlock(peers, peer); + ngx_stream_upstream_rr_peers_unlock(peers); + + return; + } +#endif + + ssl_session = ngx_ssl_get_session(pc->connection); + + if (ssl_session == NULL) { + return; + } + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0, + "save session: %p", ssl_session); + + peer = rrp->current; + + old_ssl_session = peer->ssl_session; + peer->ssl_session = ssl_session; + + if (old_ssl_session) { + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0, + "old session: %p", old_ssl_session); + + /* TODO: may block */ + + ngx_ssl_free_session(old_ssl_session); + } +} + + +static ngx_int_t +ngx_stream_upstream_empty_set_session(ngx_peer_connection_t *pc, void *data) +{ + return NGX_OK; +} + + +static void +ngx_stream_upstream_empty_save_session(ngx_peer_connection_t *pc, void *data) +{ + return; +} + +#endif diff --git a/app/nginx/src/stream/ngx_stream_upstream_round_robin.h b/app/nginx/src/stream/ngx_stream_upstream_round_robin.h new file mode 100644 index 0000000..35d9fce --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_upstream_round_robin.h @@ -0,0 +1,146 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_STREAM_UPSTREAM_ROUND_ROBIN_H_INCLUDED_ +#define _NGX_STREAM_UPSTREAM_ROUND_ROBIN_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct ngx_stream_upstream_rr_peer_s ngx_stream_upstream_rr_peer_t; + +struct ngx_stream_upstream_rr_peer_s { + struct sockaddr *sockaddr; + socklen_t socklen; + ngx_str_t name; + ngx_str_t server; + + ngx_int_t current_weight; + ngx_int_t effective_weight; + ngx_int_t weight; + + ngx_uint_t conns; + ngx_uint_t max_conns; + + ngx_uint_t fails; + time_t accessed; + time_t checked; + + ngx_uint_t max_fails; + time_t fail_timeout; + ngx_msec_t slow_start; + ngx_msec_t start_time; + + ngx_uint_t down; + + void *ssl_session; + int ssl_session_len; + +#if (NGX_STREAM_UPSTREAM_ZONE) + ngx_atomic_t lock; +#endif + + ngx_stream_upstream_rr_peer_t *next; + + NGX_COMPAT_BEGIN(25) + NGX_COMPAT_END +}; + + +typedef struct ngx_stream_upstream_rr_peers_s ngx_stream_upstream_rr_peers_t; + +struct ngx_stream_upstream_rr_peers_s { + ngx_uint_t number; + +#if (NGX_STREAM_UPSTREAM_ZONE) + ngx_slab_pool_t *shpool; + ngx_atomic_t rwlock; + ngx_stream_upstream_rr_peers_t *zone_next; +#endif + + ngx_uint_t total_weight; + + unsigned single:1; + unsigned weighted:1; + + ngx_str_t *name; + + ngx_stream_upstream_rr_peers_t *next; + + ngx_stream_upstream_rr_peer_t *peer; +}; + + +#if (NGX_STREAM_UPSTREAM_ZONE) + +#define ngx_stream_upstream_rr_peers_rlock(peers) \ + \ + if (peers->shpool) { \ + ngx_rwlock_rlock(&peers->rwlock); \ + } + +#define ngx_stream_upstream_rr_peers_wlock(peers) \ + \ + if (peers->shpool) { \ + ngx_rwlock_wlock(&peers->rwlock); \ + } + +#define ngx_stream_upstream_rr_peers_unlock(peers) \ + \ + if (peers->shpool) { \ + ngx_rwlock_unlock(&peers->rwlock); \ + } + + +#define ngx_stream_upstream_rr_peer_lock(peers, peer) \ + \ + if (peers->shpool) { \ + ngx_rwlock_wlock(&peer->lock); \ + } + +#define ngx_stream_upstream_rr_peer_unlock(peers, peer) \ + \ + if (peers->shpool) { \ + ngx_rwlock_unlock(&peer->lock); \ + } + +#else + +#define ngx_stream_upstream_rr_peers_rlock(peers) +#define ngx_stream_upstream_rr_peers_wlock(peers) +#define ngx_stream_upstream_rr_peers_unlock(peers) +#define ngx_stream_upstream_rr_peer_lock(peers, peer) +#define ngx_stream_upstream_rr_peer_unlock(peers, peer) + +#endif + + +typedef struct { + ngx_uint_t config; + ngx_stream_upstream_rr_peers_t *peers; + ngx_stream_upstream_rr_peer_t *current; + uintptr_t *tried; + uintptr_t data; +} ngx_stream_upstream_rr_peer_data_t; + + +ngx_int_t ngx_stream_upstream_init_round_robin(ngx_conf_t *cf, + ngx_stream_upstream_srv_conf_t *us); +ngx_int_t ngx_stream_upstream_init_round_robin_peer(ngx_stream_session_t *s, + ngx_stream_upstream_srv_conf_t *us); +ngx_int_t ngx_stream_upstream_create_round_robin_peer(ngx_stream_session_t *s, + ngx_stream_upstream_resolved_t *ur); +ngx_int_t ngx_stream_upstream_get_round_robin_peer(ngx_peer_connection_t *pc, + void *data); +void ngx_stream_upstream_free_round_robin_peer(ngx_peer_connection_t *pc, + void *data, ngx_uint_t state); + + +#endif /* _NGX_STREAM_UPSTREAM_ROUND_ROBIN_H_INCLUDED_ */ diff --git a/app/nginx/src/stream/ngx_stream_upstream_zone_module.c b/app/nginx/src/stream/ngx_stream_upstream_zone_module.c new file mode 100644 index 0000000..07ab88d --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_upstream_zone_module.c @@ -0,0 +1,243 @@ + +/* + * Copyright (C) Ruslan Ermilov + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +static char *ngx_stream_upstream_zone(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static ngx_int_t ngx_stream_upstream_init_zone(ngx_shm_zone_t *shm_zone, + void *data); +static ngx_stream_upstream_rr_peers_t *ngx_stream_upstream_zone_copy_peers( + ngx_slab_pool_t *shpool, ngx_stream_upstream_srv_conf_t *uscf); + + +static ngx_command_t ngx_stream_upstream_zone_commands[] = { + + { ngx_string("zone"), + NGX_STREAM_UPS_CONF|NGX_CONF_TAKE12, + ngx_stream_upstream_zone, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_upstream_zone_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_upstream_zone_module = { + NGX_MODULE_V1, + &ngx_stream_upstream_zone_module_ctx, /* module context */ + ngx_stream_upstream_zone_commands, /* module directives */ + NGX_STREAM_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_upstream_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ssize_t size; + ngx_str_t *value; + ngx_stream_upstream_srv_conf_t *uscf; + ngx_stream_upstream_main_conf_t *umcf; + + uscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_upstream_module); + umcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_upstream_module); + + value = cf->args->elts; + + if (!value[1].len) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid zone name \"%V\"", &value[1]); + return NGX_CONF_ERROR; + } + + if (cf->args->nelts == 3) { + size = ngx_parse_size(&value[2]); + + if (size == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid zone size \"%V\"", &value[2]); + return NGX_CONF_ERROR; + } + + if (size < (ssize_t) (8 * ngx_pagesize)) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "zone \"%V\" is too small", &value[1]); + return NGX_CONF_ERROR; + } + + } else { + size = 0; + } + + uscf->shm_zone = ngx_shared_memory_add(cf, &value[1], size, + &ngx_stream_upstream_module); + if (uscf->shm_zone == NULL) { + return NGX_CONF_ERROR; + } + + uscf->shm_zone->init = ngx_stream_upstream_init_zone; + uscf->shm_zone->data = umcf; + + uscf->shm_zone->noreuse = 1; + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_stream_upstream_init_zone(ngx_shm_zone_t *shm_zone, void *data) +{ + size_t len; + ngx_uint_t i; + ngx_slab_pool_t *shpool; + ngx_stream_upstream_rr_peers_t *peers, **peersp; + ngx_stream_upstream_srv_conf_t *uscf, **uscfp; + ngx_stream_upstream_main_conf_t *umcf; + + shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; + umcf = shm_zone->data; + uscfp = umcf->upstreams.elts; + + if (shm_zone->shm.exists) { + peers = shpool->data; + + for (i = 0; i < umcf->upstreams.nelts; i++) { + uscf = uscfp[i]; + + if (uscf->shm_zone != shm_zone) { + continue; + } + + uscf->peer.data = peers; + peers = peers->zone_next; + } + + return NGX_OK; + } + + len = sizeof(" in upstream zone \"\"") + shm_zone->shm.name.len; + + shpool->log_ctx = ngx_slab_alloc(shpool, len); + if (shpool->log_ctx == NULL) { + return NGX_ERROR; + } + + ngx_sprintf(shpool->log_ctx, " in upstream zone \"%V\"%Z", + &shm_zone->shm.name); + + + /* copy peers to shared memory */ + + peersp = (ngx_stream_upstream_rr_peers_t **) (void *) &shpool->data; + + for (i = 0; i < umcf->upstreams.nelts; i++) { + uscf = uscfp[i]; + + if (uscf->shm_zone != shm_zone) { + continue; + } + + peers = ngx_stream_upstream_zone_copy_peers(shpool, uscf); + if (peers == NULL) { + return NGX_ERROR; + } + + *peersp = peers; + peersp = &peers->zone_next; + } + + return NGX_OK; +} + + +static ngx_stream_upstream_rr_peers_t * +ngx_stream_upstream_zone_copy_peers(ngx_slab_pool_t *shpool, + ngx_stream_upstream_srv_conf_t *uscf) +{ + ngx_stream_upstream_rr_peer_t *peer, **peerp; + ngx_stream_upstream_rr_peers_t *peers, *backup; + + peers = ngx_slab_alloc(shpool, sizeof(ngx_stream_upstream_rr_peers_t)); + if (peers == NULL) { + return NULL; + } + + ngx_memcpy(peers, uscf->peer.data, sizeof(ngx_stream_upstream_rr_peers_t)); + + peers->shpool = shpool; + + for (peerp = &peers->peer; *peerp; peerp = &peer->next) { + /* pool is unlocked */ + peer = ngx_slab_calloc_locked(shpool, + sizeof(ngx_stream_upstream_rr_peer_t)); + if (peer == NULL) { + return NULL; + } + + ngx_memcpy(peer, *peerp, sizeof(ngx_stream_upstream_rr_peer_t)); + + *peerp = peer; + } + + if (peers->next == NULL) { + goto done; + } + + backup = ngx_slab_alloc(shpool, sizeof(ngx_stream_upstream_rr_peers_t)); + if (backup == NULL) { + return NULL; + } + + ngx_memcpy(backup, peers->next, sizeof(ngx_stream_upstream_rr_peers_t)); + + backup->shpool = shpool; + + for (peerp = &backup->peer; *peerp; peerp = &peer->next) { + /* pool is unlocked */ + peer = ngx_slab_calloc_locked(shpool, + sizeof(ngx_stream_upstream_rr_peer_t)); + if (peer == NULL) { + return NULL; + } + + ngx_memcpy(peer, *peerp, sizeof(ngx_stream_upstream_rr_peer_t)); + + *peerp = peer; + } + + peers->next = backup; + +done: + + uscf->peer.data = peers; + + return peers; +} diff --git a/app/nginx/src/stream/ngx_stream_variables.c b/app/nginx/src/stream/ngx_stream_variables.c new file mode 100644 index 0000000..5d15f3a --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_variables.c @@ -0,0 +1,1234 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + +static ngx_stream_variable_t *ngx_stream_add_prefix_variable(ngx_conf_t *cf, + ngx_str_t *name, ngx_uint_t flags); + +static ngx_int_t ngx_stream_variable_binary_remote_addr( + ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_variable_remote_addr(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_variable_remote_port(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_variable_proxy_protocol_addr( + ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_variable_proxy_protocol_port( + ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_variable_server_addr(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_variable_server_port(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_variable_bytes(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_variable_session_time(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_variable_status(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_variable_connection(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); + +static ngx_int_t ngx_stream_variable_nginx_version(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_variable_hostname(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_variable_pid(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_variable_msec(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_variable_time_iso8601(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_variable_time_local(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_variable_protocol(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); + + +static ngx_stream_variable_t ngx_stream_core_variables[] = { + + { ngx_string("binary_remote_addr"), NULL, + ngx_stream_variable_binary_remote_addr, 0, 0, 0 }, + + { ngx_string("remote_addr"), NULL, + ngx_stream_variable_remote_addr, 0, 0, 0 }, + + { ngx_string("remote_port"), NULL, + ngx_stream_variable_remote_port, 0, 0, 0 }, + + { ngx_string("proxy_protocol_addr"), NULL, + ngx_stream_variable_proxy_protocol_addr, 0, 0, 0 }, + + { ngx_string("proxy_protocol_port"), NULL, + ngx_stream_variable_proxy_protocol_port, 0, 0, 0 }, + + { ngx_string("server_addr"), NULL, + ngx_stream_variable_server_addr, 0, 0, 0 }, + + { ngx_string("server_port"), NULL, + ngx_stream_variable_server_port, 0, 0, 0 }, + + { ngx_string("bytes_sent"), NULL, ngx_stream_variable_bytes, + 0, 0, 0 }, + + { ngx_string("bytes_received"), NULL, ngx_stream_variable_bytes, + 1, 0, 0 }, + + { ngx_string("session_time"), NULL, ngx_stream_variable_session_time, + 0, NGX_STREAM_VAR_NOCACHEABLE, 0 }, + + { ngx_string("status"), NULL, ngx_stream_variable_status, + 0, NGX_STREAM_VAR_NOCACHEABLE, 0 }, + + { ngx_string("connection"), NULL, + ngx_stream_variable_connection, 0, 0, 0 }, + + { ngx_string("nginx_version"), NULL, ngx_stream_variable_nginx_version, + 0, 0, 0 }, + + { ngx_string("hostname"), NULL, ngx_stream_variable_hostname, + 0, 0, 0 }, + + { ngx_string("pid"), NULL, ngx_stream_variable_pid, + 0, 0, 0 }, + + { ngx_string("msec"), NULL, ngx_stream_variable_msec, + 0, NGX_STREAM_VAR_NOCACHEABLE, 0 }, + + { ngx_string("time_iso8601"), NULL, ngx_stream_variable_time_iso8601, + 0, NGX_STREAM_VAR_NOCACHEABLE, 0 }, + + { ngx_string("time_local"), NULL, ngx_stream_variable_time_local, + 0, NGX_STREAM_VAR_NOCACHEABLE, 0 }, + + { ngx_string("protocol"), NULL, + ngx_stream_variable_protocol, 0, 0, 0 }, + + { ngx_null_string, NULL, NULL, 0, 0, 0 } +}; + + +ngx_stream_variable_value_t ngx_stream_variable_null_value = + ngx_stream_variable(""); +ngx_stream_variable_value_t ngx_stream_variable_true_value = + ngx_stream_variable("1"); + + +static ngx_uint_t ngx_stream_variable_depth = 100; + + +ngx_stream_variable_t * +ngx_stream_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags) +{ + ngx_int_t rc; + ngx_uint_t i; + ngx_hash_key_t *key; + ngx_stream_variable_t *v; + ngx_stream_core_main_conf_t *cmcf; + + if (name->len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid variable name \"$\""); + return NULL; + } + + if (flags & NGX_STREAM_VAR_PREFIX) { + return ngx_stream_add_prefix_variable(cf, name, flags); + } + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + + key = cmcf->variables_keys->keys.elts; + for (i = 0; i < cmcf->variables_keys->keys.nelts; i++) { + if (name->len != key[i].key.len + || ngx_strncasecmp(name->data, key[i].key.data, name->len) != 0) + { + continue; + } + + v = key[i].value; + + if (!(v->flags & NGX_STREAM_VAR_CHANGEABLE)) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the duplicate \"%V\" variable", name); + return NULL; + } + + v->flags &= flags | ~NGX_STREAM_VAR_WEAK; + + return v; + } + + v = ngx_palloc(cf->pool, sizeof(ngx_stream_variable_t)); + if (v == NULL) { + return NULL; + } + + v->name.len = name->len; + v->name.data = ngx_pnalloc(cf->pool, name->len); + if (v->name.data == NULL) { + return NULL; + } + + ngx_strlow(v->name.data, name->data, name->len); + + v->set_handler = NULL; + v->get_handler = NULL; + v->data = 0; + v->flags = flags; + v->index = 0; + + rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v, 0); + + if (rc == NGX_ERROR) { + return NULL; + } + + if (rc == NGX_BUSY) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "conflicting variable name \"%V\"", name); + return NULL; + } + + return v; +} + + +static ngx_stream_variable_t * +ngx_stream_add_prefix_variable(ngx_conf_t *cf, ngx_str_t *name, + ngx_uint_t flags) +{ + ngx_uint_t i; + ngx_stream_variable_t *v; + ngx_stream_core_main_conf_t *cmcf; + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + + v = cmcf->prefix_variables.elts; + for (i = 0; i < cmcf->prefix_variables.nelts; i++) { + if (name->len != v[i].name.len + || ngx_strncasecmp(name->data, v[i].name.data, name->len) != 0) + { + continue; + } + + v = &v[i]; + + if (!(v->flags & NGX_STREAM_VAR_CHANGEABLE)) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the duplicate \"%V\" variable", name); + return NULL; + } + + v->flags &= flags | ~NGX_STREAM_VAR_WEAK; + + return v; + } + + v = ngx_array_push(&cmcf->prefix_variables); + if (v == NULL) { + return NULL; + } + + v->name.len = name->len; + v->name.data = ngx_pnalloc(cf->pool, name->len); + if (v->name.data == NULL) { + return NULL; + } + + ngx_strlow(v->name.data, name->data, name->len); + + v->set_handler = NULL; + v->get_handler = NULL; + v->data = 0; + v->flags = flags; + v->index = 0; + + return v; +} + + +ngx_int_t +ngx_stream_get_variable_index(ngx_conf_t *cf, ngx_str_t *name) +{ + ngx_uint_t i; + ngx_stream_variable_t *v; + ngx_stream_core_main_conf_t *cmcf; + + if (name->len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid variable name \"$\""); + return NGX_ERROR; + } + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + + v = cmcf->variables.elts; + + if (v == NULL) { + if (ngx_array_init(&cmcf->variables, cf->pool, 4, + sizeof(ngx_stream_variable_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + } else { + for (i = 0; i < cmcf->variables.nelts; i++) { + if (name->len != v[i].name.len + || ngx_strncasecmp(name->data, v[i].name.data, name->len) != 0) + { + continue; + } + + return i; + } + } + + v = ngx_array_push(&cmcf->variables); + if (v == NULL) { + return NGX_ERROR; + } + + v->name.len = name->len; + v->name.data = ngx_pnalloc(cf->pool, name->len); + if (v->name.data == NULL) { + return NGX_ERROR; + } + + ngx_strlow(v->name.data, name->data, name->len); + + v->set_handler = NULL; + v->get_handler = NULL; + v->data = 0; + v->flags = 0; + v->index = cmcf->variables.nelts - 1; + + return v->index; +} + + +ngx_stream_variable_value_t * +ngx_stream_get_indexed_variable(ngx_stream_session_t *s, ngx_uint_t index) +{ + ngx_stream_variable_t *v; + ngx_stream_core_main_conf_t *cmcf; + + cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module); + + if (cmcf->variables.nelts <= index) { + ngx_log_error(NGX_LOG_ALERT, s->connection->log, 0, + "unknown variable index: %ui", index); + return NULL; + } + + if (s->variables[index].not_found || s->variables[index].valid) { + return &s->variables[index]; + } + + v = cmcf->variables.elts; + + if (ngx_stream_variable_depth == 0) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "cycle while evaluating variable \"%V\"", + &v[index].name); + return NULL; + } + + ngx_stream_variable_depth--; + + if (v[index].get_handler(s, &s->variables[index], v[index].data) + == NGX_OK) + { + ngx_stream_variable_depth++; + + if (v[index].flags & NGX_STREAM_VAR_NOCACHEABLE) { + s->variables[index].no_cacheable = 1; + } + + return &s->variables[index]; + } + + ngx_stream_variable_depth++; + + s->variables[index].valid = 0; + s->variables[index].not_found = 1; + + return NULL; +} + + +ngx_stream_variable_value_t * +ngx_stream_get_flushed_variable(ngx_stream_session_t *s, ngx_uint_t index) +{ + ngx_stream_variable_value_t *v; + + v = &s->variables[index]; + + if (v->valid || v->not_found) { + if (!v->no_cacheable) { + return v; + } + + v->valid = 0; + v->not_found = 0; + } + + return ngx_stream_get_indexed_variable(s, index); +} + + +ngx_stream_variable_value_t * +ngx_stream_get_variable(ngx_stream_session_t *s, ngx_str_t *name, + ngx_uint_t key) +{ + size_t len; + ngx_uint_t i, n; + ngx_stream_variable_t *v; + ngx_stream_variable_value_t *vv; + ngx_stream_core_main_conf_t *cmcf; + + cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module); + + v = ngx_hash_find(&cmcf->variables_hash, key, name->data, name->len); + + if (v) { + if (v->flags & NGX_STREAM_VAR_INDEXED) { + return ngx_stream_get_flushed_variable(s, v->index); + } + + if (ngx_stream_variable_depth == 0) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "cycle while evaluating variable \"%V\"", name); + return NULL; + } + + ngx_stream_variable_depth--; + + vv = ngx_palloc(s->connection->pool, + sizeof(ngx_stream_variable_value_t)); + + if (vv && v->get_handler(s, vv, v->data) == NGX_OK) { + ngx_stream_variable_depth++; + return vv; + } + + ngx_stream_variable_depth++; + return NULL; + } + + vv = ngx_palloc(s->connection->pool, sizeof(ngx_stream_variable_value_t)); + if (vv == NULL) { + return NULL; + } + + len = 0; + + v = cmcf->prefix_variables.elts; + n = cmcf->prefix_variables.nelts; + + for (i = 0; i < cmcf->prefix_variables.nelts; i++) { + if (name->len >= v[i].name.len && name->len > len + && ngx_strncmp(name->data, v[i].name.data, v[i].name.len) == 0) + { + len = v[i].name.len; + n = i; + } + } + + if (n != cmcf->prefix_variables.nelts) { + if (v[n].get_handler(s, vv, (uintptr_t) name) == NGX_OK) { + return vv; + } + + return NULL; + } + + vv->not_found = 1; + + return vv; +} + + +static ngx_int_t +ngx_stream_variable_binary_remote_addr(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) + { + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + + switch (s->connection->sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) s->connection->sockaddr; + + v->len = sizeof(struct in6_addr); + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = sin6->sin6_addr.s6_addr; + + break; +#endif + + default: /* AF_INET */ + sin = (struct sockaddr_in *) s->connection->sockaddr; + + v->len = sizeof(in_addr_t); + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) &sin->sin_addr; + + break; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_variable_remote_addr(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + v->len = s->connection->addr_text.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = s->connection->addr_text.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_variable_remote_port(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + ngx_uint_t port; + + v->len = 0; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + v->data = ngx_pnalloc(s->connection->pool, sizeof("65535") - 1); + if (v->data == NULL) { + return NGX_ERROR; + } + + port = ngx_inet_get_port(s->connection->sockaddr); + + if (port > 0 && port < 65536) { + v->len = ngx_sprintf(v->data, "%ui", port) - v->data; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_variable_proxy_protocol_addr(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + v->len = s->connection->proxy_protocol_addr.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = s->connection->proxy_protocol_addr.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_variable_proxy_protocol_port(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + ngx_uint_t port; + + v->len = 0; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + v->data = ngx_pnalloc(s->connection->pool, sizeof("65535") - 1); + if (v->data == NULL) { + return NGX_ERROR; + } + + port = s->connection->proxy_protocol_port; + + if (port > 0 && port < 65536) { + v->len = ngx_sprintf(v->data, "%ui", port) - v->data; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_variable_server_addr(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + ngx_str_t str; + u_char addr[NGX_SOCKADDR_STRLEN]; + + str.len = NGX_SOCKADDR_STRLEN; + str.data = addr; + + if (ngx_connection_local_sockaddr(s->connection, &str, 0) != NGX_OK) { + return NGX_ERROR; + } + + str.data = ngx_pnalloc(s->connection->pool, str.len); + if (str.data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(str.data, addr, str.len); + + v->len = str.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = str.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_variable_server_port(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + ngx_uint_t port; + + v->len = 0; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + if (ngx_connection_local_sockaddr(s->connection, NULL, 0) != NGX_OK) { + return NGX_ERROR; + } + + v->data = ngx_pnalloc(s->connection->pool, sizeof("65535") - 1); + if (v->data == NULL) { + return NGX_ERROR; + } + + port = ngx_inet_get_port(s->connection->local_sockaddr); + + if (port > 0 && port < 65536) { + v->len = ngx_sprintf(v->data, "%ui", port) - v->data; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_variable_bytes(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + u_char *p; + + p = ngx_pnalloc(s->connection->pool, NGX_OFF_T_LEN); + if (p == NULL) { + return NGX_ERROR; + } + + if (data == 1) { + v->len = ngx_sprintf(p, "%O", s->received) - p; + + } else { + v->len = ngx_sprintf(p, "%O", s->connection->sent) - p; + } + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_variable_session_time(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + u_char *p; + ngx_time_t *tp; + ngx_msec_int_t ms; + + p = ngx_pnalloc(s->connection->pool, NGX_TIME_T_LEN + 4); + if (p == NULL) { + return NGX_ERROR; + } + + tp = ngx_timeofday(); + + ms = (ngx_msec_int_t) + ((tp->sec - s->start_sec) * 1000 + (tp->msec - s->start_msec)); + ms = ngx_max(ms, 0); + + v->len = ngx_sprintf(p, "%T.%03M", (time_t) ms / 1000, ms % 1000) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_variable_status(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + v->data = ngx_pnalloc(s->connection->pool, NGX_INT_T_LEN); + if (v->data == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(v->data, "%03ui", s->status) - v->data; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_variable_connection(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + u_char *p; + + p = ngx_pnalloc(s->connection->pool, NGX_ATOMIC_T_LEN); + if (p == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(p, "%uA", s->connection->number) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_variable_nginx_version(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + v->len = sizeof(NGINX_VERSION) - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) NGINX_VERSION; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_variable_hostname(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + v->len = ngx_cycle->hostname.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = ngx_cycle->hostname.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_variable_pid(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + u_char *p; + + p = ngx_pnalloc(s->connection->pool, NGX_INT64_LEN); + if (p == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(p, "%P", ngx_pid) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_variable_msec(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + u_char *p; + ngx_time_t *tp; + + p = ngx_pnalloc(s->connection->pool, NGX_TIME_T_LEN + 4); + if (p == NULL) { + return NGX_ERROR; + } + + tp = ngx_timeofday(); + + v->len = ngx_sprintf(p, "%T.%03M", tp->sec, tp->msec) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_variable_time_iso8601(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + u_char *p; + + p = ngx_pnalloc(s->connection->pool, ngx_cached_http_log_iso8601.len); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, ngx_cached_http_log_iso8601.data, + ngx_cached_http_log_iso8601.len); + + v->len = ngx_cached_http_log_iso8601.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_variable_time_local(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + u_char *p; + + p = ngx_pnalloc(s->connection->pool, ngx_cached_http_log_time.len); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, ngx_cached_http_log_time.data, ngx_cached_http_log_time.len); + + v->len = ngx_cached_http_log_time.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_variable_protocol(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + v->len = 3; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) (s->connection->type == SOCK_DGRAM ? "UDP" : "TCP"); + + return NGX_OK; +} + + +void * +ngx_stream_map_find(ngx_stream_session_t *s, ngx_stream_map_t *map, + ngx_str_t *match) +{ + void *value; + u_char *low; + size_t len; + ngx_uint_t key; + + len = match->len; + + if (len) { + low = ngx_pnalloc(s->connection->pool, len); + if (low == NULL) { + return NULL; + } + + } else { + low = NULL; + } + + key = ngx_hash_strlow(low, match->data, len); + + value = ngx_hash_find_combined(&map->hash, key, low, len); + if (value) { + return value; + } + +#if (NGX_PCRE) + + if (len && map->nregex) { + ngx_int_t n; + ngx_uint_t i; + ngx_stream_map_regex_t *reg; + + reg = map->regex; + + for (i = 0; i < map->nregex; i++) { + + n = ngx_stream_regex_exec(s, reg[i].regex, match); + + if (n == NGX_OK) { + return reg[i].value; + } + + if (n == NGX_DECLINED) { + continue; + } + + /* NGX_ERROR */ + + return NULL; + } + } + +#endif + + return NULL; +} + + +#if (NGX_PCRE) + +static ngx_int_t +ngx_stream_variable_not_found(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + v->not_found = 1; + return NGX_OK; +} + + +ngx_stream_regex_t * +ngx_stream_regex_compile(ngx_conf_t *cf, ngx_regex_compile_t *rc) +{ + u_char *p; + size_t size; + ngx_str_t name; + ngx_uint_t i, n; + ngx_stream_variable_t *v; + ngx_stream_regex_t *re; + ngx_stream_regex_variable_t *rv; + ngx_stream_core_main_conf_t *cmcf; + + rc->pool = cf->pool; + + if (ngx_regex_compile(rc) != NGX_OK) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc->err); + return NULL; + } + + re = ngx_pcalloc(cf->pool, sizeof(ngx_stream_regex_t)); + if (re == NULL) { + return NULL; + } + + re->regex = rc->regex; + re->ncaptures = rc->captures; + re->name = rc->pattern; + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + cmcf->ncaptures = ngx_max(cmcf->ncaptures, re->ncaptures); + + n = (ngx_uint_t) rc->named_captures; + + if (n == 0) { + return re; + } + + rv = ngx_palloc(rc->pool, n * sizeof(ngx_stream_regex_variable_t)); + if (rv == NULL) { + return NULL; + } + + re->variables = rv; + re->nvariables = n; + + size = rc->name_size; + p = rc->names; + + for (i = 0; i < n; i++) { + rv[i].capture = 2 * ((p[0] << 8) + p[1]); + + name.data = &p[2]; + name.len = ngx_strlen(name.data); + + v = ngx_stream_add_variable(cf, &name, NGX_STREAM_VAR_CHANGEABLE); + if (v == NULL) { + return NULL; + } + + rv[i].index = ngx_stream_get_variable_index(cf, &name); + if (rv[i].index == NGX_ERROR) { + return NULL; + } + + v->get_handler = ngx_stream_variable_not_found; + + p += size; + } + + return re; +} + + +ngx_int_t +ngx_stream_regex_exec(ngx_stream_session_t *s, ngx_stream_regex_t *re, + ngx_str_t *str) +{ + ngx_int_t rc, index; + ngx_uint_t i, n, len; + ngx_stream_variable_value_t *vv; + ngx_stream_core_main_conf_t *cmcf; + + cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module); + + if (re->ncaptures) { + len = cmcf->ncaptures; + + if (s->captures == NULL) { + s->captures = ngx_palloc(s->connection->pool, len * sizeof(int)); + if (s->captures == NULL) { + return NGX_ERROR; + } + } + + } else { + len = 0; + } + + rc = ngx_regex_exec(re->regex, str, s->captures, len); + + if (rc == NGX_REGEX_NO_MATCHED) { + return NGX_DECLINED; + } + + if (rc < 0) { + ngx_log_error(NGX_LOG_ALERT, s->connection->log, 0, + ngx_regex_exec_n " failed: %i on \"%V\" using \"%V\"", + rc, str, &re->name); + return NGX_ERROR; + } + + for (i = 0; i < re->nvariables; i++) { + + n = re->variables[i].capture; + index = re->variables[i].index; + vv = &s->variables[index]; + + vv->len = s->captures[n + 1] - s->captures[n]; + vv->valid = 1; + vv->no_cacheable = 0; + vv->not_found = 0; + vv->data = &str->data[s->captures[n]]; + +#if (NGX_DEBUG) + { + ngx_stream_variable_t *v; + + v = cmcf->variables.elts; + + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream regex set $%V to \"%v\"", &v[index].name, vv); + } +#endif + } + + s->ncaptures = rc * 2; + s->captures_data = str->data; + + return NGX_OK; +} + +#endif + + +ngx_int_t +ngx_stream_variables_add_core_vars(ngx_conf_t *cf) +{ + ngx_stream_variable_t *cv, *v; + ngx_stream_core_main_conf_t *cmcf; + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + + cmcf->variables_keys = ngx_pcalloc(cf->temp_pool, + sizeof(ngx_hash_keys_arrays_t)); + if (cmcf->variables_keys == NULL) { + return NGX_ERROR; + } + + cmcf->variables_keys->pool = cf->pool; + cmcf->variables_keys->temp_pool = cf->pool; + + if (ngx_hash_keys_array_init(cmcf->variables_keys, NGX_HASH_SMALL) + != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_array_init(&cmcf->prefix_variables, cf->pool, 8, + sizeof(ngx_stream_variable_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + for (cv = ngx_stream_core_variables; cv->name.len; cv++) { + v = ngx_stream_add_variable(cf, &cv->name, cv->flags); + if (v == NULL) { + return NGX_ERROR; + } + + *v = *cv; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_stream_variables_init_vars(ngx_conf_t *cf) +{ + size_t len; + ngx_uint_t i, n; + ngx_hash_key_t *key; + ngx_hash_init_t hash; + ngx_stream_variable_t *v, *av, *pv; + ngx_stream_core_main_conf_t *cmcf; + + /* set the handlers for the indexed stream variables */ + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + + v = cmcf->variables.elts; + pv = cmcf->prefix_variables.elts; + key = cmcf->variables_keys->keys.elts; + + for (i = 0; i < cmcf->variables.nelts; i++) { + + for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) { + + av = key[n].value; + + if (v[i].name.len == key[n].key.len + && ngx_strncmp(v[i].name.data, key[n].key.data, v[i].name.len) + == 0) + { + v[i].get_handler = av->get_handler; + v[i].data = av->data; + + av->flags |= NGX_STREAM_VAR_INDEXED; + v[i].flags = av->flags; + + av->index = i; + + if (av->get_handler == NULL + || (av->flags & NGX_STREAM_VAR_WEAK)) + { + break; + } + + goto next; + } + } + + len = 0; + av = NULL; + + for (n = 0; n < cmcf->prefix_variables.nelts; n++) { + if (v[i].name.len >= pv[n].name.len && v[i].name.len > len + && ngx_strncmp(v[i].name.data, pv[n].name.data, pv[n].name.len) + == 0) + { + av = &pv[n]; + len = pv[n].name.len; + } + } + + if (av) { + v[i].get_handler = av->get_handler; + v[i].data = (uintptr_t) &v[i].name; + v[i].flags = av->flags; + + goto next; + } + + if (v[i].get_handler == NULL) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "unknown \"%V\" variable", &v[i].name); + return NGX_ERROR; + } + + next: + continue; + } + + + for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) { + av = key[n].value; + + if (av->flags & NGX_STREAM_VAR_NOHASH) { + key[n].key.data = NULL; + } + } + + + hash.hash = &cmcf->variables_hash; + hash.key = ngx_hash_key; + hash.max_size = cmcf->variables_hash_max_size; + hash.bucket_size = cmcf->variables_hash_bucket_size; + hash.name = "variables_hash"; + hash.pool = cf->pool; + hash.temp_pool = NULL; + + if (ngx_hash_init(&hash, cmcf->variables_keys->keys.elts, + cmcf->variables_keys->keys.nelts) + != NGX_OK) + { + return NGX_ERROR; + } + + cmcf->variables_keys = NULL; + + return NGX_OK; +} diff --git a/app/nginx/src/stream/ngx_stream_variables.h b/app/nginx/src/stream/ngx_stream_variables.h new file mode 100644 index 0000000..8155111 --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_variables.h @@ -0,0 +1,111 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_STREAM_VARIABLES_H_INCLUDED_ +#define _NGX_STREAM_VARIABLES_H_INCLUDED_ + + +#include +#include +#include + + +typedef ngx_variable_value_t ngx_stream_variable_value_t; + +#define ngx_stream_variable(v) { sizeof(v) - 1, 1, 0, 0, 0, (u_char *) v } + +typedef struct ngx_stream_variable_s ngx_stream_variable_t; + +typedef void (*ngx_stream_set_variable_pt) (ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +typedef ngx_int_t (*ngx_stream_get_variable_pt) (ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); + + +#define NGX_STREAM_VAR_CHANGEABLE 1 +#define NGX_STREAM_VAR_NOCACHEABLE 2 +#define NGX_STREAM_VAR_INDEXED 4 +#define NGX_STREAM_VAR_NOHASH 8 +#define NGX_STREAM_VAR_WEAK 16 +#define NGX_STREAM_VAR_PREFIX 32 + + +struct ngx_stream_variable_s { + ngx_str_t name; /* must be first to build the hash */ + ngx_stream_set_variable_pt set_handler; + ngx_stream_get_variable_pt get_handler; + uintptr_t data; + ngx_uint_t flags; + ngx_uint_t index; +}; + + +ngx_stream_variable_t *ngx_stream_add_variable(ngx_conf_t *cf, ngx_str_t *name, + ngx_uint_t flags); +ngx_int_t ngx_stream_get_variable_index(ngx_conf_t *cf, ngx_str_t *name); +ngx_stream_variable_value_t *ngx_stream_get_indexed_variable( + ngx_stream_session_t *s, ngx_uint_t index); +ngx_stream_variable_value_t *ngx_stream_get_flushed_variable( + ngx_stream_session_t *s, ngx_uint_t index); + +ngx_stream_variable_value_t *ngx_stream_get_variable(ngx_stream_session_t *s, + ngx_str_t *name, ngx_uint_t key); + + +#if (NGX_PCRE) + +typedef struct { + ngx_uint_t capture; + ngx_int_t index; +} ngx_stream_regex_variable_t; + + +typedef struct { + ngx_regex_t *regex; + ngx_uint_t ncaptures; + ngx_stream_regex_variable_t *variables; + ngx_uint_t nvariables; + ngx_str_t name; +} ngx_stream_regex_t; + + +typedef struct { + ngx_stream_regex_t *regex; + void *value; +} ngx_stream_map_regex_t; + + +ngx_stream_regex_t *ngx_stream_regex_compile(ngx_conf_t *cf, + ngx_regex_compile_t *rc); +ngx_int_t ngx_stream_regex_exec(ngx_stream_session_t *s, ngx_stream_regex_t *re, + ngx_str_t *str); + +#endif + + +typedef struct { + ngx_hash_combined_t hash; +#if (NGX_PCRE) + ngx_stream_map_regex_t *regex; + ngx_uint_t nregex; +#endif +} ngx_stream_map_t; + + +void *ngx_stream_map_find(ngx_stream_session_t *s, ngx_stream_map_t *map, + ngx_str_t *match); + + +ngx_int_t ngx_stream_variables_add_core_vars(ngx_conf_t *cf); +ngx_int_t ngx_stream_variables_init_vars(ngx_conf_t *cf); + + +extern ngx_stream_variable_value_t ngx_stream_variable_null_value; +extern ngx_stream_variable_value_t ngx_stream_variable_true_value; + + +#endif /* _NGX_STREAM_VARIABLES_H_INCLUDED_ */ diff --git a/app/nginx/src/stream/ngx_stream_write_filter_module.c b/app/nginx/src/stream/ngx_stream_write_filter_module.c new file mode 100644 index 0000000..8fdcd37 --- /dev/null +++ b/app/nginx/src/stream/ngx_stream_write_filter_module.c @@ -0,0 +1,273 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef struct { + ngx_chain_t *from_upstream; + ngx_chain_t *from_downstream; +} ngx_stream_write_filter_ctx_t; + + +static ngx_int_t ngx_stream_write_filter(ngx_stream_session_t *s, + ngx_chain_t *in, ngx_uint_t from_upstream); +static ngx_int_t ngx_stream_write_filter_init(ngx_conf_t *cf); + + +static ngx_stream_module_t ngx_stream_write_filter_module_ctx = { + NULL, /* preconfiguration */ + ngx_stream_write_filter_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_write_filter_module = { + NGX_MODULE_V1, + &ngx_stream_write_filter_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_STREAM_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_stream_write_filter(ngx_stream_session_t *s, ngx_chain_t *in, + ngx_uint_t from_upstream) +{ + off_t size; + ngx_uint_t last, flush, sync; + ngx_chain_t *cl, *ln, **ll, **out, *chain; + ngx_connection_t *c; + ngx_stream_write_filter_ctx_t *ctx; + + ctx = ngx_stream_get_module_ctx(s, ngx_stream_write_filter_module); + + if (ctx == NULL) { + ctx = ngx_pcalloc(s->connection->pool, + sizeof(ngx_stream_write_filter_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_stream_set_ctx(s, ctx, ngx_stream_write_filter_module); + } + + if (from_upstream) { + c = s->connection; + out = &ctx->from_upstream; + + } else { + c = s->upstream->peer.connection; + out = &ctx->from_downstream; + } + + if (c->error) { + return NGX_ERROR; + } + + size = 0; + flush = 0; + sync = 0; + last = 0; + ll = out; + + /* find the size, the flush point and the last link of the saved chain */ + + for (cl = *out; cl; cl = cl->next) { + ll = &cl->next; + + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, + "write old buf t:%d f:%d %p, pos %p, size: %z " + "file: %O, size: %O", + cl->buf->temporary, cl->buf->in_file, + cl->buf->start, cl->buf->pos, + cl->buf->last - cl->buf->pos, + cl->buf->file_pos, + cl->buf->file_last - cl->buf->file_pos); + +#if 1 + if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "zero size buf in writer " + "t:%d r:%d f:%d %p %p-%p %p %O-%O", + cl->buf->temporary, + cl->buf->recycled, + cl->buf->in_file, + cl->buf->start, + cl->buf->pos, + cl->buf->last, + cl->buf->file, + cl->buf->file_pos, + cl->buf->file_last); + + ngx_debug_point(); + return NGX_ERROR; + } +#endif + + size += ngx_buf_size(cl->buf); + + if (cl->buf->flush || cl->buf->recycled) { + flush = 1; + } + + if (cl->buf->sync) { + sync = 1; + } + + if (cl->buf->last_buf) { + last = 1; + } + } + + /* add the new chain to the existent one */ + + for (ln = in; ln; ln = ln->next) { + cl = ngx_alloc_chain_link(c->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = ln->buf; + *ll = cl; + ll = &cl->next; + + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, + "write new buf t:%d f:%d %p, pos %p, size: %z " + "file: %O, size: %O", + cl->buf->temporary, cl->buf->in_file, + cl->buf->start, cl->buf->pos, + cl->buf->last - cl->buf->pos, + cl->buf->file_pos, + cl->buf->file_last - cl->buf->file_pos); + +#if 1 + if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "zero size buf in writer " + "t:%d r:%d f:%d %p %p-%p %p %O-%O", + cl->buf->temporary, + cl->buf->recycled, + cl->buf->in_file, + cl->buf->start, + cl->buf->pos, + cl->buf->last, + cl->buf->file, + cl->buf->file_pos, + cl->buf->file_last); + + ngx_debug_point(); + return NGX_ERROR; + } +#endif + + size += ngx_buf_size(cl->buf); + + if (cl->buf->flush || cl->buf->recycled) { + flush = 1; + } + + if (cl->buf->sync) { + sync = 1; + } + + if (cl->buf->last_buf) { + last = 1; + } + } + + *ll = NULL; + + ngx_log_debug3(NGX_LOG_DEBUG_STREAM, c->log, 0, + "stream write filter: l:%ui f:%ui s:%O", last, flush, size); + + if (size == 0 + && !(c->buffered & NGX_LOWLEVEL_BUFFERED) + && !(last && c->need_last_buf)) + { + if (last || flush || sync) { + for (cl = *out; cl; /* void */) { + ln = cl; + cl = cl->next; + ngx_free_chain(c->pool, ln); + } + + *out = NULL; + c->buffered &= ~NGX_STREAM_WRITE_BUFFERED; + + return NGX_OK; + } + + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "the stream output chain is empty"); + + ngx_debug_point(); + + return NGX_ERROR; + } + + chain = c->send_chain(c, *out, 0); + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "stream write filter %p", chain); + + if (chain == NGX_CHAIN_ERROR) { + c->error = 1; + return NGX_ERROR; + } + + for (cl = *out; cl && cl != chain; /* void */) { + ln = cl; + cl = cl->next; + ngx_free_chain(c->pool, ln); + } + + *out = chain; + + if (chain) { + if (c->shared) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "shared connection is busy"); + return NGX_ERROR; + } + + c->buffered |= NGX_STREAM_WRITE_BUFFERED; + return NGX_AGAIN; + } + + c->buffered &= ~NGX_STREAM_WRITE_BUFFERED; + + if (c->buffered & NGX_LOWLEVEL_BUFFERED) { + return NGX_AGAIN; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_write_filter_init(ngx_conf_t *cf) +{ + ngx_stream_top_filter = ngx_stream_write_filter; + + return NGX_OK; +} -- cgit 1.2.3-korg