Mercurial > hg > nginx-quic
view src/stream/ngx_stream_proxy_module.c @ 6774:bcb107bb89cd
Mail: support SASL EXTERNAL (RFC 4422).
This is needed to allow TLS client certificate auth to work. With
ssl_verify_client configured, the auth daemon can choose to allow the
connection to proceed based on the certificate data.
This has been tested with Thunderbird for IMAP only. I've not yet found a
client that will do client certificate auth for POP3 or SMTP, and the method is
not really documented anywhere that I can find. That said, its simple enough
that the way I've done is probably right.
author | Rob N ★ <robn@fastmail.com> |
---|---|
date | Sat, 08 Oct 2016 18:05:00 +1100 |
parents | edcd9303a4d3 |
children | 563a1ee345a4 |
line wrap: on
line source
/* * Copyright (C) Roman Arutyunyan * Copyright (C) Nginx, Inc. */ #include <ngx_config.h> #include <ngx_core.h> #include <ngx_stream.h> 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; 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; } 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->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 && url.addrs[0].sockaddr) { u->resolved->sockaddr = url.addrs[0].sockaddr; u->resolved->socklen = url.addrs[0].socklen; u->resolved->naddrs = 1; u->resolved->host = url.addrs[0].name; } else { 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; 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, 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) { 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; }